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. 微信小程序-导航 & 路由

    微信小程序-导航 & 路由 页面跳转 页面路由 页面栈, 框架以栈的形式维护了当前的所有页面. https://developers.weixin.qq.com/miniprogram/dev ...

  2. 使用 js 实现一个中文自动转换成拼音的工具库

    使用 js 实现一个中文自动转换成拼音的工具库 中文 => zhong-wen 应用场景 SEO 友好, URL 自动转换 blogs 发布文章,自动化部署,自动生成 url 的 path (时 ...

  3. VAST重磅出击,NGK网络搜索量超越ETH!

    Wechat指数中,NGK超越ETH,NGK搜索指数是157648点位,单日环比上涨11.95%,ETH搜索指数是115604点位,就连区块链标杆的BTC也仅仅只有171669点位,我们可清楚的看到N ...

  4. 2018-1-6-IDEA快速代码生成

    2018-1-6-IDEA快速代码生成 Java 自动生成 Intellij IDEA 利用IDEA编辑器的Live Templates可以实现自定义方法.属性.注释等,下面是我自己的常用模板. 属性 ...

  5. 按键显示器(判断键盘监听器获得的值为普通Key还中modifiers)

    1 import sys 2 from PyQt5 import QtWidgets,QtCore 3 from PyQt5.QtCore import Qt 4 from PyQt5.uic.pro ...

  6. 解决margin-top无效问题

    当两个空的块级元素嵌套时,如果内部的块设置有margin-top属性,而且父元素没有下边解决方法所述的特征,那么内部块的margin-top属性会绑架父元素(即将margin-top传递凌驾给了父元素 ...

  7. windows server2012 搭建FTP服务器过程

    搭建过程链接地址:https://blog.csdn.net/smalllu161226/article/details/53887751 1.打开windows server2012R2 服务器管理 ...

  8. DQL:data query language用来查询数据库表中的数据

    对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 如果没有查询条件,则每次查询所有的行.实际应用中,一般要指定查询的条件.对记录进行过滤. 查询 ...

  9. 鸿蒙开源第三方组件 ——B站开源弹幕库引擎的迁移(上)

    鸿蒙入门指南,小白速来!0基础学习路线分享,高效学习方法,重点答疑解惑--->[课程入口] 目录: 一.弹幕库的基础知识 二.弹幕库的使用方法 三.sample解析 四.作者系列文章合集 前言 ...

  10. SpringBoot 整合 Shiro 密码登录与邮件验证码登录(多 Realm 认证)

    导入依赖(pom.xml)  <!--整合Shiro安全框架--> <dependency> <groupId>org.apache.shiro</group ...