useState运行过程解析


function App() {
const [n, setN] = useState(0); //使用 myUseState()
return (
<div>
<p>{n}</p>
<p>
<button
onClick={() => {
setN(n + 1);
}}
>
+1
</button>
</p>
</div>
);
} const rootElement = document.getElementById("root");
ReactDOM.render(<App></App>, rootElement);
第一次渲染
  1. 调用 render <App /> ,<App / >组件调用 App()函数

  2. App()函数调用 const [n, setN] = useState(0)给 n 赋值,并且得到 一个 setN函数

  3. App()函数会根据 n 的值 和 setN返回一个虚拟的 DOM节点

  4. React 会根据 虚拟DOM创建一个真实DOM节点,并且将真实DOM渲染到浏览器上,所以我们可以通过调试工具看到一个渲染出来的 DIV节点

点击一次button按钮
  1. 调用 onClick()函数,onClick()函数又调用setN()函数

  2. setN在更新了 n 的值(先不管是怎么更新的)之后会重新调用 render <App />

  3. 组件重新调用 App()函数

  4. App函数同样会执行const [n, setN] = useState(0),根据 新的n 返回一个 新的虚拟DOM

  5. React 根据 DOM Diff算法 将 新的虚拟DOM旧的虚拟DOM进行对比,得到最小的变动范围对象(patch 对象)

  6. 根据最小的变动范围对象来更新真实DOM

    注:

    1. 每次调用 App()都会运行 const [n, setN] = useState(0)
    2. 同样的一句话每次调用之后,每次得到的 n的值是不一样的

点击button 后 n 是怎么变的 ?

首先要思考几个问题:

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

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

    以上问题通过 console.log()就能得到答案

执行 setN的时候
  1. 一定会重新渲染UI,但是这时候 n 会变吗?n 还没有变!!

  2. setN( n + 1)并没有改变 n,而是改变了一个中介数据 state(state 是什么?继续往下看)

  3. 因为要重新渲染UI,所以 render <App />会重新执行 App()

重新执行App()的时候
  1. 重新执行const [n, setN] = useState(0)的时候确实 n 会得到不同的值!
分析
  • setN

    • setN(n + 1) 一定会修改数据 state,将 n + 1存入state
    • setN()一定会触发 重新渲染(re-render)
  • useState

    • useState肯定会从 state读取 n 的最新值
  • state

    • 每个组件都有自己的数据 state,这是我们需要理解的核心

尝试实现一个 useState



const myUseState = (initialValue) => {
let state = initialValue ;// 我们设置的一个 state 接收 初始值
const setState = (newValue) => {
// 我们设置的一个 setState函数
state = newValue; // 更新 state 的值
re_render(); // useState会重新渲染页面UI
};
return [state, setState]; // 返回一个 state 和 更新state的函数: setState
}; const re_render = () => {
ReactDOM.render(<App></App>, rootElement);
}; // 上面是我们实现的一个 myUseState
function App() {
console.log("App 运行了");
const [n, setN] = myUseState(0);
console.log(n)
return (
<div>
<p>{n}</p>
<p>
<button
onClick={() => {
setN(n + 1);
}}
>
+1
</button>
</p>
</div>
);
} const rootElement = document.getElementById("root");
ReactDOM.render(<App></App>, rootElement);

上面的代码还存在一些问题

  • 每次调用 myUseState(0)都会将 state重置为初始值

  • 我们需要一个不会被 myUseState重置的 state变量

  • 还需要判断 state的取值是 初始值还是新值

改进 state变量


let _state;
const myUseState = (initialValue) => {
_state = _state === undefined ? initialValue : _state;// 我们设置的一个 state 接收 初始值
const setState = (newValue) => {
// 我们设置的一个 setState函数
_state = newValue; // 更新 state 的值
re_render(); // useState会重新渲染页面UI
};
return [_state, setState]; // 返回一个 state 和 更新state的函数: setState
}; const re_render = () => {
ReactDOM.render(<App></App>, rootElement);
}; // 上面是我们实现的一个 myUseState
function App() {
console.log("App 运行了");
const [n, setN] = myUseState(0);
console.log(n)
return (
<div>
<p>{n}</p>
<p>
<button
onClick={() => {
setN(n + 1);
}}
>
+1
</button>
</p>
</div>
);
} const rootElement = document.getElementById("root");
ReactDOM.render(<App></App>, rootElement);

