React state状态属性用法讲解

目录
  • 类组件中的state
    • setState的用法
    • 类组件如何限制state更新视图
    • setState原理揭秘
  • 函数组件中的state
    • useState的用法
    • 如何监听state的变化
    • setState(dispatch)更新特点
    • useState的注意事项
  • 总结
    • 相同点
    • 不同点

类组件中的state

setState的用法

React项目中UI改变来源于state的改变,类组件中setState是更新组件,渲染视图的主要方式

基本用法

setState(obj,callback)
  • 第一个参数:当obj是一个对象,即为即将合并的state;如果obj是一个函数,那么当组件的state和props将作为参数,返回值用于合并新的state
  • 第二个参数callback:callback为一个函数,函数执行上下文中可以获取当前setState更新后的最新的值,可以作为依赖state变化的副作用函数,可以用来做一些基本的DOM操作
/* 第一个参数为function类型 */
this.setState((state,props)=>{
    return {number:}
})
/* 第一个参数为object类型 */
this.setState({ number:1 },()=>{
    console.log(this.state.number) //获取最新的number
})

加入一次事件中触发一次如上的setState,在React底层主要做了哪些事呢?

  • 首先,setState会产生当前更新的优先级------产生更新优先级
  • 接下来React会从fiber Root根部fiber向下调和子节点,调和阶段将对比发生更新的彼方,更新对比expirationTime,找到发生更新的组件,合并state,然后触发render函数,得到最新的UI视图,完成render阶段--------对比
  • 接下来到commit阶段,commit阶段,替换真实DOM,完成此次更新流程。--------替换真实DOM
  • 此时仍然在commit阶段,会执行setState中callback函数,如上的()=>{console.log(this.state.number)} ,到此为止就完成了一次setState的过程。

更新的流层图如下:

要记住一个主要任务的先后顺序,这对于弄清渲染过程会有帮助:

render阶段render函数执行--->commit阶段真实DOM替换--->setState回调函数执行callback

类组件如何限制state更新视图

对于类组件如何显示state带来的更新作用呢?

  • pureComponet可以对state和props进行浅比较,如果没有发生变化,那么组件就不会更新
  • shouldComponentUpdate生命周期可以通过判断前后state变化来决定组件需不需要更新,需要更新返回true,否则返回false

setState原理揭秘

知其然,知其所以然,下面将介绍setState的底层逻辑,要弄清楚state的更新机制,所以接下来要从两个方向分析

  • 一是揭秘enqueueSetState到底做了什么?
  • 二是React底层是如何进行批量更新的?

首先,这里极简了一下enqueueSetState的代码,如下:

enqueueSetState(){
    //每次调用setState,react都会创建一个update里面保存了如下
    const update= createUpdate(expirationTime,suspenseConfig)
    //callback 可以理解为setState回调函数,第二个参数
    callback && (update.callback=callback)
    //enqueuUpdate 把当前的update 传入当前fier ,待更新队列中
    enqueuUpdate(fiber,update)
    //开始调度更新
    scheduleUpdateOnFiber(fiber,expirationTime)
}

enqueueSetState作用实际很简单,就是创建一个update,然后放入当前的fiber对象的待更新队列中,最后开启调度更新,进入上述讲到的更新流程。

那么问题来了,React的batchUpdate批量更新是什么时候加上去的呢?

这就要提前聊到事件系统了,正常的state更新,UI交互,都离不开用户的事件,比如点击事件,表单输入等,React是采用事件合成的形式,每一个事件都是由React事件系统统一调度的,那么State批量更新正是和事件系统息息相关的。

//在legcy模式下,所有的事件都将经过此函数统一处理
function dispatchEventFoLegacyPluginEventSystem(){
    //handleTopLevel 事件处理函数
    batchEventUpdates(handleTopLvele,bookKeeping)
}

batchEventUpdates方法具体如下:

batchEventUpdate(fn,a){
    //开启批量更新
    isBatchingEventUpdates=true;
    try{
        //这里执行了事处理函数,比如在一次点击事件中触发setState,那么它将在这个函数执行
        return batchEventUpdateImpl(fn,a,b);
    }finally{
        //try里面的return 不会影响finally执行
        //完成一次事件,批量更新
        isBatchingEventUpdates=false
    }
}

如上分析出流程图,在React事件执行之前通过isBatchEventUpdates=true打开开关开启事件批量更新,当该事件结束,再通过isBactchEventUpdates=false;关闭开关,然后在scheduleUpdateOnFiber中根据开关来确定是否进行批量更新

举个例子,如下组件中这么写:

