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。也就是说oldN和newN是同属存储在缓存中的,并非是同一个变量。
当一个值没有被变量引用就会自动被垃圾回收。
在setState解除对oldN的引用之前,oldN被定时器引用,这就会使oldN没有被垃圾回收。而setState会得到newN。此时oldN和newN毫无关系,它们并不是同一个变量。这就导致了三秒之后定时器打印出来n的值是之前的oldN
为什么会导致变量n前后会不是同一个呢?
因为newN和oldN是在不同的虚拟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都会创建新的变量。
那么让自始至终都使用的同一个变量呢?
- 使用
window.state挂到全局上,当然这个一看就不太行,至少污染了全局 - 使用
React.useRef,不过改变React.useRef页面不会自动更新,所以还需要配合React.useState。 - 使用
React.createContext全局上下文,该方法比较浓重,一般使用第二种方法。 - 终极办法使用
Vue3...
setState注意事项
跟新对象时
- 不可局部更新
- 地址必须要改变