React ----状态(setState)

64 阅读2分钟

为何使用setState

  • React 中不能直接修改 state 的值来让界面发生更新,因为 React 并没有实现类似 Vue2 中的 Object.defineProperty,或是 Vue3 中的 Proxy 的方式进行数据劫持,监听数据变化

  • React 中必须通过调用 setState 来告知 React 数据已经发生了变化

  • setState 方法是从 Component 中继承过来的

setState的两种调用方式

值更新:直接传递一个新的状态对象

this.setState({count:1})

函数更新:传递一个回调函数,接收prevState和props作为参数

this.setState((prevState,props)=>{
  return { count:prevState.count + 1 }
})

setState的特性

异步更新

class Example extends React.Component {
  state = {
    count: 0
  };

  handleClick = () => {
    // 错误示范:直接读取更新后的状态
    this.setState({ count: this.state.count + 1 });
    console.log(this.state.count); // 不会立即显示更新后的值

    // 正确示范:在回调中读取更新后的状态
    this.setState({ count: this.state.count + 1 }, () => {
      console.log(this.state.count); // 会显示更新后的值
    });
  };
}

状态合并

class Counter extends React.Component {
   state = {
     user:{
       name:"test",
       age:23
     }
     
   };
   updateUser = () =>{
     // 浅合并,只更新指定的字段
     this.setState({ 
       user:{
         ...this.state.user,// 保留其他字段
         age:24
       }
     })
   }
}

批量更新

class Counter extends React.Component {
   state = {
     count:0
   };
   handleClick = () =>{
     // 这些setState会被批量处理,但是最终count的值只会加1
     this.setState({ count:this.state.count + 1})
     this.setState({ count:this.state.count + 1})
     this.setState({ count:this.state.count + 1})
     
     // 使用函数式更新可以累加,最终count的值为3
     this.setState(prevState => ({ count:this.state.count + 1}))
     this.setState(prevState => ({ count:this.state.count + 1}))
     this.setState(prevState => ({ count:this.state.count + 1}))
   }

}

setState源码解析 React V18.0.0

packages/react/src/ReactBaseClasses.js

Component.prototype.setState = function(partialState, callback) {
 this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

this.updater

this.updater 是在哪个地方进行赋值的我们暂时不用关心,只需要知道他被赋值为classComponentUpdater

classComponentUpdater

packages/react-reconciler/src/ReactFiberClassComponent.js 我们只需关心生成了update,插入到update队列,enqueueUpdate(fiber, update,lane);然后调用scheduleWork

// 伪代码
const classComponentUpdater = {
  ...
  enqueueSetState(inst, payload, callback) {
    const update = createUpdate(expirationTime);
    // setState(payload, callback);
    update.payload = payload;
    update.callback = callback;
    
    // 插入到update队列
    enqueueUpdate(fiber, update,lane);
    
    scheduleWork(fiber, expirationTime);
  },
  ...

scheduleWork

packages/react-reconciler/src/ReactFiberScheduler.js

// isWorking、isCommitting是全部变量,在后面我们会具体分析到
if (
    !isWorking ||
    isCommitting ||
    nextRoot !== root
  ) {
    const rootExpirationTime = root.expirationTime;
    requestWork(root, rootExpirationTime);
  }

requestWork

setState调用原理

image.png

React 18之后,默认所有的setState都是异步处理,实现批量更新

实现同步效果,调用react-dom中的flushSync的API,同步效果