state
state允许React组件在不违反props规则的情况下,根据用户操作,网络影响,或者其他随便什么东西,来动态改变其输出,类似于vue中的data
state的定义
constructor(props) { super(props); this.state = { posts: [], comments: [] }; }
state不能直接修改,需要调用this.setState()
// 错误 这样将不会重新渲染一个组件:
this.state.comment = 'Hello';
// 正确 用 setState() 代替:
this.setState({comment: 'Hello'});
唯一可以分配 this.state 的地方是构造函数。
state(状态)更新可能是异步的
使用回调函数的形式实现异步操作
// 错误
this.setState({
counter: this.state.counter + this.props.increment,
});
// 正确
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
使用另一种 setState() 的形式,它接受一个函数而不是一个对象。
这个函数将接收前一个状态作为第一个参数,应用更新时的 props 作为第二个参数:
state(状态)更新会被合并
你的状态可能包含几个独立的变量
调用this.setState()修改单个的状态不会影响其他的状态,只会合并当前修改这一个状态
hook函数请参考链接
https://baike.baidu.com/item/%E9%92%A9%E5%AD%90%E5%87%BD%E6%95%B0?fr=aladdin
react组件的三个状态
1、mount
react Components被render解析生成对应的dom节点被插入浏览器的dom结构一个的过程
在浏览器看到组件元素从无到有的过程
2、update
一个mounte的react Component被重新render的过程,但是在这个过程中,dom结构并不一定会发生改变
在react中,状态的改变会触发update的hook函数
3、unmount
一个mount的react Component对应的dom节点被从dom中移除的一个过程
react针对以上三种状态都封装了hook函数
生命周期钩子
mounting
如下这些方法在组件实例被创建和被插入到dom中时被调用
1.constructor()
constructor初始化state的好地方,如果我们不需要初始化state,也不需要bind任何方法,那么在我们的组件中不需要实现constructor函数。
constructor在组件被mounted之前调用,我们的组件继承自React.Component,constructor函数中我们在其他操作前应该先调用super(props),否则this.props将会是undefined
注意下面的情况,很容易产生bug,我们通常的做法是提升state到父组件,而不是使劲的同步state和props
constructor(props) { super(props); this.state = { color: props.initialColor }; }
2.componentWillMount()
此方法在mounting之前被立即调用,它在render()之前调用,因此在此方法中setState不会触发重新渲染。此方法是服务器渲染中调用的唯一生命周期钩子,通常我们建议使用constructor()。
3.render()
render()方法是react组件必须的,它检查this.props和this.state并且返回一个React元素,我们也可以返回null和false,代表我们不想有任何的渲染
render()方法是一个纯方法,即它不会修改组件的state,在每一次调用是返回同样的结果。它不直接和浏览器交互,如果我们想要交互,应该在componentDidMount()或者其他的生命周期函数里面
4.componentDIDMount()
此方法在组件被mounted之后立即被调用,初始化dom节点应该在此方法中,如需要从远端健在数据,这里是实例化网络请求的好地方,此方法中setState会触发组件重新渲染
Updating
props和state的改变产生更新。在重新渲染组建时,如下的方法被调用
1.componentWillReceiveProps()
一个已经mounted的组件接收一个新的props之前componentWillReceiveProps()被调用,如果我们需要更新state来响应prop的更改,我们可以在此方法中比较this.props和nextProps并使用this.setState来更改state。
注意,即使props没有改变,React也可以调用这个方法,因此如果你只想处理改变,请确保比较当前值和下一个值。当父组件导致你的组件重新渲染时,可能会发生这种情况。
React在组件mounting期间不会调用此方法,只有在一些组件的props可能被更新的时候才会调用。调用this.setState通常不会触发componentWillReceiveProps。
2.shouldComponentUpdate()
使用此方法让React知道组件的输出是否不受当前state或props更改的影响。默认行为是在每次state更改时重新渲染组件,在大多数情况下,我们应该默认改行为。
当接收到新的props或state时,shouldComponentUpdate()在渲染之前被调用。默认返回true,对于初始渲染或使用forceUpdate()时,不调用此方法。返回false不会阻止子组件的state更改时,该子组件重新渲染。
如果shouldComponentUpdate()返回false,那么componentWillUpdate(),render()和componentDidUpdate()将不会被调用。在将来,React可能将shouldComponentUpdate()作为提示而不是strict指令,返回仍然可能导致组件重新渲染。
3.componentWillUpdate()
当接收新的props或state时,componentWillUpdate()在组件渲染之前被立即调用。使用此函数作为在更新发生之前执行准备的机会。初始渲染不会调用此方法。
注意:这里不能调用this.setState()(如果调用会怎么样?好奇心很重呀,试了一下,会产生死循环,一直更新。
如果我们需要更新state以响应props的更改,我们应该使用componentWillReceiveProps()
4.render()
render()方法是react组件必须的,它检查this.props和this.state并且返回一个React元素,我们也可以返回null或false,代表我们不想有任何的渲染。
render()方法应该是一个纯方法,即它不会修改组件的state,在每一次调用时返回同样的结果。它不直接和浏览器交互,如果我们想要交互,应该在componentDidMount()或者其他的生命周期函数里面。
5.componentDidUpdate()
此函数在更新后立即被调用。初始渲染不调用此方法。
当组件已经更新时,使用此操作作为DOM操作的机会。这也是一个好的地方做网络请求,只要你比较当前的props和以前的props(例如:如果props没有改变,可能不需要网络请求)。
Unmounting
1.componentWillUnmount()
当从dom中移除组件时,这个方法会被调用
此函数在组件被卸载和销毁之前被立即调用。在此方法中执行一些必要的清理。例如清除计时器,取消网络请求或者清理在componentDidMount中创建的任何DOM元素。
演示
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello World</title> <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <script crossorigin src="https://unpkg.com/[email protected]/babel.min.js"></script> </head> <body> <div id="root"></div> </body> </html> <script type="text/babel"> // 创建一个类组件 class LifeCycle extends React.Component { // 1. mount阶段 constructor(props) { super(props); this.state = {name: 'wlt'}; this.changeState = this.changeState.bind(this); console.log('constructor'); } componentWillMount() { console.log('componentWillMount'); } render() { console.log('render'); return ( <div className="life-cycle-bg"> <p>Hello {this.props.value}</p> <p>Hello {this.state.name}</p> <button onClick={this.changeState}>改变lifeCycle的state</button> </div> ); } componentDidMount() { console.log('componentDidMount'); } // 2. update阶段 componentWillReceiveProps(nextProps) { // 只作用于属性的变化,不作用于状态的变化 console.log('componentWillReceiveProps'); } shouldComponentUpdate(nextProps, nextState) { console.log('shouldComponentUpdate'); return true; } componentWillUpdate(nextProps, nextState) { console.log('componentWillUpdate'); } // ---- render会重新执行 componentDidUpdate(prevProps, prevState) { console.log('componentDidUpdate'); } // 3. Unmount阶段 componentWillUnmount(prevProps, prevState) { console.log('componentWillUnmount'); } // 修改状态 changeState() { this.setState({name: 'sxm'}); } } class ParentLifeCycle extends React.Component { constructor(props) { super(props); this.state = { value: 'World', destroyed: false, rerender: false }; this.handleChange = this.handleChange.bind(this); this.destroyComponent = this.destroyComponent.bind(this); this.handleRerender = this.handleRerender.bind(this); } handleChange() { this.setState((prevState, props) => ({ value: prevState.value + ' wlt' })); } handleRerender() { this.setState({rerender: true}); } destroyComponent() { this.setState({destroyed: true}); } render() { if(this.state.destroyed) return null; return ( <div className="parent-life-cycle-bg"> <p> <button onClick={this.handleChange}>改变LifeCyle的props</button> <button onClick={this.handleRerender}>父组件重新渲染,子组件re-render</button> <button onClick={this.destroyComponent}>删除组件</button> </p> <LifeCycle value={this.state.value}/> </div> ); } } ReactDOM.render( <ParentLifeCycle />, document.getElementById('root') ); </script>
例子
实现一个基本的定时器功能
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello World</title> <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <script crossorigin src="https://unpkg.com/[email protected]/babel.min.js"></script> </head> <body> <div id="root"></div> </body> </html> <script type="text/babel"> // 创建一个组件 -- 使用函数式组件 function Clock(){ // 创建元素 const element = ( <div> <h1>hello world!</h1> <h2>It is {new Date().toLocaleTimeString()}</h2> </div> ) // 使用render函数渲染已经创建的元素 ReactDOM.render( element, document.getElementById('root') ); } setInterval(Clock,1000) </script>
存在的问题
完成定时器的功能,使用组件化的形式进行封装的时候,在进行组件调用的时候应该能够直接通过 就能完成一个定时器,而不用再配合外部的js操作
这样做的话能够实现相关的功能,但是不利于复用
改写
使用组件的形式
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello World</title> <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <script crossorigin src="https://unpkg.com/[email protected]/babel.min.js"></script> </head> <body> <div id="root"></div> </body> </html> <!-- 在组件挂载和卸载的时候,会执行对应的生命周期钩子函数 --> <script type="text/babel"> // 使用类的形式对组件进行改写之后,我们可以向类中添加一些内容 class Clock extends React.Component{ constructor(props){ // 类组件应始终使用 props 调用基础构造函数。 super(props) // 使用状态进行改写 this.state = { date:new Date() } } // 组件挂载的生命周期钩子函数 componentDidMount(){ console.log("组件挂载了") this.timer = setInterval(()=>{ // 使用this.setState() 更新本地的状态 this.setState({ date:new Date() }) },1000) } // 组件卸载时会执行的生命周期钩子函数 componentWillUnmount(){ console.log("组件卸载了") clearInterval(this.timer) } render(){ return ( <div> <h1>hello world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}</h2> </div> ) } } // 使用render函数渲染已经创建的组件 ----- 三个组件互补影响 ReactDOM.render( <div> <Clock></Clock> <Clock></Clock> <Clock></Clock> </div>, document.getElementById('root') ); </script>