useState 用法
看下例代码,可以想象一下,点击 button 会发送什么
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 />
调用 App() ,得到虚拟 DIV , DOM Diff ,更新真实 DIV
some questions
脑补之后,可以思考几个问题
执行 setN 的时候会发生什么?n会变吗?App()会重新执行吗?
如果App()会重新执行,那么 useState(0) 的时候,n 每次的值会有不同吗?
执行setN 的时候,n 并不会马上变,而是稍会儿变。
可以看到,App() 确实会重新执行,重新执行useState(0),每次n是不同的。
分析
setN 一定会修改某个数据x,将 n+1存入 x,并且一定会触发 <App />
重新渲染(re-render)
useState肯定会从x读取n的最新值
每个组件都有自己的数据x,可以将其命名为 state
尝试实现 React.useState
看下面代码 示例
let _state;
function myUseState(initialValue) {
_state = _state === undefined ? initialValue : _state;
function setState(newState) {
_state = newState;
render();
}
return [_state, setState];
}
// 理解需要,不用在意 render 的实现
const render = () => ReactDOM.render(<App />, rootElement);
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 难道就这么简单?
别急,还有问题
如果一个组件用了两个useState怎么办,由于所有数据都放在 _state ,所以会冲突
改进思路
把 _state 做成一个对象,比如 _state={a:0,b:0} ,其实不行,因为 useState(0) 并不知道变量叫 a 还是 b
把 _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];
}
_state 数组方案缺点
useState 调用顺序
若第一次渲染时 n 是第一个,m 是第二个, k 是第三个,则第二次渲染时必须保证顺序完全一致
所以 React 不允许出现如下代码, 不能存在 if 中
现在的实现代码还有一个问题
App 用了 _state 和 index , 那其他组件用什么?
解决办法:给每个组件创建一个 _state 和 index
又有问题:放全局作用域重名了怎么办?
解决办法:放在组件对应的虚拟节点对象上
总结
每个函数组件对应着一个 React 节点
每个节点保存着 state 和 index
useState 会读取 state[index]
index 由useState 出现的顺序决定
setState 会修改 state,并触发更新
文中的实现都是基于思想逻辑的简化处理