你认为React.setState改变了state值吗

1,373 阅读3分钟

setState是否会改变n的值

换一个角度: 你认为React.setState(n+1)之前的n和之后n是同一个n吗?

你的想当然不一定是对的,让我们通过代码来思考一下。页面提供了两个按钮,一个是将n+1一个是在三秒钟之后打印出n的值。

代码示例:

const Component = () => {
    const [n,setN] = React.useState(0)
    const log = ()=>{
        console.log('点击了log三秒之后打印n的值')
        setTimeout(()=>{
            console.log('n:',n)
        },3000)
    }
    return (
        <>
            <button onClick={log}>log</button>
            <button onClick={()=>{setN(n+1);console.log('点击了n')}}>n+1</button>
            <div>n: {n}</div>
        </>
    )
}

测试一

先点击n+1,后点击log

结果符合我们的正常思维。在三秒钟之后n=1

测试二

先点击log,后点击n+1

控制台的输出似乎不太符合我们的预期,三秒之后输出居然是n=0

分析

当我们点击log的时候会得到一个旧的n我将它称为oldN,定时器会引用这个oldN,点击n+1之后setState(n+1)会将oldN解除引用,得到新的+1之后的n将它称为newN。也就是说oldNnewN是同属存储在缓存中的,并非是同一个变量。

当一个值没有被变量引用就会自动被垃圾回收。

setState解除对oldN的引用之前,oldN被定时器引用,这就会使oldN没有被垃圾回收。而setState会得到newN。此时oldNnewN毫无关系,它们并不是同一个变量。这就导致了三秒之后定时器打印出来n的值是之前的oldN

为什么会导致变量n前后会不是同一个呢?

因为newNoldN是在不同的虚拟DOM中的,是由函数组件返回的新的虚拟DOM对象。这就直接导致了setState之后的n是不同的。

这就是React的设计思想,React认为一个变量用了就不要去改它了,需要修改的时候就直接换一个新的。

有什么办法可以让一个变量贯穿始终

使用React.useRef

有什么方法可以让state都是同一个变量,不要每次都去创建一个新的。

利用useRef的自始至终都是同一个DOM元素,但是useRef改变是不会渲染页面的,还是需要配合使用useState来更新UI。只要useState之前的值和之后的值不同就会更新页面。

const Component = () => {
    let n = React.useRef(0) //得到一个对象{current:0}
    const log = () => {
        console.log('点击了log三秒之后打印n的值')
        setTimeout(() => {
            console.log('n:', n.current)
        }, 3000)
    }
    const onClick = () => {
        n.current += 1
        update(n.current)
        console.log('点击了log')
    }
    const update = React.useState(null)[1] //只需要使用数组的第二个元素。
    return (
        <>
            <button onClick={log}>log</button>
            <button onClick={onClick}>n+1</button>
            <div>n: {n.current}</div>
        </>
    )
}

效果示例: 自始至终都是同一个变量n

React.createContext

createContext用于创建一个全局上下文,其实就是全局变量,只不过全局的范围被限制在组件中。指定组件范围内的全局变量,不管是多深的子组件都可以使用。

React.createContext(null)
const Theme = () => {
    const [theme,setTheme] = React.useState('red')
    return (<themeContext.Provider value={{theme,setTheme}}> //在改组件作用域内的所有子组件都可以使用该变量。
        <Child1></Child1>
        <Child2></Child2>
    </themeContext.Provider>)
}


const Child2 = () => {
    const {theme,setTheme} = React.useContext(themeContext)
    return (
        <div>
        	{theme}
            <button onClick={()=>setTheme('blue')}>blue</button>
        </div>
    )
}

总结

React.setState并不会去改变原始的state值而是会用一个新的替换它。也就是每次React.setState都会创建新的变量。

那么让自始至终都使用的同一个变量呢?

  1. 使用window.state挂到全局上,当然这个一看就不太行,至少污染了全局
  2. 使用React.useRef,不过改变React.useRef页面不会自动更新,所以还需要配合React.useState
  3. 使用React.createContext全局上下文,该方法比较浓重,一般使用第二种方法。
  4. 终极办法使用Vue3 ...

setState注意事项

跟新对象时

  1. 不可局部更新
  2. 地址必须要改变

其他文章

剖析React.useState存储原理