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>
);
}
ReactDOM.render(<App />, rootElement);
运行过程:
- 首次渲染
render<App/> - 调用
App(),得到虚拟Div对象, 创建真实DIV - 当用户点击
button时,调用setN(n+1),再次render<App/>
分析
setN一定会修改数据x, 将n+1存入xsetN一定会触发<App/>,进行重新渲染useState肯定会从x读取n的最新值- 每个数据都有自己的数据
x, 我们将其命名为state
手动实现React.useState
let _state;
// 类似render原理实现
const render = () => ReactDOM.render(<App />, rootElement);
function myUseState(initialValue) {
_state = _state === undefined ? intialValue : _state
function setState(newState) {
_state = newState;
render();
}
return [_state, setState];
}
function App() {
const [n, setN] = myUseState(0);
return (
<div className="App">
<p>{n}</p>
<p>
<button onClick={() => setN(n + 1)}>+1</button>
</p>
</div>
);
}
ReactDOM.render(<App />, rootElement);
实现两个useState
改进思路
- 试着把
_state做成一个对象,比如_state= {n:0, m :0}。但是这样做的话,useState(0)就不知道变量叫n还是m - 试着把
_state做成数组,比如_state= [0,0], 这种方法似乎可行
代码实例
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];
}
// 类似render原理实现
const render = () => {
index = 0;
ReactDOM.render(<App />, rootElement);
};
function App() {
const [n, setN] = myUseState(0);
const [m, setM] = myUseState(0);
console.log(_state);
return (
<div className="App">
<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);
_state数组方案的缺点:
useState调用顺序。如果第一次渲染是n是第一个,m是第二个,k是第三个。则要求第二次渲染时必须保障顺序一致。也就是不能使用if...else打乱顺序。App用了_sate和index,其他组件用什么? 解决方法: 给每个组件创建一个_state和index_state和index放在全局作用域重名了怎么办? 解决方法:放在组件对应的虚拟节点对象上

小结
- 每个函数组件对应一个
React节点, 即FiberNode - 每个节点保存着
state和index,state即memorizedState,index的实现使用了链表结构 useState会读取state[index]index由useState调用的顺序决定setState会修改state,并触犯更新
useRef和useContext
代码实例
unction App() {
const [n, setN] = React.useState(0);
const log = () => setTimeout(() => console.log(`n: ${n}`), 3000);
return (
<div className="App">
<p>{n}</p>
<p>
<button onClick={() => setN(n + 1)}>+1</button>
<button onClick={log}>log</button>
</p>
</div>
);
}
ReactDOM.render(<App />, rootElement);
上面的代码出现了问题
- 点击
+1再点击log没有bug - 点击
log再点击+1,出现bug,为什么log打印的是上一次的数据
疑惑解答: 因为有多个n

- 使用全局变量,比如
window.xxx - 使用
useRef,useRef不仅可以用于div,还能用于任意数据。但是useRef不会在属性变动时自动触发更新,只能手动设置更新,但是不推荐使用手动更新
function App() {
const nRef = React.useRef(0);
const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000);
return (
<div className="App">
<p>{nRef.current} 这里并不能实时更新</p>
<p>
<button onClick={() => (nRef.current += 1)}>+1</button>
<button onClick={log}>log</button>
</p>
</div>
);
}
ReactDOM.render(<App />, rootElement);
- 使用
useContext,useContext不仅可以贯穿始终,还可以贯穿不同组件
function App() {
const [theme, setTheme] = React.useState("red");
return (
<themeContext.Provider value={{ theme, setTheme }}>
<div className={`App ${theme}`}>
<p>{theme}</p>
<div>
<ChildA />
</div>
<div>
<ChildB />
</div>
</div>
</themeContext.Provider>
);
}
function ChildA() {
const { setTheme } = React.useContext(themeContext);
return (
<div>
<button onClick={() => setTheme("red")}>red</button>
</div>
);
}
function ChildB() {
const { setTheme } = React.useContext(themeContext);
return (
<div>
<button onClick={() => setTheme("blue")}>blue</button>
</div>
);
}
ReactDOM.render(<App />, rootElement);
小结
- 每次重新渲染,函数组件就会执行
- 函数组件对应的所有
state就会被重新复制 - 如果不想出现复制的
state,可以使用useRef,或者useContext解决