react管理状态的工具:
1、利用hooks进行状态管理;
2、利用Redux进行状态管理,这种方式的配套工具比较齐全,可以自定义各种中间件;
3、利用Mobx进行状态管理,它通过透明的函数响应式编程使得状态管理变得简单和可扩展。
2013 年 5 月 React 诞生。但 2015 年之前,大概都是 jQuery 的天下。2015 年 3 月 React 0.13.0 发布,带来了 class 组件写法。
在 React class 组件时代,状态就是 this.state,使用 this.setState 更新。
为避免一团乱麻,React 引入了 "组件" 和 "单向数据流" 的理念。有了状态与组件,自然就有了状态在组件间的传递,一般称为 "通信"。
父子通信较简单,而深层级、远距离组件的通信,则依赖于 "状态提升" + props 层层传递。
于是,React 引入了 Context,一个用于解决组件 "跨级" 通信的官方方案。
但 Context 其实相当于 "状态提升",并没有额外的性能优化,且写起来比较啰嗦。
为优化性能,一般会添加多个 Context,写起来就更啰嗦。在项目没那么复杂时,还不如层层传递简单。
Context 没那么好用,React 官方也没什么最佳实践,于是一个个社区库就诞生了。
目前比较常用的状态管理方式有hooks、redux、mobx三种。
(1).组件的特点
组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据
在组件化过程中,通常会将一个完整的功能拆分成多个组件,以更好的完成整个应用的功能
(2).知道组件通讯意义
而在这个过程中,多个组件之间不可避免的要共享某些数据
为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通、这个过程就是组件通讯
父组件向子组件通信是通过父组件的props传递数据完成。
UserList.jsx接收父组件的数据,展示用户信息,子组件:
import React, { Component } from 'react'
export default class UserList extends Component {
render() {
return (
<div>
<ul>
{this.props.users.map(user =><li key={user.id}>
{user.name}
</li>)}
</ul>
</div>
)
}
}
UserListContainer.jsx向子组件传递数据,父组件:
import React, { Component } from 'react'
import UserList from './UserList'
export default class UserListContainer extends Component {
state={users:[]}
componentDidMount(){
const users=[
{id:"1001",name:"Jone"},
{id:"1002",name:"Mali"},
{id:"1003",name:"Locy"},
{id:"1004",name:"Rose"},
{id:"1005",name:"Jack"}
]
this.setState({users:users});
}
render() {
return (
<div><div>用户信息列表</div>
<UserList users={this.state.users}/>
</div>
)
}
}
运行结果:
解释:数据users在父组件中通过属性传递给子组件UserList,在UserList中通过props接收父组件传入的数据,完成父传子,这是最简单,最基本的一个状态的传递方法,推荐常用。
子传父依然使用props,父组件先给子组件传递一个回调函数,子组件调用父组件的回调函数传入数据,父组件处理数据即可。
在UserList中添加新增加功能:
import React, { Component } from 'react'
export default class UserList extends Component {
state={newUser:""}
handleChange=e=>{
this.setState({newUser:e.target.value});
}
handleClick=e=>{
if(this.state.newUser&&this.state.newUser.length>0){
this.props.onAddUser(this.state.newUser);
}
}
render() {
return (
<div>
<ul>
{this.props.users.map(user =><li key={user.id}>
{user.name}
</li>)}
</ul>
<div>
姓名:<input type="text" onChange={this.handleChange} value={this.state.newUser}></input>
<button onClick={this.handleClick} type="submit">新增</button>
</div>
</div>
)
}
}
在UserListContainer中添加onAddUser参数与函数:
import React, { Component } from 'react'
import UserList from './UserList'
export default class UserListContainer extends Component {
state={users:[]}
componentDidMount(){
const users=[
{id:"1001",name:"Jone"},
{id:"1002",name:"Mali"},
{id:"1003",name:"Locy"},
{id:"1004",name:"Rose"},
{id:"1005",name:"Jack"}
]
this.setState({users:users});
}
onAddUser(newUser){
let users=this.state.users;
this.setState({users:users.concat({
id:parseInt((users[users.length-1].id)+1)+"",
name:newUser
})});
}
render() {
return (
<div><div>用户信息列表</div>
<UserList users={this.state.users} onAddUser={this.onAddUser.bind(this)}/>
</div>
)
}
}
运行:
解释:在子组件中用户输入了一个新的姓名,调用props.addUser方法将新添加的用户信息发送给父组件完成添加功能,所以这里实现了子传父功能。
* UserListContainer中包含UserList组件,所以UserListContainer是父组件,而UserList是子组件
* 子组件通过调用父组件中的onAddUser方法将输入的用户添加到集合中,完成子传父功能
兄弟组件不能直接相互传送数据,需要通过状态提升的方式实现兄弟组件的通信,即把组件之间需要共享的状态保存到距离它们最近的共同父组件内,任意一个兄弟组件都可以通过父组件传递的回调函数来修改共享状态,父组件中共享状态的变化也会通过props向下传递给所有兄弟组件,从而完成兄弟组件之间的通信。
我们在UserListContainer中新增一个子组件UserDetail,用于显示当前选中用户的详细信息,比如用户的年龄、联系方式、家庭地址等。这时,UserList 和 UserDetail 就成了兄弟组件,UserListContainer是它们的共同父组件。当用户在 UserList中点击一条用户信息时,UserDetail需要同步显示该用户的详细信息,因此,可以把当前选中的用户 currentUser保存到UserListContainer的状态中。
UserList.jsx
import React, { Component } from 'react'
import "./css/userList.css"
/**
* 用户列表组件
*/
export default class UserList extends Component {
/**
* 构造函数
* @param {*} props
*/
constructor(props) {
super(props);
this.state = { newUser: ""};
}
/**
* 输入框内容变化事件
* @param {*} e
*/
handleChange = e => {
this.setState({ newUser: e.target.value });
}
/**
* 新增用户按钮点击事件
* @param {*} e
*/
handleClick = e => {
if (this.state.newUser && this.state.newUser.length > 0) {
this.props.onAddUser(this.state.newUser);
}
}
/**
* 用户列表项点击事件
* @param {*} userId
*/
handleSelect(userId) {
this.props.onSetCurrentUser(userId);
}
/**
* 渲染函数
*/
render() {
return (
<div>
<ul>
{this.props.users.map(user => <li key={user.id} className={user.id === this.props.currentUserId ? "active" : ""} onClick={this.handleSelect.bind(this, user.id)}>
{user.name}
</li>)}
</ul>
<div>
姓名:<input type="text" onChange={this.handleChange} value={this.state.newUser}></input>
<button onClick={this.handleClick} type="submit">新增</button>
</div>
</div>
)
}
}
UserDetails.jsx
import React, { Component } from 'react'
export default class UserDetails extends Component {
render() {
return (
this.props.currentUser?
<div>
<h2>详细信息</h2>
<fieldset>
<legend>用户</legend>
<p>
{/* 用户编号 */}
编号:{this.props.currentUser.id}
</p>
<p>
{/* 用户姓名 */}
姓名:{this.props.currentUser.name}
</p>
</fieldset>
</div>:"" )
}
}
UserListContainer.jsx
import React, { Component } from 'react'
import UserList from './UserList'
import UserDetails from './UserDetails'
export default class UserListContainer extends Component {
state={users:[],currentUserId:null}
componentDidMount(){
const users=[
{id:"1001",name:"Jone"},
{id:"1002",name:"Mali"},
{id:"1003",name:"Locy"},
{id:"1004",name:"Rose"},
{id:"1005",name:"Jack"}
]
this.setState({users:users});
}
// 添加用户
onAddUser(username){
let users=this.state.users;
let newUser={
id:(parseInt(users[users.length-1].id)+1)+"",
name:username
};
this.setState({users:users.concat(newUser),currentUserId:newUser.id});
}
// 设置当前用户
onSetCurrentUser(userId){
this.setState({currentUserId:userId});
}
render() {
const users=this.state.users.filter(user=>user.id===this.state.currentUserId);
const currentUser=users&&users.length>0&&users[0];
return (
<div><div>用户信息列表</div>
<UserList users={this.state.users} onAddUser={this.onAddUser.bind(this)} onSetCurrentUser={this.onSetCurrentUser.bind(this)} currentUserId={this.state.currentUserId}/>
<UserDetails currentUser={currentUser} />
</div>
)
}
}
运行效果:
解释:在子组件UserList中添加一个username,通过onAddUser将username传入父组件UserListContainer中,这里完成了状态提升,在UserListContainer中再将新添加的用户传入给UserDetail组件,实现从父传给子组件,整个过程实现了兄弟之间的数据传递功能。
UserListPro.jsx
import React, { Component } from 'react'
import "./css/userListPro.css"
export default class UserListPro extends Component {
state={username:""}
usernameChange=(e)=>{
this.setState({username:e.target.value})
}
handleSubmit=e=>{
const {username} = this.state;
if(username&&username.length>0){
//调用父组件的onAddUser方法将值username传递给父组件
this.props.onAddUser(this.state.username);
}
e.preventDefault();
};
selectHandle=(e)=>{
//将当前选择的用户编号传给父组件
this.props.onSetCurrentId(e.target.id);
};
render() {
console.log(this);
return (
<div>
<ul className='userDetail'>
{this.props.users.map(user=><li
key={user.id}
id={user.id}
onClick={this.selectHandle}
className={user.id===this.props.currentId?"active":""}
>{user.id} - {user.name}</li>)}
</ul>
<div>
<form onSubmit={this.handleSubmit}>
<p>
<label>姓名:</label>
<input type="text" value={this.state.username} onChange={this.usernameChange}/>
<button>添加</button>
</p>
</form>
</div>
</div>
)
}
}
UserListContainer.jsx
import React, { Component } from 'react'
import UserListPro from './UserListPro';
import UserDetail from './UserDetail';
/**
* UserListContainer中包含UserList组件,所以UserListContainer是父组件,而UserList是子组件
* 子组件通过调用父组件中的onAddUser方法将输入的用户添加到集合中,完成子传父功能
*/
export default class UserListContainer extends Component {
//currentId用于记录当前用户的编号
state={users:[],currentId:null}
componentDidMount(){
const users=[
{id:"1001",name:"Jone"},
{id:"1002",name:"Mali"},
{id:"1003",name:"Locy"},
{id:"1004",name:"Rose"},
{id:"1005",name:"Jack"}
];
this.setState({users:users});
}
//新添加用户
onAddUser(username){
//生成新的编号
//let id=parseInt(this.state.users[this.state.users.length-1].id)+1;
let id=this.state.users[this.state.users.length-1].id*1+1;
//将添加的新用户
const user={id,name:username};
//将新用户添加到users状态中
this.setState({users:this.state.users.concat(user),currentId:id});
}
//子组件通过该方法设置当前用户的编号
onSetCurrentId=(id)=>{
this.setState({currentId:id});
}
render() {
//根据用户编号从用户集合中获取用户集合
const users=this.state.users.filter(user => user.id === this.state.currentId);
//当前用户
let currentUser=null;
//如果查找了
if(users&&users.length>0){
//设置当前用户
currentUser=users[0];
}
return (
<div>
<h2>用户列表</h2>
<UserListPro
users={this.state.users}
onAddUser={this.onAddUser.bind(this)}
currentId={this.state.currentId}
onSetCurrentId={this.onSetCurrentId}
/>
<div>
<UserDetail currentUser={currentUser}></UserDetail>
</div>
</div>
)
}
}
UserDetail.jsx
import React, { Component } from 'react'
export default class UserDetail extends Component {
render() {
return (
this.props.currentUser?
<div>
<h2>用户详情</h2>
<fieldset>
<legend>详细</legend>
<p>
编号:{this.props.currentUser.id}
</p>
<p>
姓名:{this.props.currentUser.name}
</p>
</fieldset>
</div>
:""
)
}
}
css/userListPro.css
.userDetail li{
cursor: pointer;
}
.userDetail li:hover{
background: lightyellow;
}
.active{
background: lightyellow;
}
运行结果:
当组件所处层级太深时,往往需要经过很层的props传递才能将所需的数据或者回调函数传递给使用组件,所以props作为桥梁通信便会显得很麻烦。React提供了一个context上下文,让任意层级的子组件都可以获取父组件中的状态和方法。
Parent.jsx 父
import React, { Component } from 'react'
import Sub1 from './Sub1';
export default class Parent extends Component {
state={
n:100
}
setN(n){
this.setState({n});
}
render() {
return (
<div style={{backgroundColor:"lightblue"}}>
<ul>
<li>
<div>
<h2>父组件 n={this.state.n}</h2>
</div>
<Sub1 onSetN={this.setN.bind(this)}></Sub1>
</li>
</ul>
</div>
)
}
}
Sub1.jsx 子
import React, { Component } from 'react'
import Sub11 from './Sub11';
export default class Sub1 extends Component {
setNumber() {
let n=parseInt(this.txtInput.value);
this.props.onSetN(n);
}
render() {
return (
<div style={{background:"lightred"}}>
<ul>
<li>
<h2>子组件:Sub1</h2>
<p>
<input ref={input=>this.txtInput=input} type="text" />
<button onClick={this.setNumber.bind(this)}>设置N的值</button>
</p>
<Sub11 onSetN={this.props.onSetN}/>
</li>
</ul>
</div>
)
}
}
Sub11.jsx 孙
import React, { Component } from 'react'
export default class Sub11 extends Component {
setNumber() {
let n=parseInt(this.txtInput.value);
this.props.onSetN(n);
}
render() {
return (
<div style={{background:"lightred"}}>
<ul>
<li>
<h2>孙组件:Sub11</h2>
<p>
<select ref={input=>this.txtInput=input} type="text">
<option value={300}>300</option>
<option value={600}>600</option>
<option value={900}>900</option>
</select>
<button onClick={this.setNumber.bind(this)}>设置N的值</button>
</p>
</li>
</ul>
</div>
)
}
}
结果:
解释:
当组件所处层级太深时,往往需要经过很层的props传递才能将所需的数据或者回调函数传递给使用组件,所以props作为桥梁通信便会显得很麻烦。React提供了一个context上下文,让任意层级的子组件都可以获取父组件中的状态和方法。
每个组件都拥有context属性,可以查看到:
getChildContext:与访问context属性需要通过contextTypes指定可访问的属性一样,getChildContext指定的传递给子组件的属性需要先通过childContextTypes来执行,不然会报错。
使用context改进后的示例如下:
Parent.jsx 父
import React, { Component } from 'react'
import {PropTypes} from 'prop-types'
import Sub1 from './Sub1';
class Parent extends Component {
state={
n:100
}
setN=(n)=>{
this.setState({n});
}
getChildContext(){
return {onSetN: this.setN}
}
render() {
return (
<div style={{backgroundColor:"lightblue"}}>
<ul>
<li>
<div>
<h2>父组件 n={this.state.n}</h2>
</div>
<Sub1></Sub1>
</li>
</ul>
</div>
)
}
}
//声明context的属性的类型信息
Parent.childContextTypes = {
onSetN:PropTypes.func
}
export default Parent;
Sub1.jsx 子
import React, { Component } from 'react'
import Sub11 from './Sub11';
import {PropTypes} from 'prop-types'
class Sub1 extends Component {
setNumber() {
let n=parseInt(this.txtInput.value);
this.context.onSetN(n);
}
render() {
return (
<div style={{background:"lightred"}}>
<ul>
<li>
<h2>子组件:Sub1</h2>
<p>
<input ref={input=>this.txtInput=input} type="text" />
<button onClick={this.setNumber.bind(this)}>设置N的值</button>
</p>
<Sub11/>
</li>
</ul>
</div>
)
}
}
//声明要使用的context属性的类型信息
Sub1.contextTypes={
onSetN: PropTypes.func
}
export default Sub1;
Sub11.jsx 孙
import React, { Component } from 'react'
import {PropTypes} from 'prop-types'
class Sub11 extends Component {
setNumber() {
let n=parseInt(this.txtInput.value);
this.context.onSetN(n);
console.log(this);
}
render() {
return (
<div style={{background:"lightred"}}>
<ul>
<li>
<h2>孙组件:Sub11</h2>
<p>
<select ref={input=>this.txtInput=input} type="text">
<option value={300}>300</option>
<option value={600}>600</option>
<option value={900}>900</option>
</select>
<button onClick={this.setNumber.bind(this)}>设置N的值</button>
</p>
</li>
</ul>
</div>
)
}
}
//声明要使用的context属性的类型信息
Sub11.contextTypes={
onSetN: PropTypes.func
}
export default Sub11;
结果:
1、数组解构就是能快速提取数组中的指定成员(数组的某一项值或所有的值)
例如:
解构赋值都是一一对应的,按照顺序。
const arr = [200,300,400]
const [a,b,c] = arr
console.log(a,b,c) // 200,300,400
也可以取数组的某一项值(结构必须保持一致)
const arr = [200,300,400]
const [, , c] = arr
console.log(c) // 400
还可在用“...”的方式提取所有的成员(注意的是这种...的写法只能在解构成员的最后一个成员使用)代码如下
const arr = [200,300,400]
const [a,...all] = arr
console.log(all) // [300,400] 会返回得到一个最后所有的数组
如果提取的解构成员小于数组的长度,就会从前到后的顺序来提取,代码如
const arr = [200,300,400]
const [a] = arr
console.log(a) // 200 按顺序提取第一个
如果提取成员大于数组长度,那么最后的提取的最后是undefined,代码如下
const arr = [200,300,400]
const [a,b,c,d] = arr
console.log(d) // undefined
2、对象解构和数组解构基本类似,只不过对象解构的取值方式是根据对象的属性名来取值
例如:
const obj = {name:'100',age:'30',size:'M'}
const { name } = obj
console.log(name) // 100
顺便说一下,对象里面的属性名和其他自定义的变量名称如果重名的时候要怎么解决,一旦重名就会报错,看代码:
const obj = {name:'100',age:'30',size:'M'}
const name = 'lucy'
const {name} = obj
console.log(name) // 会报错
// 要么重新命名,要么可以按照下面的写法来避免
const obj = {name:'100',age:'30',size:'M'}
const name = 'lucy'
const {name:nameObj} = obj // 对象属性名称的重新指定
console.log(nameObj) // 100
思考题:
请问下面的代码是什么意思?控制台输出什么?
<script>
function useBook() {
return ["ES6高级编程", (bookname) => console.log(bookname + "!")];
}
const [name, showBook] = useBook();
showBook(name);
</script>
答案:
ES6高级编程!
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state,一般搭配函数式组件使用。
在React 16.8之前,函数式组件只能作为无状态组件(只读组件),即不能进行状态管理。
函数式创建组件通常是无状态组件,这种方式没有办法在内部对状态统一管理,如果我们非要添加状态管理呢,那就只能借助redux啦~或者我们自己利用观察者模式实现一个发布订阅。
那么如果我们非要这么做,React版本在16.8.X以后增添了一个新特性就是hooks。
hooks涉及API有useState、 useEffect、 useCallback、 useRef、 useMemo、 React.memo、 useReducer等,具体可以参考官方文档。
搭配函数式组件,主要是可以进行组件的状态管理,好处是不像传统state需要注意this指向(函数式组件中没有this)。
一般用法 const [ a , setA ] = useState(初始值)
a表示组件需要声明的变量a,setA允许你在组件其它的位置对a的数据进行改变setA(2),即a的值将为2
一个组件中可以声明多个useState()
Counter3.jsx
import React,{useState} from 'react';
export default function Counter3(props){
let [count,setCount]=useState(0);
return (<div>
<h2>{props.name}</h2>
<p>
<button onClick={()=>setCount(count+1)}>{count}</button>
</p>
</div>);
}
调用
const vnode = (
<div>
<Counter3 name="函数式组件使用状态的计数器" />
</div>
);
结果:
不管在Vue中还是React,如果我们想使用一个元素的DOM,不需要通过JS中操纵DOM的方法,它们提供了一个专属的API就是ref。
而Vue中的ref可能比较简单,这一篇主要讲一下如何在React中使用ref,以及使用ref的场景。
在React中,ref可以挂载到html元素上,同时也可以挂载在React元素上,看下面的代码:
import React, { Component } from 'react'
// import { findDOMNode } from 'react-dom'
import Child from './Child'
export default class Father extends Component {
componentDidMount(){
console.log(this.refs.refElement);
console.log(this.refs.child);
}
render() {
return (
<div>
<input ref={ 'refElement' }></input>
<Child ref={ 'child' }/>
<button onClick={this.fn}>123</button>
</div>
)
}
}
控制台的打印为:
可以看到,在React中,ref是可以挂载到HTML元素和React元素上的。
(1)挂载HTML元素,返回真实的DOM
(2)挂载React元素,返回render后的实例对象
同时React也提供了一个方法findDOMNode可以将React元素的ref返回变成真实的DOM元素。
import { findDOMNode } from 'react-dom'
console.log(findDOMNode(this.refs.child));
同时在上面的代码我们也可以看出来,ref的挂载是在componentDidMount等生命周期之前执行的。
import React, { Component } from 'react'
export default class Father extends Component {
componentDidMount(){
console.log(this.refs.refElement);
}
render() {
return (
<div>
<input ref={ 'refElement' }></input>
<button onClick={this.fn}>123</button>
</div>
)
}
}
这种方式和Vue的ref比较相似,但是官方目前已经不推荐使用该方式,后续可能还会废弃。
import React, { Component } from 'react'
export default class Father extends Component {
componentDidMount(){
console.log(this.refElement);
}
render() {
return (
<div>
<input ref={ ref => this.refElement = ref }></input>
<button onClick={this.fn}>获取元素</button>
</div>
)
}
}
import React, { Component } from 'react'
export default class Father extends Component {
refElement = React.createRef();
componentDidMount(){
console.log(this.refElement.current);
}
render() {
return (
<div>
<input ref={this.refElement}></input>
<button onClick={this.fn}>123</button>
</div>
)
}
}
住这里面通过refElement中的current,获取真实的DOM元素。
6.1、使用多种方法实现页面加载完成时让搜索文本框获取焦点,侧重练习ref的使用。
6.2、完成所有的上课示例。