import React, { Component } from 'react';
export default class Test extends Component {
  state={number:0}
  handleClick=()=>{
    this.setState({number:this.state.number+1},()=>{
      console.log('callback1',this.state.number)
    })
    console.log(this.state.number)
    this.setState({number:this.state.number+1},()=>{
      console.log('callback2',this.state.number)
    })
    console.log(this.state.number)
    this.setState({number:this.state.number+1},()=>{
      console.log('callback3',this.state.number)
    })
    console.log(this.state.number)
  }
  render() {
    return (
      <div>
        {this.state.number}
        <button onClick={this.handleClick}>number++</button>
      </div>
    );
  }
}

点击打印结果:0,0,0 callback1 1 ,callback2 1,callback3 1

如上代码,在整个React上下文执行栈中会变成这样:

那么,为什么异步操作里面的批量更新规则会被打破呢?比如用promise或者setTime在handleClick中这么写:

handleClick=()=>{
  setTimeout(()=>{
    this.setState({number:this.state.number+1},()=>{
      console.log('callback1',this.state.number)
    })
    console.log(this.state.number)
    this.setState({number:this.state.number+1},()=>{
      console.log('callback2',this.state.number)
    })
    console.log(this.state.number)
    this.setState({number:this.state.number+1},()=>{
      console.log('callback3',this.state.number)
    })
    console.log(this.state.number)
  })
}

打印:callback1 1,1,callback2 2,2,callback3 3 ,3

那么整个React上下文执行栈就会变成如图这样

所以批量更新规则被打破。

那么,如果在如上异步环境下,继续开启批量更新模式呢?

React-Dom提供了批量更新方法unstable_batchChedUpdates,可以去手动批量更新,可以将上述setTimeout里面的内容作如下修改:

import ReactDom from 'react-dom';
const {unstable_batchedUpdates}=ReactDom
  setTimeout(()=>{
    unstable_batchedUpdates(()=>{
      this.setState({number:this.state.number+1},()=>{
        console.log('callback1',this.state.number)
      })
      console.log(this.state.number)
      this.setState({number:this.state.number+1},()=>{
        console.log('callback2',this.state.number)
      })
      console.log(this.state.number)
      this.setState({number:this.state.number+1},()=>{
        console.log('callback3',this.state.number)
      })
      console.log(this.state.number)
    })
  })

点击打印结果:0,0,0 callback1 1 ,callback2 1,callback3 1

在实际工作中,unstable_batchChedUpdates可以用于Ajax数据交互之后,合并多次setState,或者是多次useState。原因很简单,所有的数据交互都是在异步环境下,如果没有批量更新处理,一次数据交互多次改变state会促使视图多次渲染。

那么如何提升更新优先级呢?

React-dom提供了flushSync,flushSync可以将回调函数中的更新任务,放在一个比较高的优先级中。React设定了很多不同优先级的更新任务,如果一次更新任务在flushSync回调内部,那么将获得一个比较高优先级的更新。

函数组件中的state

React-hooks发布之后,useState可以使函数组件像类组件一样拥有state,也就是说名函数组件可以通过useState来改变UI视图。那么useState到底应该如何使用,底层优势怎么运行的呢?

useState的用法

const [state,setState] = useState(initData)
  • state,目的是提供给UI,作为渲染视图的数据源
  • setState改变state的函数,可以理解为推动函数组件渲染的渲染函数
  • initData有两种情况,第一种情况是非函数,将作为state的初始化的值,第二种情况是函数,函数返回值作为作为useState初始化的值

initData为函数的情况:

const [number,setNumber]= React.useState(0)

initData为函数的情况:

  const [number,setNumber]=React.useState(()=>{
    //props中的a=1 state为0-1随机数
    //props中a=2 state为1-10的随机数
    //否则 state为1-100的随机数
    if(props.a===1) return Math.random()
    if(props.a===2) return Math.ceil(Math.random()*10)
    return Math.ceil(Math.random()*100)
  })

对于setState参数,也有两种情况:

  • 第一种是非函数情况,此时将作为新的值,赋予state,作为下一次渲染使用;
  • 第二种是函数的情况,如果setState的参数是一个函数,这里可以称它为reducer,reducer参数,是上一次返回最新的state,返回值作为新的state

setState参数是一个非函数的情况:

  const [number,setNumber]= React.useState(0)
  const handleClick=()=>{
    setNumber(1)
    setNumber(2)
    setNumber(3)
  }

setState参数是一个函数的情况:

  const [number,setNumber]= React.useState(0)
  const handleClick=()=>{
    setNumber((state)=>{
      return state+1//0+1=1
    })
    setNumber(8)//8
    setNumber((state)=>{
      return state+1//8+1=9
    })
  }

如何监听state的变化

类组件中的setState中,有第二个参数callback或是生命周期函数componentDidUpdate可以检测监听到state改变或是组件更新。

那么在函数组件中,如何监听state变化呢?这个时候就需要useEffect出场了,通常可以把state作为依赖项传入useEffect第二个参数deps,但是注意useEffect初始化是会默认执行一遍。

