认清 React 的useState逻辑
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);
第一次渲染
调用
render <App />
,<App / >组件调用App()
函数App()
函数调用const [n, setN] = useState(0)
给 n 赋值,并且得到 一个setN
函数App()
函数会根据n
的值 和setN
返回一个虚拟的DOM
节点React 会根据
虚拟DOM
创建一个真实DOM
节点,并且将真实DOM
渲染到浏览器上,所以我们可以通过调试工具看到一个渲染出来的DIV
节点
点击一次button按钮
调用
onClick()
函数,onClick()
函数又调用setN()
函数setN
在更新了 n 的值(先不管是怎么更新的)之后会重新调用render <App />
组件重新调用
App()
函数App
函数同样会执行const [n, setN] = useState(0)
,根据新的n
返回一个新的虚拟DOM
React 根据
DOM Diff
算法 将新的虚拟DOM
和旧的虚拟DOM
进行对比,得到最小的变动范围对象(patch 对象)根据最小的变动范围对象来
更新真实DOM
注:
- 每次调用
App()
都会运行const [n, setN] = useState(0)
- 同样的一句话每次调用之后,每次得到的
n
的值是不一样的
- 每次调用
点击button 后 n 是怎么变的 ?
首先要思考几个问题:
执行
setN()
的时候会发生什么: n 会变吗?App()
会重新执行吗?如果
App()
会重新执行,那么useState(0)
的时候,n
每次的值会有不同吗?以上问题通过 console.log()就能得到答案
执行 setN
的时候
一定会重新渲染UI,但是这时候 n 会变吗?n 还没有变!!
setN( n + 1)
并没有改变 n,而是改变了一个中介数据state
(state 是什么?继续往下看)因为要重新渲染UI,所以
render <App />
会重新执行App()
重新执行App()
的时候
- 重新执行
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,所以会产生冲突
改进思路
- 把_state做成一个对象
- 比如_state = { n:0, m:0 }
- 不行, 因为
useState(0)
并不知道变量叫n
还是m
- 把_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);
代码分析
- 上面的代码貌似可以解决两个 state 同时存储的问题,但是还是不能完成
useState
的操作 - 我们应该要注意到当我们只是想重新更新
state
的值的时候需要重新渲染App()
,index += 1
同样也会执行,这就意味着这个数组每次调动useState
之后都会变长一个单位!变长的总长度就是App()
里面使用的useState
的次数。 - 所以我们需要在每次渲染
<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
很明显的缺点
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 的思想
App 用了 _state 和 index,那其他组件还能用吗?
- 我们可以给每个组件都创建 _state 和 index
_state 和 index 放在全局作用域重名了咋办?
- 每个组件都有一个虚拟DOM节点
- 我们可以把 _state和index放到虚拟DOM上
认清 React 的useState逻辑的更多相关文章
- React Hooks --- useState 和 useEffect
首先要说的一点是React Hooks 都是函数,使用React Hooks,就是调用函数,只不过不同的Hooks(函数)有不同的功能而已.其次,React Hooks只能在函数组件中使用,函数组件也 ...
- React Hooks: useState All In One
React Hooks: useState All In One useState import React, { useState } from 'react'; function Example( ...
- React报错之React hook 'useState' is called conditionally
正文从这开始~ 总览 当我们有条件地使用useState钩子时,或者在一个可能有返回值的条件之后,会产生"React hook 'useState' is called conditiona ...
- React报错之React hook 'useState' cannot be called in a class component
正文从这开始~ 总览 当我们尝试在类组件中使用useState 钩子时,会产生"React hook 'useState' cannot be called in a class compo ...
- React Hooks useState为什么顺序很重要
一个Function Component的state状态整体是作为memoizedState存在FIber中的. function执行时,首先取memoizedState第一个base state,作 ...
- react的登录逻辑
https://blog.csdn.net/qq_36822018/article/details/83028661(先看看这个 https://blog.csdn.net/weixin_342681 ...
- React基本实例
学习React不是一蹴而就的事情,入门似乎也没那么简单.但一切都是值得的. 今天给大家带来一个详细的React的实例,实例并不难,但对于初学者而言,足够认清React的思考和编写过程.认真完成这个实例 ...
- React Hooks用法大全
前言 在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖 ...
- React 新特性学习
1 context 2 contextType 3 lazy 4 suspense 5 memo 6 hooks 7 effect hooks =========== 1 Context 提供了一种方 ...
随机推荐
- Vue dynamic component All In One
Vue dynamic component All In One Vue 动态组件 vue 2.x https://vuejs.org/v2/guide/components-dynamic-asyn ...
- FTP 与 SSH 的安全性对比, 以及FTP,SSH,SFTP,SCP 的关系简单解析!
FTP 与 SSH 的安全性对比? ftP: http://baike.baidu.com/subview/369/6149695.htm TCP/IP协议中,FTP标准命令TCP端口号为21,Por ...
- 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 ...
- Linux shell create file methods
Linux shell create file methods touch, cat, echo, EOF touch $ touch file.py $ touch file1.txt file2. ...
- leetcode bug & 9. Palindrome Number
leetcode bug & 9. Palindrome Number bug shit bug "use strict"; /** * * @author xgqfrms ...
- historyReverser & array reverse
historyReverser & array reverse\ "use strict"; /** * * @author xgqfrms * @license MIT ...
- iPad Pro 如何通过 USB-C 接口外接机械键盘
iPad Pro 如何通过 USB-C 接口外接机械键盘 外接机械键盘 Type-C Dock OK,cnblogs 图片文件不能超过20M https://support.apple.com/zh- ...
- components & slot
components & slot vue https://github.com/vuejs/rfcs/blob/master/active-rfcs/0001-new-slot-syntax ...
- Regular Expressions all in one
Regular Expressions all in one Regular Expressions Cheatsheet https://developer.mozilla.org/en-US/do ...
- vscode Paste Image插件使用
Paste Image 在编写md需要插入图片,这个插件可以将粘贴板的图片保存到本地资源 假如我在/readme.md中编写文档,我需要将粘贴板的图片放在/images/下面,配置两个关键配置即可: ...