react类组件的更新

类组件状态的使用

  • 类组件的数据来源有两个地方:
    - 父组件传过来的属性。
    - 自己内部的状态。
  • 属性和状态发生变化后组件都会更新,视图都会渲染。
  • 在构造函数里初始化state。

定义一个clock组件,显示时分秒,并且每一秒更新一次时间:

clock

代码如下:

class Clock extends React.Component{
  constructor(props){
    super(props)
    this.state = {date:new Date()}
  }
  // 组件已经渲染到DOM后运行
  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>现在时间是:{this.state.date.toLocaleTimeString()}</h2>
      </div>
    )
  }
}

ReactDOM.render(
//  <FunctionCmp style={{color:'red'}} title="function-title" content="function-content"></FunctionCmp>,
  <Clock style={{color:'red'}}></Clock>,
  document.getElementById('root')
);

这个示例在react文档中也可以看到:https://reactjs.bootcss.com/docs/state-and-lifecycle.html

state的使用注意

  • 不要直接修改state,比如this.state.comment = 'hello'。这样不会重新渲染组件。
  • 修改state 应该使用 setState()
  • 构造函数是唯一可以给this.state 赋值的地方。
  • state的更新可能是异步的。因为this.propsthis.state可能会异步更新,所以不要依赖他们的值来更新下一个状态。
  • state的更新可能会被合并。

state 更新可能是异步

处于性能考虑,React可能会把多个setState()调用合并成一个调用。所以不要依赖他们的值来更新下一个状态。

来看一个例子:

class Counter extends React.Component{
  constructor(props){
    super(props)
    this.state = {number:0} 
  }
  // state = {number:0} // 也可以这种方式定义state

  handleClick = ()=>{
    this.setState({number:this.state.number + 1})
    console.log(this.state.number);
    this.setState({number:this.state.number + 1})
    console.log(this.state.number);
  }

  render(){
    return (
      <div>
        <p>{this.state.number}</p>
        <button onClick={this.handleClick}>+</button>
      </div>
    )
  }
}

定义一个 Counter 组件。Counter组件有一个点击事件,每次点击 number ++。按照正常思维,handleClick的输出应该是:1,2。那么事实真的如此吗?

在这里插入图片描述

打开控制台,可以看到 页面上的state 确实变了,但是并不是我们两次+1后的结果。控制台输出的两次还是最初的值,而且两个输出的值一样,并不是上一个 +1 之后的结果。

主要产生原因就是:

在事件处理函数中,setState 并不会修改this.state,等事件处理函数结束后再进行更新。

所以在handleClick中,每次 调用this.state.number + 1的时候拿到的this.state.number都是默认的0。

如果想要依赖上一个状态,可以使用函数的方式,函数的参数 就是上一次的返回的新的状态。

handleClick = ()=>{
    // this.setState({number:this.state.number + 1})
    // console.log(this.state.number);
    // this.setState({number:this.state.number + 1})
    // console.log(this.state.number);

    this.setState((state)=>({number:state.number+1}))
    console.log(this.state.number);
    this.setState((state)=>({number:state.number+1}))
    console.log(this.state.number);
  }

在这里插入图片描述
可以看到 界面的 数字就变成了连续两次+1的结果了。(至于控制台的就还是和上面说的原因一样,在函数处理中并不是修改this.state,所以两次拿到的是当前this.state.number的结果。)

但是我就是想要同步执行怎么去解决呢?

可以使用setTimeoutsetTimeout会开启一个宏任务,在下一次事件循环的时候执行。

handleClick = ()=>{
    this.setState({number:this.state.number + 1})
    console.log(this.state.number);  // 输出 0
    this.setState({number:this.state.number + 1})
    console.log(this.state.number);  // 输出 0

    setTimeout(() => {
      this.setState({number:this.state.number + 1})
      console.log(this.state.number);  // 输出 2  因为setTimeout 外面的执行完毕会后 this.state.number 已经变为1
      this.setState({number:this.state.number + 1})  
      console.log(this.state.number);  // 输出 3
    });
  }

在这里插入图片描述

如何判断setState是同步还是异步,是不是批量

1.原则 就是 凡是在React可以管控的地方,(比如上面的handleClick回调事件中,或者生命周期函数中等),他就是异步的,批量的。
2.在React不能管控到的地方(比如setTimeout中,是在宏任务队列中它管控不到,改成promise 也可以),就是同步的。非批量的。

简易的批量更新的大概原理

let isBatchUpdate = false
let queue = []
let state = {number:0}