import React,{useEffect, useState} from 'react'
import ReactDom from 'react-dom'
export default function Test() {
  const [number,setNumber]= React.useState(0)
  const handleClick=()=>{
    ReactDom.flushSync(()=>{
      setNumber(2)
    })
    setNumber(1)
    setTimeout(()=>{
      setNumber(3)
    })
  }
  useEffect(()=>{
    console.log('变化',number)
  },[number])
  console.log(number)
  return (
    <button onClick={handleClick}>text1</button>
  )
}

执行结果:

setState(dispatch)更新特点

上述讲到的批量更新和flushSync,在函数组件中,dispatch更新效果和类组件是一样的,但是useState有一点值得注意,就是当帝爱用改变state的函数dispatch,在本次函数执行上下文中,是获取不到state的值的,举例如下:

  const [number,setNumber]= React.useState(0)
  const handleClick=()=>{
    ReactDom.flushSync(()=>{
      setNumber(2)
      console.log(number)
    })
    setNumber(1)
    console.log(number)
    setNumber(()=>{
      setNumber(3)
      console.log(number)
    })
  }

结果:0 0 0

原因很简单,函数组件更新就是函数的执行,在函数一次执行过程中,函数内部所有变量重新生命,所以改变的state,只有在下一次函数组件执行时才会更新,所以在如同上一个函数执行上下文中,number一直为0,无论怎么打印,都拿不到最新的state。

useState的注意事项

在使用useState的dispatchAction更新state的时候,记得不要传入相同的state,这样会使视图不更新,比如下面:

  const [state,dispatchState]= React.useState({
    name:'aline'
  })
  const handleClick=()=>{
    state.name='aline'
    dispatchState(state)//直接改变state,在内存中执行的地址没有变
  }

上述例子为什么没有更新呢?是因为在useState的dispatchAction处理逻辑中,会浅比较state两次,发现state相同,不会开启更新调度任务。其中demo中两次state指向了相同的内存空间,所以默认为state相等,就不会发生视图更新了

解决问题:把上述的dispatchState改成dispatch({...state})根本解决了问题,浅拷贝了对象,重新开启了内存空间。

总结

类组件中的setState和函数组件中的useState有什么异同?

相同点

首先从原理角度出发,setState和useState更新视图,底层都调用了scheduleUpdateOnFiber方法,而且时间驱动情况下都有批量更新规则

不同点

  • 再不是pureComponent组件模式下,setState不会浅比较两次的state的值,只有调用setState,在没有其他优化手段的前提下,会执行更新,但是useState中的dispatchAction会默认比较两次state是否相同,然后决定是否更新组件
  • setState有专门监听state变化的回调函数callback,着这个回调函数中可以获取到最新的值,而在函数组件中,只能通过useEffect来执行state变化引起副作用。
  • setState在顶层处理state的逻辑主要是和旧state进行合并操作,而useState则是替换,及重新赋值

