基本写法

  1. function App(){
  2. const [n,setN] = useState(0);
  3. return (
  4. <div>
  5. <p>{n}</p>
  6. <button onClick={()=>setN(n+1)}>{n}</button>
  7. </div>
  8. )
  9. }
  10. ReactDOM.render(<App />,rootElement)

如上代码所示,这是一个简单的useState应用,单击button,n+1。

那么useState的运行原理是怎样的呢?setN直接将n变成n+1?

运行原理

首次渲染 render ,调用App(),得到一个虚拟的div,然后React将其变成一个真实的div。

用户点击button,调用setN(n+1),再次 render ,调用App(),得到一个新的虚拟div,React将新旧对比获得一个新的真实div。

每次调用App(),都会运行useState(0)。但每次n的值都不同,它是如何做到的?

几个问题

  • 执行setN的时候会发生什么?n会变吗?App()会重新执行吗?
    n不会变,App()会重新执行

  • 如果App()会重新执行,那么useState(0)的时候,n每次的值会有不同吗?
    n的值会不同

分析

每个组件都有自己的数据x,将其叫做state

setN

setN一定修改了某个数据x,即将n+1存入x

setN一定会触发重新渲染

useState

useState一定会从x中读取n的最新值

尝试实现useState

  1. let _state
  2. const myUseState = (initialValue)=>{
  3. _state = _state === undefined ? initialValue : _state;
  4. const setState = (newValue) => {
  5. _state = newValue;
  6. render();
  7. //这里的render指的是重新渲染API,这里是最无脑的写法
  8. }
  9. return [_state,setState]
  10. };
  11. const render = () =>{
  12. ReactDOM.render(<App />,rootElement)
  13. };
  14. function App(){
  15. const [n,setN] = myUseState(0);
  16. return (
  17. <div>
  18. <p>{n}</p>
  19. <button onClick={()=>setN(n+1)}>{n}</button>
  20. </div>
  21. )
  22. };
  23. ReactDOM.render(<App />,rootElement)

但这种写法存在问题,那就是无法使用多个useState。

如何使用多个useState

思路:将_state变成一个数组。

  1. let _state = [];
  2. let index = 0;
  3. //声明一个数组和顺序
  4. function myUseState(initialValue) {
  5. const currentIndex = index;
  6. index += 1;
  7. _state[currentIndex] = _state[currentIndex] || initialValue;
  8. const setState = newState => {
  9. _state[currentIndex] = newState;
  10. render();
  11. };
  12. return [_state[currentIndex], setState];
  13. }
  14. const render = () => {
  15. index = 0;
  16. //每次重新渲染需要重置index,否则数组长度会增加,导致失败
  17. ReactDOM.render(<App />, rootElement);
  18. };
  19. function App() {
  20. const [n, setN] = myUseState(0);
  21. const [m, setM] = myUseState(0);
  22. console.log(_state);
  23. return (
  24. <div className="App">
  25. <p>{n}</p>
  26. <p>
  27. <button onClick={() => setN(n + 1)}>+1</button>
  28. </p>
  29. <p>{m}</p>
  30. <p>
  31. <button onClick={() => setM(m + 1)}>+1</button>
  32. </p>
  33. </div>
  34. );
  35. }
  36. ReactDOM.render(<App />, rootElement);

这样做就需要每个useState需要按一定顺序调用,否则会出现问题,不能缺项,顺序不能发生变化。

React也是这样规定的。

简单总结

用自己的话,简单总结下useState

  • 每个函数组件对应一个React节点
  • 每个节点保存这state和index
  • useState会读取state[index]
  • index由useState出现的顺序决定
  • steState会修改state,触发重新渲染
  • useState不能局部更新
  • setState(obj)后,obj的地址会发生变化
  • useState也能够接收函数

更新问题

  1. function App() {
  2. const [n, setN] = React.useState(0);
  3. const log = () => setTimeout(() => console.log(`n: ${n}`), 3000);
  4. return (
  5. <div className="App">
  6. <p>{n}</p>
  7. <p>
  8. <button onClick={() => setN(n + 1)}>+1</button>
  9. <button onClick={log}>log</button>
  10. </p>
  11. </div>
  12. );
  13. }
  14. ReactDOM.render(<App />, rootElement);

这是一个简单的页面,点击+1button时n+1,点击log时三秒后log n。

这里有两种情况:

  • 先点击+1,再点击log
    n+1,log也是n+1

  • 先点击log,再点击+1
    先显示n+1,log的结果却是n

这是为什么?

第二种情况的执行原理是:

点击log,然后log函数读取现在的n,当点击+1时,触发重新渲染得到一个新的n,注意新旧两个n是同时存在的,不是同一个,因为React不会直接修改原值。