我们来看一下这个 + 1 的案例,里面使用了 useState ,useState是如何运行的呢?
我们不妨自己手写探讨一下。
function App() {
const [n, setN] = React.useState(0);
return (
<div className="App">
<p>{n}</p>
<p>
<button onClick={() => setN(n + 1)}>+1</button>
</p>
</div>
);
}
setN-定会修改数据x,将n+1存入
setN 一定会触发重新渲染(re-render)
useState肯定会从x读取n的最新值
setN 并不会改变n,App()会重新执行。
第一尝试手写
function myUseState(initialValue) {
var state = initialValue;
function setState(newState) {
state = newState;
render();
}
return [state, setState];
}
出现的BUG 每次都会重新复制为0。
myUseState 会将 state 重置。
这样我们将外部声明一个变量,他就不会重置
let _state;
function myUseState(initialValue) {
_state = _state === undefined ? initialValue : _state;
function setState(newState) {
_state = newState;
render();
}
return [_state, setState];
}
出现的BUG,如果出现了2个useState就发生冲突,原因是我们存储的所有数据都在_state
。
所以一旦出现多个使用useState,j就会出现BUG。
接着尝试_state
变为一个数组,用下标去定位想要获取的值。
let _state = [];
let index = 0;
function myUseState(initialValue) {
const currentIndex = index;
index += 1;
_state[currentIndex] = _state[currentIndex] || initialValue;
const setState = newState => {
_state[currentIndex] = newState;
render();
};
return [_state[currentIndex], setState];
}
但是这个方案还是存在一些问题。我们每次渲染的值必须保证顺序完全一致。所以子啊Reactl里面不允许出现以下代码。
let m, setM;
if (n % 2 === 1) {
[m, setM] = React.useState(0);
}
上面这样看似在筛选数据,但是这样就打乱了它本存在的数据。所以它不能用在if条件判断。
但是我们的代码还是存在命名重复的问题。
解决办法:
在每个组件对应的虚拟节点的对象上创建一个_state 和 index。
总结以上的规律
- 每个函数组件对应一个React节点*
- 每个节点保存着state和index
- useState会读取state[index]
- index由useState出现的顺序决定
- setState会修改state,并触发更新
我们不妨再试一下这个案例
当我们快速点击log 再点击+1 3秒后发现打出来的 log 是0
为什么呢 ,因为在这个useState会有多个n
setN不会改变 n ,它会在这个时间点多出现一个新的 n
如果想先按 log 后按 +1 同步打出 1
方法一:useRef
const nRef = React.useRef(0);
const update = React.useState()[1];
const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000);
详细案例
方法二: useContext
详细案例
总结:
- 每次重新渲染,组件函数就会执行
- 对应的所有state都会出现「分身」
- 如果你不希望出现分身
- 可以用useRef | useContext等