到此这篇关于React state状态属性用法讲解的文章就介绍到这了,更多相关React state状态内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 解决react中useState状态异步更新的问题

    目录 疑惑 状态异步更新带来的问题 问题示例 问题解决 类组件的解决方案 函数组件的解决方案 其他解决方案 结尾 疑惑 相信刚开始使用react函数组件的小伙伴也遇到过一个坑,就是 useState 更新状态是异步更新的,但是react 并没有提供关于这个问题的解决方案.那我们能否使用自己的方法来解决这个问题呢?答案肯定是可以的. 状态异步更新带来的问题 就拿一个比较常见的场景来说.在react项目中,我们想在关闭对话框后再去处理其他业务.但是 useState 的状态是异步更新的.我们通过se

  • React事件监听和State状态修改方式

    目录 React事件监听和State状态修改 React事件监听相关例子 React事件监听和State状态修改 on*函数可直接使用,但只能使用在html标签上,自己创建的组件标签不可 组件可以增加state状态,修改用setState方法,修改会有缓存,只能保证在渲染到页面之前会改好.所以setState后立马去使用值可能是不对的 狗子组件1:点击会打印bark和run class Dog extends Component {   bark () {     console.log('ba

  • 浅谈React之状态(State)

    在React当中,当你更新组件的state,然后新的state就会重新渲染到页面中.在这个时候不需要你操作任何DOM.你也可以认为组件在React当中是一个状态机(State Machines).当用户进行操作时会实现不同的状态,然后再渲染到你的页面中,让你的页面与数据始终保持一致. 如何定义State 定义一个合适的State,是正确创建组件的第一步.State必须能代表一个组件UI呈现的完整状态集,即组件的任何UI改变,都可以从State的变化中反映出来:同时,State还必须是代表一个组件

  • React State状态与生命周期的实现方法

    一.实现组件的方法: 组件名称首字母必须大写 1.通过JS函数方式实现组件 <div id="app"></div> <script type="text/babel"> var ReactDiv = document.getElementById('app'); function GetReactComp(){ return <p>我是react组件</p> } const hellocomp = <

  • React在组件中如何监听redux中state状态的改变

    目录 在组件中监听redux中state状态的改变 解决方式 React和redux的状态处理 在组件中监听redux中state状态的改变 解决方式 1.在组件中引入store 2.在constructor构造器方法中,重写store.subscribe方法(该方法即是监听state状态改变的放过) 组件完整代码如下: import React, { Component } from 'react' import CSSModules from 'react-css-modules'  imp

  • React state状态属性详细讲解

    目录 1. 基本使用 2. 使用setState操作state数据 3. 案例-toDoList 4. 案例-购物车 1. 基本使用 要点: 成员属性 state 它是一个特殊的属性,它是当前类的私有数据,只有在当前的组件中才能操作里面的数据 状态( state )即数据,是组件内部的私有数据,只能在组件内部使用,和vue中data差不多,不过它没有像vue中的data进行了数据劫持 state的值是对象,表示一个组件中可以有多个数据 通过this.state来获取状态,react 中没有做数据

  • React 无状态组件(Stateless Component) 与高阶组件

    无状态组件(Stateless Component) 是 React 0.14 之后推出的,大大增强了编写 React 组件的方便性,也提升了整体的渲染性能. 无状态组件 (Stateless Component) function HelloComponent(props, /* context */) { return <div>Hello {props.name}</div> } ReactDOM.render(<HelloComponent name="Se

  • React state状态属性用法讲解

    目录 类组件中的state setState的用法 类组件如何限制state更新视图 setState原理揭秘 函数组件中的state useState的用法 如何监听state的变化 setState(dispatch)更新特点 useState的注意事项 总结 相同点 不同点 类组件中的state setState的用法 React项目中UI改变来源于state的改变,类组件中setState是更新组件,渲染视图的主要方式 基本用法 setState(obj,callback) 第一个参数:

  • spring bean标签的primary属性用法讲解

    目录 bean标签的primary属性 @Primary 在spring中常被忽视的注解 下面是个简单的使用例子 用@Qualifier这个注解来解决问题 扩展:Spring注解常用汇总 bean标签的primary属性 primary这个翻译过来是 首要的,首选的意思. primary的值有true和false两个可以选择.默认为false. 当一个bean的primary设置为true,然后容器中有多个与该bean相同类型的其他bean, 此时,当使用@Autowired想要注入一个这个类型

  • React组件三大属性之state,props,refs

    目录 1.1基本理解和使用 1.1.1 使用React开发者工具调试 1.1.2 定义组件的方式 1.1.3 注意 1.1.4 渲染类组件标签的基本流程 1.2 组件实例的三大核心属性之一:state 1.2.1 理解 1.2.2 案例 1.2.3 在类式组件使用state 1.2.4 在类式组件使用state的简写方式 1.2.3 强烈注意 1.3 组件实例的三大核心属性之一:props 1.3.1 理解 1.3.3 作用 1.3.4 在类式组件使用props 1.3.5 在函数式组件使用pr

  • 40行代码把Vue3的响应式集成进React做状态管理

    前言 vue-next是Vue3的源码仓库,Vue3采用lerna做package的划分,而响应式能力@vue/reactivity被划分到了单独的一个package中. 如果我们想把它集成到React中,可行吗?来试一试吧. 使用示例 话不多说,先看看怎么用的解解馋吧. // store.ts import { reactive, computed, effect } from '@vue/reactivity'; export const state = reactive({ count:

  • React各种状态管理器的解读及使用方法

    首先我们要先知道什么是状态管理器,这玩意是干啥的? 当我们在多个页面中使用到了相同的属性时就可以用到状态管理器,将这些状态存到外部的一个单独的文件中,不管在什么时候想使用都可以很方便的获取. react和vue有些不同,react没有自己专属的状态管理方式.它使用的其实是js相关的状态管理器.我们需要记住的是,视图可以引起状态的改变,而状态的改变会导致视图的二次渲染. 说了这么半天,那么我们在react中到底是怎样使用状态管理器的呢? redux闪亮登场 redux的前身技术是flux,他和fl

  • React Hooks核心原理深入分析讲解

    目录 Hooks 闭包 开始动手实现 将useState应用到组件中 过期闭包 模块模式 实现useEffect 支持多个Hooks Custom Hooks 重新理解Hooks规则 React Hooks已经推出一段时间,大家应该比较熟悉,或者多多少少在项目中用过.写这篇文章简单分析一下Hooks的原理,并带大家实现一个简易版的Hooks. 这篇写的比较细,相关的知识点都会解释,给大家刷新一下记忆. Hooks Hooks是React 16.8推出的新功能.以这种更简单的方式进行逻辑复用.之前

随机推荐