这样我们就实现了一个 useState!!

useState那么简单吗?

  • 上面的 useState只能实现一个组件使用一个 useState 的情况
  • 如果一个组件使用了两个 useState,由于数据都放在 _state,所以会产生冲突
改进思路
  1. 把_state做成一个对象

    • 比如_state = { n:0, m:0 }
    • 不行, 因为useState(0)并不知道变量叫 n 还是 m
  2. 把_state 做成数组
    • 比如_state = [0, 0]
    • 貌似可以

let _state = [];
let index = 0; const myUseState = (initialValue) => {
const currentIndex = index; // 需要一个 currentIndex 来保存当前的 index 的值
_state[currentIndex] = _state[currentIndex] === undefined ? initialValue : _state[currentIndex];
const setState = (newValue) => {
_state[currentIndex] = newValue;
re_render();
}; index += 1; // 当前index设置完之后,后面的state要存放在下一个 index中
return [_state[currentIndex], setState];
}; const re_render = () => {
ReactDOM.render(<App></App>, rootElement);
}; function App() {
const [n, setN] = myUseState(0);
const [m, setM] = myUseState(0);
return (
<div>
<p>n :{n}</p>
<button
onClick={() => {
setN(n + 1);
}}
>
n+1
</button>
<p>m: {m}</p>
<button
onClick={() => {
setM(m + 1);
}}
>
m+1
</button>
</div>
);
} const rootElement = document.getElementById('root');
ReactDOM.render(<App></App>, rootElement);
代码分析
  1. 上面的代码貌似可以解决两个 state 同时存储的问题,但是还是不能完成 useState的操作
  2. 我们应该要注意到当我们只是想重新更新 state的值的时候需要重新渲染App() ,index += 1同样也会执行,这就意味着这个数组每次调动useState之后都会变长一个单位!变长的总长度就是 App()里面使用的 useState的次数。
  3. 所以我们需要在每次渲染 <App />之前都需要重置一下 index = 0
重置 index

let _state = [];
let index = 0; const myUseState = (initialValue) => {
const currentIndex = index; // 需要一个 currentIndex 来保存当前的 index 的值
_state[currentIndex] = _state[currentIndex] === undefined ? initialValue : _state[currentIndex];
const setState = (newValue) => {
_state[currentIndex] = newValue;
re_render();
}; index += 1; // 当前index设置完之后,后面的state要存放在下一个 index中
return [_state[currentIndex], setState];
}; const re_render = () => {
index = 0; // !! 这里很重要,重新渲染之前必须要重置 index = 0,否则数组会变长!
ReactDOM.render(<App></App>, rootElement);
}; function App() {
const [n, setN] = myUseState(0);
const [m, setM] = myUseState(0);
return (
<div>
<p>n :{n}</p>
<button
onClick={() => {
setN(n + 1);
}}
>
n+1
</button>
<p>m: {m}</p>
<button
onClick={() => {
setM(m + 1);
}}
>
m+1
</button>
</div>
);
} const rootElement = document.getElementById('root');
ReactDOM.render(<App></App>, rootElement);
终于成功实现了useState的逻辑!

总结一下:

1. 我们要知道 `useState`的运行过程,n 是怎么进行改变的
2. 当使用两个 `state`的时候要保证不冲突,所以要把 state 定义成数组用来存储
3. 使用 `currentIndex` 记录当前的 index
4. 在 App 渲染之前要重置 `index = 0`,否则 state 数组会变长!

