• React useState 进阶实践指南


    useState 用法详解

    React-hooks 正式发布以后, useState 可以使函数组件像类组件一样拥有 state,也就说明函数组件可以通过 useState 改变 UI 视图。那么 useState 到底应该如何使用,底层又是怎么运作的呢,首先一起看一下 useState

    基本用法

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

    initData 为非函数的情况

    /* 此时将把 0 作为初使值 */
    const [ num , setNum ] = useState(0)
    
    • 1
    • 2

    initData 为函数的情况

    每当 React 重新渲染组件时,都会执行useState(initData)。 如果初始状态是原始值(数字,布尔值等),则不会有性能问题。
    当初始状态需要昂贵的性能方面的操作时,可以通过为useState(computeInitialState)提供一个函数来使用状态的延迟初始化,如下所示:

    function MyComponent({ bigJsonData }) {
      const [value, setValue] = useState(function getInitialData() {
        const object = JSON.parse(bigJsonData); 
        return object.initialValue;
      });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    getInitialData()仅在初始渲染时执行一次,以获得初始状态。在以后的组件渲染中,不会再调用getInitialData(),从而跳过昂贵的操作。

    state 变化监听

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

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

    具体可以参考如下 Demo :

    import { useState, useEffect } from "react";
    export default function App() {
      const [num, setNum] = useState(0);
      /* 监听 num 变化 */
      useEffect(() => {
        console.log("监听num变化,此时的num是:  " + num);
      }, [num]);
    
      const handerClick = () => {
        setNum(1);
        setTimeout(() => {
          setNum(3);
        });
      };
    
      console.log(num);
    
      return (
        <div>
          <span> {num}</span>
          <button onClick={handerClick}>num++</button>
        </div>
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    点击按钮后输出:

    0
    监听num变化,此时的num是:  0
    1
    监听num变化,此时的num是:  1
    3
    监听num变化,此时的num是:  3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我们再把上面的demo改成这样,看看会输出什么:

    const [ num , setNum ] = React.useState(0)
    const handleClick = ()=>{
        setNum(1) 
        console.log(num)
        setTimeout(()=>{
            setNum(3) 
            console.log(num)
        })   
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    结果:0 0 0

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

    我们只需要记住:在本次函数执行上下文中,是获取不到最新的 state 值的就可以了

    过时状态问题

    闭包是一个从外部作用域捕获变量的函数。
    闭包(例如事件处理程序,回调)可能会从函数组件作用域中捕获状态变量。 由于状态变量在渲染之间变化,因此闭包应捕获具有最新状态值的变量。否则,如果闭包捕获了过时的状态值,则可能会遇到过时的状态问题。
    来看看一个过时的状态是如何表现出来的。组件延迟3秒计数按钮点击的次数

    import React, { useState } from 'react';
    
    function DelayedCount() {
      const [count, setCount] = useState(0);
    
      const handleClickAsync = () => {
        setTimeout(function delay() {
          setCount(count + 1);
        }, 3000);
      }
    
      return (
        <div>
          {count}
          <button onClick={handleClickAsync}>Increase async</button>
        </div>
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    快速多次点击按钮。count 变量不能正确记录实际点击次数,有些点击被吃掉。
    delay() 是一个过时的闭包,它从初始渲染(使用0初始化时)中捕获了过时的count变量。
    为了解决这个问题,使用函数方法来更新count状态:

    import React, { useState } from 'react';
    
    function DelayedCount() {
      const [count, setCount] = useState(0);
    
      const handleClickAsync = () => {
        setTimeout(function delay() {
          setCount(count => count + 1);
        }, 3000);
      }
    
      return (
        <div>
          {count}
          <button onClick={handleClickAsync}>Increase async</button>
        </div>
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    现在setCount(count => count + 1)delay()中正确更新计数状态。React 确保将最新状态值作为参数提供给更新状态函数,过时闭包的问题解决了。
    快速单击按钮。 延迟过去后,count 能正确表示点击次数。

    更新引用数据类型

    在使用 useStatedispatchAction 更新 state 的时候,记得不要传入相同的 state,这样会使视图不更新:

    const textObj = {name:'yinjie'}
    const [useState1, setUseState1] = useState(textObj)
    
    setUseState1((oldUseState1) => {
    	oldUseState1.name = 'xxx'
        return oldUseState1
    }
    useEffect(() => {
    	console.log(useState1)  
    },[useState1])
    //结果是没有任何反应
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    为什么会造成这个原因呢?

    useStatedispatchAction 处理逻辑中,会浅比较两次 state ,发现 state 相同,不会开启更新调度任务;demo 中两次 state 指向了相同的内存空间,所以默认为 state 相等,就不会发生视图更新了。

    解决方法:

    const textObj = {name:'yinjie'}
    const [useState1, setUseState1] = useState(textObj)
    setUseState1((oldUseState1) => {
    	oldUseState1.name = 'xxx'
    	/** 返回一个新的对象,useEffectc才能检测得到 */
        return {...oldUseState1}
    }
    
    useEffect(() => {
    	console.log(useState1)  // {name: "xxx"}
    },[useState1])
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在上面的 demo 中我们浅拷贝了对象,重新申请了一个内存空间。

    useState 实现原理

    下面简单写一下useState的实现原理:

    function useState(init) {
    	let state;
    	// useState无法保存函数
    	if(typeof init === 'function') {
    		state = init()
    	} else {
    		state = init
    	}
    
    	const setState = (change) => {
    		// 判断一下是否传递过来的是函数
    		if(typeof change === 'function') {
    			// 如果是函数,调用,并将之前的state传过去,接收到的返回值作为新的state并赋值
    			state = change(state)
    		} else {
    			// 如果不是函数,直接赋值
    			state = change;
    		}
    	}	
    	return [state, setState]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
  • 相关阅读:
    Flink Table Store v0.2 应用场景和核心功能
    ssm在线教学质量评价系统毕业设计源码141550
    JSON,对象深拷贝
    呼叫中心系统角色功能的应用
    负载均衡集群技术LVS
    STM32 LED编程 GPIO的初始化(标准库)
    Dubbo-聊聊Dubbo协议
    面试题三:请你谈一谈Vue中的filter功能的实现
    matlab PSO粒子群优化混动汽车功率
    ggplot在直方图上添加核密度图及关于ggrcs包绘制直方图的说明
  • 原文地址:https://blog.csdn.net/qq_49900295/article/details/128067263