zoukankan      html  css  js  c++  java
  • react的setState使用中遇到的问题

    setState()更新的数据和自己预期的不一致

    对 React 新手来说,使用 setState 是一件很复杂的事情。即使是熟练的 React 开发,也很有可能因为 React 的一些机制而产生一些bug,react文档 中也说明了当使用 setState 的时候,需要注意什么问题:

    • 绝对不要 直接改变 this.state ,因为之后调用 setState() 可能会替换掉你做的改变。把 this.state 当做是不可变的。
    • setState() 不会立刻改变 this.state ,而是创建一个即将处理的 state 转变。在调用该方法之后访问 this.state 可能会返回现有的值。
    • setState 的调用没有任何同步性的保证,并且调用可能会为了性能收益批量执行。

    setState() 将总是触发一次重绘,除非在 shouldComponentUpdate() 中实现了条件渲染逻辑。如果可变对象被使用了,但又不能在 shouldComponentUpdate() 中实现这种逻辑,仅在新 state 和之前的 state 存在差异的时候调用 setState() 可以避免不必要的重新渲染。

    总结出来,当使用 setState 的时候,有三个问题需要注意:

    1. 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 。 onSelect 方法永远传递的是之前的 state.selection 值,因为当 fireOnSelect 调用的时候, setState 还没有完成它的工作。我认为 React 至少要把 setState 改名为 scheduleState 或者把回掉函数设为必须参数。

    2. setState会造成不必要的渲染

    setState 造成的第二个问题是:每次调用都会造成重新渲染。很多时候,这些重新渲染是不必要的。你可以用 React performance tools 中的 printWasted 来查看什么时候会发生不必要渲染。但是,大概的说,不必要的渲染有以下几个原因:

    • 新的 state 其实和之前的是一样的。这个问题通常可以通过 shouldComponentUpdate 来解决。也可以用 pure render 或者其他的库赖解决这个问题。
    • 通常发生改变的 state 是和渲染有关的,但是也有例外。比如,有些数据是根据某些状态来显示的。
    • 第三,有些 state 和渲染一点关系都没有。有一些 state 可能是和事件、 timer ID 有关的。

    3.setState并不能很有效的管理所有的组件状态

    基于上面的最后一条,并不是所有的组件状态都应该用 setState 来进行保存和更新的。复杂的组件可能会有各种各样的状态需要管理。用 setState 来管理这些状态不但会造成很多不需要的重新渲染,也会造成相关的生命周期钩子一直被调用,从而造成很多奇怪的问题。

    总结

    基于上面提出的三点,我认为新手应该注意的地方是:

    setState 是不保证同步的

    setState 是不保证同步的,是不保证同步的,是不保证同步的。重要的事情说三遍。之所以不说它是异步的,是因为 setState 在某些情况下也是同步更新的。 可以参考这篇文章

    如果需要在 setState 后直接获取修改后的值,那么有几个方案:

    1.传入对应的参数,不通过 this.state 获取

    针对于之前的例子,完全可以在调用 fireOnSelect 的时候,传入需要的值。而不是在方法中在通过 this.state 来获取

    2.使用回调函数

    setState 方法接收一个 function 作为回调函数。这个回掉函数会在 setState 完成以后直接调用,这样就可以获取最新的 state 。对于之前的例子,就可以这样:

    this.setState({
      selection: value
    }, this.fireOnSelect)
    

    3.使用setTimeout
    在 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
    

    4.和渲染无关的状态尽量不要放在 state 中来管理

    通常 state 中只来管理和渲染有关的状态 ,从而保证 setState 改变的状态都是和渲染有关的状态。这样子就可以避免不必要的重复渲染。其他和渲染无关的状态,可以直接以属性的形式保存在组件中,在需要的时候调用和改变,不会造成渲染。

    避免不必要的修改,当 state 的值没有发生改变的时候,尽量不要使用 setState 。虽然 shouldComponentUpdatePureComponent 可以避免不必要的重复渲染,但是还是增加了一层 shallowEqual 的调用,造成多余的浪费。

    参考:https://www.tuicool.com/articles/zEfEfua

  • 相关阅读:
    C语言I博客作业06
    C语言I博客作业05
    C语言I博客作业04
    C语言I博客作业02
    C语言II博客作业04
    C语言II博客作业03
    C语言II博客作业02
    C语言II博客作业01
    学期总结
    C语言I博客作业08
  • 原文地址:https://www.cnblogs.com/feiyu6/p/9202873.html
Copyright © 2011-2022 走看看