useState很明显的缺点

  1. useState 调用顺序不能乱

    • 如果第一次渲染时 n 是第一个,m是第二个,k是第三个
    • 则第二次渲染时必须保证顺序完全一致
    • 所以React不允许出现以下代码

    不允许出现的代码:

    if( n % 2 === 0){
    [m, setM] = React.useState(0);
    }

    否则会报错:

    /*
    React Hook 'React.useState' is called conditionally. React Hooks must be called in the exact same order in every component render.

    这个报错的原理其实就是我们存储state的数组的顺序是必须要一致的,否则如果存在判断语句的话 state的调用顺序不一致,就会导致 state的值混乱!这会直接导致出现bug或者错误!!

    注:vue3 目前已经克服了这个问题,似然vue3是借鉴的 react 的思想

  2. App 用了 _state 和 index,那其他组件还能用吗?

    • 我们可以给每个组件都创建 _state 和 index
  3. _state 和 index 放在全局作用域重名了咋办?

    • 每个组件都有一个虚拟DOM节点
    • 我们可以把 _state和index放到虚拟DOM上

认清 React 的useState逻辑的更多相关文章

  1. React Hooks --- useState 和 useEffect

    首先要说的一点是React Hooks 都是函数,使用React Hooks,就是调用函数,只不过不同的Hooks(函数)有不同的功能而已.其次,React Hooks只能在函数组件中使用,函数组件也 ...

  2. React Hooks: useState All In One

    React Hooks: useState All In One useState import React, { useState } from 'react'; function Example( ...

  3. React报错之React hook 'useState' is called conditionally

    正文从这开始~ 总览 当我们有条件地使用useState钩子时,或者在一个可能有返回值的条件之后,会产生"React hook 'useState' is called conditiona ...

  4. React报错之React hook 'useState' cannot be called in a class component

    正文从这开始~ 总览 当我们尝试在类组件中使用useState 钩子时,会产生"React hook 'useState' cannot be called in a class compo ...

  5. React Hooks useState为什么顺序很重要

    一个Function Component的state状态整体是作为memoizedState存在FIber中的. function执行时,首先取memoizedState第一个base state,作 ...

  6. react的登录逻辑

    https://blog.csdn.net/qq_36822018/article/details/83028661(先看看这个 https://blog.csdn.net/weixin_342681 ...

  7. React基本实例

    学习React不是一蹴而就的事情,入门似乎也没那么简单.但一切都是值得的. 今天给大家带来一个详细的React的实例,实例并不难,但对于初学者而言,足够认清React的思考和编写过程.认真完成这个实例 ...

  8. React Hooks用法大全

    前言 在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖 ...

  9. React 新特性学习

    1 context 2 contextType 3 lazy 4 suspense 5 memo 6 hooks 7 effect hooks =========== 1 Context 提供了一种方 ...

随机推荐

  1. Vue dynamic component All In One

    Vue dynamic component All In One Vue 动态组件 vue 2.x https://vuejs.org/v2/guide/components-dynamic-asyn ...

  2. FTP 与 SSH 的安全性对比, 以及FTP,SSH,SFTP,SCP 的关系简单解析!

    FTP 与 SSH 的安全性对比? ftP: http://baike.baidu.com/subview/369/6149695.htm TCP/IP协议中,FTP标准命令TCP端口号为21,Por ...

  3. How to enable HTTPS for local development in macOS using Chrome

    How to enable HTTPS for local development in macOS using Chrome HTTPS, macOS, Chrome local HTTPS htt ...

  4. Linux shell create file methods

    Linux shell create file methods touch, cat, echo, EOF touch $ touch file.py $ touch file1.txt file2. ...

  5. leetcode bug & 9. Palindrome Number

    leetcode bug & 9. Palindrome Number bug shit bug "use strict"; /** * * @author xgqfrms ...

  6. historyReverser & array reverse

    historyReverser & array reverse\ "use strict"; /** * * @author xgqfrms * @license MIT ...

  7. iPad Pro 如何通过 USB-C 接口外接机械键盘

    iPad Pro 如何通过 USB-C 接口外接机械键盘 外接机械键盘 Type-C Dock OK,cnblogs 图片文件不能超过20M https://support.apple.com/zh- ...

  8. components & slot

    components & slot vue https://github.com/vuejs/rfcs/blob/master/active-rfcs/0001-new-slot-syntax ...

  9. Regular Expressions all in one

    Regular Expressions all in one Regular Expressions Cheatsheet https://developer.mozilla.org/en-US/do ...

  10. vscode Paste Image插件使用

    Paste Image 在编写md需要插入图片,这个插件可以将粘贴板的图片保存到本地资源 假如我在/readme.md中编写文档,我需要将粘贴板的图片放在/images/下面,配置两个关键配置即可: ...