实现一个简单的 useState
import React from 'react';
import ReactDOM from 'react-dom';
function App() {
const [a, setA] = myUseState(0);
return (
<div className="App">
<div>
<span>{a}</span>
<button onClick={() => setA(a + 1)}>+</button>
</div>
</div>
);
}
// 定义一个重渲染函数
// 这里我们是简化了react重渲染的
// 这样当你更新useState的状态值,通过重渲染来更新UI视图
const rerender = () => {
ReactDOM.render(
<App />,
document.getElementById('root')
);
};
let _state; // 定义一个state状态值
// 使用myUseState函数来模拟useState
// 首先传入初始state状态值 - initialState
// 判断_state是否为undefined,如果为undefined则取初始值,否则直接使用_state值
// 因为_state为更新的状态值,如果未更新的话初始化的时候则默认取传入的initialState
// 当调用了setState之后,则更新_state,并进行重渲染来渲染UI视图
const myUseState = initialState => {
_state = _state === undefined ? initialState : _state;
const setState = newState => {
_state = newState;
rerender();
};
return [_state, setState];
};
export default App;
代码如上会存在一些问题,首先就是myUseState如果定义了多个state,那么_state就满足不了了。
如何才可以解决多个state状态值的问题呢?加强我们的myUseState咯?
主要的思路就是给组件每个调用myUseState的地方绑定一个内部的state值。 如下所示:
import React from 'react';
import ReactDOM from 'react-dom';
function App() {
const [a, setA] = myUseState(0);
const [b, setB] = myUseState(0);
return (
<div className="App">
<div>
<span>{a}</span>
<button onClick={() => setA(a + 1)}>+</button>
</div>
<div>
<span>{b}</span>
<button onClick={() => setB(b + 1)}>+</button>
</div>
</div>
);
}
const rerender = () => {
ReactDOM.render(
<App />,
document.getElementById('root')
);
};
let _state = [];
let index = 0;
const myUseState = initialState => {
const curIndex = index;
index++;
_state[curIndex] = _state[curIndex] === undefined ? initialState : _state[curIndex];
const setState = newState => {
_state[curIndex] = newState;
rerender();
};
return [_state[curIndex], setState];
};
export default App;
然后当你测试的时候你会发现并没有按照我们的思路走。
通过debug调试你会发现,初次render的时候,App里面打印出_state为[0, 0]。然后通过setA之后,变成了[1, 0, 0, 0]。
因为在setA的时候 _state[curIndex] = newState ,而此时的curIndex为0(其实就是一个闭包吧),newState为1。然后接着往下走rerender函数,又调用了两次myUseState。那么此时在调用myUseState这两次之前,index分别2,3。
那么你可能有疑问为什么_state为[1,0,0,0],而a没有更新为1呢?
因为rerender之后,对应的状态值a,b都为0了。如下所示,步骤1即返回给了a,而步骤2返回了b。
执行如下所示:
// 1. setA操作myUseState第一次调用
const myUseState = initialState => {
const curIndex = index; // curIndex为2了,自行体会下。不难理解
index++; // 下次读取index,他就是3了哦
_state[curIndex] = _state[curIndex] === undefined ? initialState : _state[curIndex];
const setState = newState => {
_state[curIndex] = newState;
rerender();
};
return [_state[curIndex], setState]; // _state[2]值为0
};
// 2. myUseState第二次调用
const myUseState = initialState => {
const curIndex = index; // curIndex 为 3
index++; // 下次读取index,他就是4了哦
_state[curIndex] = _state[curIndex] === undefined ? initialState : _state[curIndex];
const setState = newState => {
_state[curIndex] = newState;
rerender();
};
return [_state[curIndex], setState]; // _state[3]值为0
};
所以这就是为什么_state值为[1,0,0,0]的原因了。
那么如何解决呢?
思路就是在rerender之前,将index置为0就可以对应起来了。
所以将rerender修改下即可:
const rerender = () => {
index = 0;
ReactDOM.render(
<App />,
document.getElementById('root')
);
};
测试通过。
当然在react可不止这么简单,这边只是大致实现的思路。
在react内部,其实_state和index都会挂载到fiberNode上的对应属性上并进行维护,即链表形式的一种数据结构。
由于其实现机制需要记录次序,所以也就是为什么不可以在条件判断语句中(if)使用hook的原因了。
完整代码:
import React from 'react';
import ReactDOM from 'react-dom';
function App() {
const [a, setA] = myUseState(0);
const [b, setB] = myUseState(0);
return (
<div className="App">
<div>
<span>{a}</span>
<button onClick={() => setA(a + 1)}>+</button>
</div>
<div>
<span>{b}</span>
<button onClick={() => setB(b + 1)}>+</button>
</div>
</div>
);
}
const rerender = () => {
index = 0;
ReactDOM.render(
<App />,
document.getElementById('root')
);
};
let _state = [];
let index = 0;
const myUseState = initialState => {
const curIndex = index;
index += 1;
_state[curIndex] = _state[curIndex] === undefined ? initialState : _state[curIndex];
const setState = newState => {
_state[curIndex] = newState;
rerender();
};
return [_state[curIndex], setState];
};
export default App;
总结
- 每个函数组件会对应一个React节点
- 每个节点保存着state和index
- useState会读取state[index]
- index由useState出现的顺序决定
- setState会修改state,并触发更新
注意:此内容对react实现做了简化,react节点应该是FIberNode,_state真实名称对为memoizedState,index实现则用到链表。