function setState(newState){
  if(isBatchUpdate){
    queue.push(newState)
  }else{
    state = {...state,...newState}
  }
}


function handleClick(){
  // 模拟 react 在执行前 设置批量为true
  isBatchUpdate = true

  setState({number:1})
  console.log(state);
  setState({number:2})
  console.log(state);

  /**到这里我们自己处理业务逻辑结束,react开始批量更新state**/

  state = queue.reduce((newState,action)=>{return {...newState,...action}},state)
  isBatchUpdate = false
}

handleClick()

实现类组件的更新

首先在Component添加setState方法。Component自己并不管更新调度,需要借助Updater更新器来更新调度。它只负责更新组件。Component再添加一个updateComponent方法,用来更新组件。

class Updater{
  constructor(classInstance){
    this.classInstance = classInstance
    this.pendingStates = [] // 需要更新的状态  队列
    this.callbacks = []  // setState 的所有的回调
  }

  addState(partialState,callback){
    this.pendingStates.push(partialState)
    if(callback && typeof callback === 'function'){
      this.callbacks.push(callback)
    }
    this.emitUpdate()
  }

  // 触发更新
  emitUpdate(){

  }
}
class Component{
  static isReactComponent = true
  constructor(props){
    this.props = props
    this.state = {}
    // 每一个类组件的实例有一个updater更新器
    this.updater = new Updater(this)
  }
  
  setState(partialState,callback){
    this.updater.addState(partialState,callback)
  }
  forceUpdate(){
    
  }
}

export default Component

完整基本实现的代码地址:https://gitee.com/Nsir/my-react/tree/v1.0

实现合成事件 和 批量更新

前面我们已经知道,React 在事件处理函数中会将state 进行批量更新。所以我们在updateProps绑定事件的时候做一些处理:
在这里插入图片描述

Component.js中新加一个处理批量更新对象:

export const updateQueue = {
  isBatchingUpdate :false, // 控制是否批量更新state  默认false,在事件处理函数触发的时候设置为true ,使得state 更新进行批量异步更新
  updates:[],
  batchUpdate(){
    updateQueue.updates.forEach(updater=>updater.updateComponent())
    updateQueue.updates.length = 0 // 重置为0
    updateQueue.isBatchingUpdate = false
  }
}

修改之前的emitUpdate:

// 触发更新
  // 专门抽取这个方法 ,是因为 后面不管是state更新,还是props 更新都会触发更新,都会调用这个方法
  emitUpdate(){
    // 如果是批量更新,就先将当前的updater实例缓存到 updateQueue的updates中
    if(updateQueue.isBatchingUpdate){
      updateQueue.updates.push(this)
    }else{
      this.updateComponent()  // 触发组件更新
    }
  }

接下来实现合成事件。在react15是将事件委托到document上,react17是绑定到root容器上。这样当点击事件触发的时候,比如点击了click ,那么document也会触发click 事件,我们就可以在document的click事件中做自己的一些处理:

比如 在这里将isBatchingUpdate置为true,然后执行原来组件上触发的click 事件,然后再将isBatchingUpdate设为false,然后批量更新state。感觉就有点切面编程的思想。

新增一个 event.js文件:


import { updateQueue } from "./Component"

export function addEvent(dom,eventType,handler){
  let store = (dom.store = dom.store ? dom.store : {})  // store 存放着当前dom上对应的事件处理函数
  store[eventType] = handler  // 比如 store.onclick = handle
  // 进行委托
  if(!document[eventType]){
    // 事件委托到document上
    document[eventType] = dispatchEvent
  }
}


function dispatchEvent(event){
  // target 当前触发事件所点击的元素
  // type 事件类型 比如:click
  let {target,type} = event
  let eventType = `on${type}`

  updateQueue.isBatchingUpdate = true  // 切换为批量更新模式
  let syntheticEvent = createSyntheticEvent(event)

  // 模拟事件冒泡
  while (target) {
    let {store} = target
    let handler = store && store[eventType]
    handler && handler.call(target,syntheticEvent)
    target = target.parentNode
  }

  updateQueue.isBatchingUpdate = false
  updateQueue.batchUpdate()
}

// 基于老的事件对象创建合成事件对象
// react 源码中 在此方法中做了各个浏览器的兼容适配处理
function createSyntheticEvent(event){
  let syntheticEvent = {}
  for (const key in event) {
    if (Object.hasOwnProperty.call(event, key)) {
      syntheticEvent[key] = event[key]
    }
  }
  return syntheticEvent
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值