React: States is tricky
类似于 Android 的生命周期调节参数,此外 state 必须在定义它的那个 class 里面使用。
另外可以将 state 传到子元素,不过不能传给其他同级元素或者父元素
因此只可能出现 Down Flow 不可能向上传递。
另外 stateful 的 Component 和 stateless 的 Component 完全可以随意交叉使用,反正数据都可以相互传递
<h2>It is {this.state.date.toLocaleTimeString()}.</h2> <FormattedDate date={this.state.date} />
对于一个 Component:
- 初始化的时候的操作叫做 Mounting
- 销毁的操作叫做 unMounting
然后可以在这两个事件进行监听
- **componentDidMount() 事件 ** 会在 Component 渲染成功时执行
- **componentWillUnmount() 事件 ** 会在 Component 销毁的时候执行
因此对于一个定时器来说,应该在 **componentDidMount() 事件 ** 里面注册 Timer 并且在 **componentWillUnmount() 事件 ** 里面关闭 Timer
然后在 Timer 里面必须更新 **state 数据 ,因为我们会将 state 数据 ** 输出出来, 更新 state 需要执行 setState(),将参数传进去就行
简单易懂
class Clock extends React.Component { constructor(props) { super(props); this.state = { date: new Date() }; } componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render( <Clock />, document.getElementById('root') );
几个关于 setState()
的要点:
// Wrong this.state.comment = 'Hello';
// Correct this.setState({ comment: 'Hello' });
The only place where you can assign this.state
is the constructor.
因此不能直接使用 state 来 overwite state
React may batch multiple setState()
calls into a single update for performance.
Because this.props
and this.state
may be updated asynchronously, you should not rely on their values for calculating the next state.
For example, this code may fail to update the counter:
// Wrong this.setState({ counter: this.state.counter + this.props.increment, });
To fix it, use a second form of setState()
that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument:
写一个 callback 即可解决问题
// Correct this.setState((prevState, props) => ({ counter: prevState.counter + props.increment }));
When you call setState()
, React merges the object you provide into the current state.
使用
setState()
的时候,只要没有提供 value 的 state 都不会改变
For example, your state may contain several independent variables:
constructor(props) { super(props); this.state = { posts: [], comments: [] }; }
Then you can update them independently with separate setState()
calls:
componentDidMount() { fetchPosts().then(response => { this.setState({ posts: response.posts }); }); fetchComments().then(response => { this.setState({ comments: response.comments }); }); }
The merging is shallow, so this.setState({comments})
leaves this.state.posts
intact, but completely replaces this.state.comments
.
有几个需要注意的点,给一个 Component 设置 State 的时候可能这样:
handleClickOnLikeButton() { console.log(this.state.isLiked) this.setState({ isLiked: !this.state.isLiked }) console.log(this.state.isLiked) }
这种情况下会输出两次修改前的值,因为在请求修改之后,会有一段延时时间,修改命令会加入到队列但是没有办法保证立刻修改。
同理,以下这种修改是不合理的:
handleClickOnLikeButton() { this.setState({ count: 0 }) // => this.state.count 还是 undefined this.setState({ count: this.state.count + 1 }) // => undefined + 1 = NaN this.setState({ count: this.state.count + 2 }) // => NaN + 2 = NaN }
不过 setState()
方法提供了替代方案,接收一个函数,并返回函数结果:
handleClickOnLikeButton() { this.setState((prevState) => { return { count: 0 } }) this.setState((prevState) => { return { count: prevState.count + 1 } // 上一个 setState 的返回是 count 为 0,当前返回 1 }) this.setState((prevState) => { return { count: prevState.count + 2 } // 上一个 setState 的返回是 count 为 1,当前返回 3 }) // 最后的结果是 this.state.count 为 3 }
这样就可以利用上一次运算结果继续运算。
另外,虽然上面我们执行了三次,但实际上组件只会重新渲染一次
作者: Michel Weststrate
这篇文章原标题是 3 Reasons why I stopped using React.setState,但是我对原文作者提出的论点不是很感冒,但是作者提出的三点对 React
新手来说是很容易忽略的地方,所以我在这里只提出部分内容,而且把标题改为 ** 使用 React.setState 需要注意的三点 **。
对 React
新手来说,使用 setState
是一件很复杂的事情。即使是熟练的 React
开发,也很有可能因为 React
的一些机制而产生一些 bug,比如下面这个例子:
文档 中也说明了当使用 setState
的时候,需要注意什么问题:
** 注意:** 绝对不要 直接改变
this.state
,因为之后调用setState()
可能会替换掉你做的改变。把this.state
当做是不可变的。
setState()
不会立刻改变this.state
,而是创建一个即将处理的state
转变。在调用该方法之后访问this.state
可能会返回现有的值。对
setState
的调用没有任何同步性的保证,并且调用可能会为了性能收益批量执行。
setState()
将总是触发一次重绘,除非在shouldComponentUpdate()
中实现了条件渲染逻辑。如果可变对象被使用了,但又不能在shouldComponentUpdate()
中实现这种逻辑,仅在新state
和之前的state
存在差异的时候调用setState()
可以避免不必要的重新渲染。
总结出来,当使用 setState
的时候,有三个问题需要注意:
很多开发刚开始没有注意到 setState
是异步的。如果你修改一些 state
,然后直接查看它,你会看到之前的 state
。这是 setState
中最容易出错的地方。 setState
这个词看起来并不像是异步的,所以如果你不假思索的用它,可能会造成 bugs
。下面这个例子很好的展示了这个问题:
class Select extends React.Component { constructor(props, context) { super(props, context) this.state = { selection: props.values[0] }; } render() { return ( <ul onKeyDown={this.onKeyDown} tabIndex={0}> {this.props.values.map(value => <li className={value === this.state.selection ? 'selected' : ''} key={value} onClick={() => this.onSelect(value)} > {value} </li> )} </ul> ) } onSelect(value) { this.setState({ selection: value }) this.fireOnSelect() } onKeyDown = (e) => { const {values} = this.props const idx = values.indexOf(this.state.selection) if (e.keyCode === 38 && idx> 0) { /* up */ this.setState({ selection: values[idx - 1] }) } else if (e.keyCode === 40 && idx < values.length -1) { /* down */ this.setState({ selection: values[idx + 1] }) } this.fireOnSelect() } fireOnSelect() { if (typeof this.props.onSelect === "function") this.props.onSelect(this.state.selection) /* not what you expected……*/ } } ReactDOM.render( <Select values={["State.", "Should.", "Be.", "Synchronous."]} onSelect={value => console.log(value)} />, document.getElementById("app") )
第一眼看上去,这个代码似乎没有什么问题。两个事件处理中调用 onSelect
方法。但是,这个 Select
组件中有一个 bug
很好的展现了之前的 GIF
图。 onSelect
方法永远传递的是之前的 state.selection
值,因为当 fireOnSelect
调用的时候, setState
还没有完成它的工作。我认为 React
至少要把 setState
改名为 scheduleState
或者把回掉函数设为必须参数。
这个 bug 很容易修改,最难的地方在于你要知道有这个问题。
setState
造成的第二个问题是:每次调用都会造成重新渲染。很多时候,这些重新渲染是不必要的。你可以用 React performance tools
中的 printWasted 来查看什么时候会发生不必要渲染。但是,大概的说,不必要的渲染有以下几个原因:
-
新的
state
其实和之前的是一样的。这个问题通常可以通过shouldComponentUpdate
来解决。也可以用pure render
或者其他的库来解决这个问题。 -
通常发生改变的
state
是和渲染有关的,但是也有例外。比如,有些数据是根据某些状态来显示的。 -
第三,有些
state
和渲染一点关系都没有。有一些state
可能是和事件、timer ID
有关的。
基于上面的最后一条,并不是所有的组件状态都应该用 setState
来进行保存和更新的。复杂的组件可能会有各种各样的状态需要管理。用 setState
来管理这些状态不但会造成很多不需要的重新渲染,也会造成相关的生命周期钩子一直被调用,从而造成很多奇怪的问题。
在原文中作者推荐了一个叫做 MobX
的库来管理部分状态,我不是很感冒,所以我就不介绍。如果感兴趣的,可以通过最上面的链接看看原文中的介绍。
基于上面提出的三点,我认为新手应该注意的地方是:
setState
是不保证同步的,是不保证同步的,是不保证同步的。重要的事情说三遍。之所以不说它是异步的,是因为 setState
在某些情况下也是同步更新的。可以参考这篇文章
如果需要在 setState
后直接获取修改后的值,那么有几个方案:
针对于之前的例子,完全可以在调用 fireOnSelect
的时候,传入需要的值。而不是在方法中在通过 this.state
来获取
setState
方法接收一个 function
作为回调函数。这个回掉函数会在 setState
完成以后直接调用,这样就可以获取最新的 state
。对于之前的例子,就可以这样:
this.setState({ selection: value }, this.fireOnSelect)
在 setState
使用 setTimeout
来让 setState
先完成以后再执行里面内容。这样子:
this.setState({ selection: value }); setTimeout(this.fireOnSelect, 0);
直接输出,回调函数, setTimeout
对比
componentDidMount(){ this.setState({val: this.state.val + 1}, ()=>{ console.log("In callback" + this.state.val); }); console.log("Direct call" + this.state.val); setTimeout(()=>{ console.log("begin of setTimeout" + this.state.val); this.setState({val: this.state.val + 1}, ()=>{ console.log("setTimeout setState callback" + this.state.val); }); setTimeout(()=>{ console.log("setTimeout of settimeout" + this.state.val); }, 0); console.log("end of setTimeout" + this.state.val); }, 0); }
如果 val 默认为 0, 输入的结果是:
> Direct call 0 > In callback 1 > begin of setTimeout 1 > setTimeout setState callback 2 > end of setTimeout 2 > setTimeout of settimeout 2
通常 state
中只来管理和渲染有关的状态,从而保证 setState
改变的状态都是和渲染有关的状态。这样子就可以避免不必要的重复渲染。其他和渲染无关的状态,可以直接以属性的形式保存在组件中,在需要的时候调用和改变,不会造成渲染。或者参考原文中的 MobX
。
避免不必要的修改,当 state
的值没有发生改变的时候,尽量不要使用 setState
。虽然 shouldComponentUpdate
和 PureComponent
可以避免不必要的重复渲染,但是还是增加了一层 shallowEqual
的调用,造成多余的浪费。
以上
关于本文
文章标题 | React: States is tricky |
发布日期 | 2019-01-17 |
文章分类 | Tech |
相关标签 | #React |
留言板
PLACE_HOLDER
PLACE_HOLDER
PLACE_HOLDER
PLACE_HOLDER