Hooks原理解析
- 分析useState原理和源码
- useRef的作用
- useContext的作用
- Vue3对比React
function App() {
const [n, setN] = React.useState(0);
return (
<div className="App">
{n}
<button onClick={()=>setN(n+1)}>+1<button>
</div>
);
}
React.render(<App />, rootElement);
首次渲染 render<App />—>调用App()—>得到虚拟div—>创建真实div—>用户点击button,调用setN(n+1)—>再次render<App />—>再次调用App()—>得到虚拟div—>DOM diff—>更新真的div
每次调用App(),都会运行useState(0)
但是每次运行useState得到的n是变化的,就是正确的n
执行setN的时候n不会变,App()会重新渲染
这个useState做了啥
分析
setN
- setN一定会改变数据x(中间变量),将
n+1存入x - setN一定会触发
<App />重新渲染
useState - useState肯定会从x读取n的最新的值
x - 每个组件有自己的数据x,我们将其命名为state
尝试实现React.useState
const myUseState = initialValue => {
var state = initialValue;
const setState = newState {
state = newState;
render();
}
return [state, setState];
}
//不用在意render的实现
const render = () => ReactDOM.render(<App />, rootElement);
function App() {
const [n, setN] = myUseState(0);
return (
<div>
<p>{n}</p>
<p>
<button onClick={()=>setN(n+1)}>+1</button>
</p>
</div>
);
}
ReactDOM.render(<App />, rootElement);
上面的代码完全没有用,因为myUseState会将state重置
解决:
我们将这个变量声明在myUseState外面即可
let _state;
const myUseState = initialValue => {
_state = _state === undefined ? initialValue : _state;
const setState = newState {
_state = newState;
render();
}
return [_state, setState];
}
//不用在意render的实现
const render = () => ReactDOM.render(<App />, rootElement);
function App() {
const [n, setN] = myUseState(0);
return (
<div>
<p>{n}</p>
<p>
<button onClick={()=>setN(n+1)}>+1</button>
</p>
</div>
);
}
ReactDOM.render(<App />, rootElement);
现在就能用了。能够实现加一的功能,这是简单的实现,还有问题,要修改一下
问题
如果一个组件用了两个useState咋办
const [n, setN] = myUseState(0);
const [m, setM] = myUseState(0);
这两个组件共用一个_state就会冲突
解决:把_state做成数组
let _state = [];
let index = 0;//表示第几个
const myUseState = initialValue => {
const currentIndex = index;
_state[currentIndex] = _state[currentIndex] === undefined ? initialValue : _state[currentIndex];
const setState = newState {
_state[currentIndex] = newState;
render();
}
index += 1;
return [_state[currentIndex], setState];
}
//不用在意render的实现
const render = () => {
index = 0;
ReactDOM.render(<App />, rootElement);
}
function App() {
const [n, setN] = myUseState(0);
const [m, setM] = myUseState(0);
return (
<div>
<p>{n}</p>
<p>
<button onClick={()=>setN(n+1)}>+1</button>
</p>
<p>{m}</p>
<p>
<button onClick={()=>setM(m+1)}>+1</button>
</p>
</div>
);
}
ReactDOM.render(<App />, rootElement);
为什么要使用currentIndex,因为要实现先返回[_state[index], setState],再去index += 1,都返回了就不可能再加一,所以需要一个中间的变量。
然后在render中把index重置为0,免得超出数组下标。
难点
为什么n和m的下标对应了数组的下标呢,因为在App里面是按顺序执行的,index会随之改变,把当前下标赋值给n和m,setN和setM。只有重新渲染了,index才会被重新置为0,但是又会渲染App,正确的下标又被给了n和m。
新问题
上面的难点就是因为顺序是不变的,所以n和m的下标才会正确的对应上。
所以下面的代码是不允许出现的
function App() {
const [n, setN] = React.useState(0);
let m, setM;
if(n%2===1) {
[m, setM] = React.useState(0);
}
const [j, setJ] = React.useState(0);
...
}
这样会打乱顺序,如果if语句后面还要使用useState,就更会出问题了。
useState不能放在if语句里面
问题重重
App组件用了_state和index,那其他组件用什么?
解决:给组件创建一个_state和index
又有问题:放在全局作用域重名了咋整?
解决:放在组件对应的虚拟节点对象上
图示
总结
- 每个组件对应一个React节点
- 每个节点保存着state和index
- useState会读取state[index]
- index由useState出现的顺序决定
- setState会修改state,并触发更新
上面的做了简化,React节点应该是FiberNode,_state的真实名字称为memorizedState,index的实现规则用到了链表
可以点击进行自行学习