react-hooks 的 useState 原理以及出现不更新视图的场景和原因?
1. useState 使用demo//App 组件function App() {let [a, setA] = React.useState(0);// 改变a的值const changeA = () => {setA(a + 1);console.log("render");};return (<><div>{a}</div>
1. useState
使用demo
// App 组件
function App() {
let [a, setA] = React.useState(0);
// 改变a的值
const changeA = () => {
setA(a + 1);
console.log("render");
};
return (
<>
<div>{a}</div>
<button onClick={changeA}> +</button>
</>
);
}
render();
// 渲染
function render() {
ReactDOM.render(<App />, document.getElementById("root"));
}
2. 实现效果
效果如下,每次点击加1,同时更新视图:
3.实现原理解析:
1 . 调用 useState() 方法, 传入一个参数(如上demo,假如为数字0), 所以,我需要有个 useState() 方法, 同时导出了 一个数组, 俩个元素, 第一个元素是要渲染的值, 第二个值是一个函数
// 模拟这个场景
function useState(value){
function setState(){}
return [value, setState];
}
2.
useState
内部的setState()
接收这个参数, 第一次初始化的时候,就是默认的值, 但之后的值,每次更新都会通过调用setA()
函数(其实内部走的是setState
方法),同时传入一个新的值,覆盖掉老的值
// 模拟 setA 函数
function useState(value){
// 次函数会接收一个新的值
function setState(newValue){
value = newValue
// 调用更新视图
}
return [value, setState];
}
4. 按以上的思路实现代码
function useState(value) {
function setState(newValue) {
value= newValue;
render();
console.log(newValue);
// 调用更新视图
}
return [value, setState];
}
// App 组件
function App() {
let [a, setA] = useState(0);
// 改变a的值
const changeA = () => {
setA(a + 1);
console.log("render");
};
return (
<>
<div>{a}</div>
<button onClick={changeA}> +</button>
</>
);
}
// 渲染
function render() {
ReactDOM.render(<App />, document.getElementById("root"));
}
render();
效果如下,发现并没有渲染视图, 同时打印发现,结果一直是1, 并没有根据上次的值进行累加
5. 分析没有渲染预想结果的原因
1.分析如下代码,首先第一个原因, 把每次传入进来的新的value的值 虽然进行了保存, 但每次调用
setA()
传入的a+1
值依赖的都是初始值 0,所以每次都会打印1
function useState(value) {
function setState(newValue) {
value= newValue;
render(); // 调用更新视图
console.log(newValue); // 永远是1
}
return [value, setState];
}
2.改写, 首先定义一个全局变量,每次调用时, 先取一下上次最后保存的值, 如果有,就取出来上次的最后一个值,没有的话,就取默认值,也就是这里利用一下闭包
let lastValue;
function useState(value) {
// 如果有,就取出来上次的最后一个值,没有的话,就取默认值
lastValue = lastValue || value;
function setState(newValue) {
// 每次改变都保存更新的值
lastValue = newValue;
render();
// 调用更新视图
}
return [lastValue , setState];
}
6. 改写后实现结果
7. 当使用多个 useState时,数据不冲突的原理
先看上边的demo,自己实现的useState,调用多次,是有bug的, 更新a时, b也会同时更新
7.1 俩按钮的demo事件
import React from "react";
import ReactDOM from "react-dom";
let lastValue;
function useState(value) {
lastValue = lastValue || value;
function setState(newValue) {
lastValue = newValue;
render();
// 调用更新视图
console.log(lastValue);
}
return [lastValue, setState];
}
// App 组件
function App() {
let [a, setA] = useState(0);
let [b, setB] = useState(1);
// 改变a的值
const changeA = () => {
setA(a + 1);
};
// 改变b的值
const changeB = () => {
setB(a + 1);
};
return (
<>
<div>{a}</div>
<button onClick={changeA}> a+</button>
<hr />
<div>{b}</div>
<button onClick={changeB}> b+</button>
</>
);
}
// 渲染
function render() {
ReactDOM.render(<App />, document.getElementById("root"));
}
render();
点a按钮
和b按钮
,都会同时更新,明明是俩变量,但是确同时更新
7.2 分析上边出现的原因,以及怎么解决保证具体是哪个state
分析:
- 怎么让同一个函数的调用,根据各自的参数, 产生对应的值,怎么来区分
- useState 会调用多次,每次怎么知道是谁的值,而lastState ,刚好保存的又是上次对应的自己的值
- 此时,我可以想到怎么区分, 一种是使用对象,或者map,但是用map,或者对象,但这里明显它俩不合适,不知道key怎么定义,还有就是数组,通过索引来找到具体的操作的是哪个 ,无法像下边这样写
- 对象不合适
let index = 0;
let lastValue = {};
function useState(value) {
// 明显的 lastValue = {index:1}, 而我期望的是{"0":1}
lastValue = lastValue[index] || { index: value };
console.log(lastValue);
let curIndex = index;
function setState(newValue) {
lastValue[curIndex] = newValue;
render();
// 调用更新视图
console.log(lastValue);
}
return [lastValue[curIndex++], setState];
}
7.3 利用数组的方式实现
采用数组索引的方式, 大致思路:
- 两个全局变量, 一个用于 将传入的值,存入到数组中,一个是默认的初始索引值0,
- 先取值,如果
数组 [index]
能取到值,就用数组 [index]
, 取不到 就使用用户传入的值- 调用 内部的 闭包函数
setState
时, 让数组 [index] = newValue
新值- 所以每次导出的值的索引都要加1, 使用后置++,以保证再下次再调用时,索引向上递增1,
- 同时,还要保存这个索引,因为它每次都会变
7.4 实现代码
let state = [];
let index = 0;
function useState(value) {
// 拿到本次索引的值
state[index] = state[index] || value;
// 保存本次的index
let cutIndex = index;
function setState(param) {
state[cutIndex] = param;
render();
}
// 每调用一次, 索引再下次再调用时就+1
return [state[index++], setState];
}
function App() {
let [a, setA] = useState(0);
let [b, setB] = useState(1);
const changeA = () => setA(a + 1);
const changeB = () => setB(b + 1);
return (
<>
<div>{a}</div>
<button onClick={changeA}> +</button>
<hr />
<div>{b}</div>
<button onClick={changeB}> +</button>
</>
);
}
function render() {
ReactDOM.render(<App />, document.getElementById("root"));
}
render();
但你每次点击,索引索引再变, 但是视图并没有更新, 此时还需要每次render时都重置下索引
// ...略
function render() {
index = 0;
ReactDOM.render(<App />, document.getElementById("root"));
}
render();
8. 调用了自定义的setXXX 还是不更新视图
通常不更新视图,是因为你的数据原因,基本数据结构类型 不会 出现这种问题的, 引用数据类型根据你写法的不同是会出现这种情况的, 你每次调用自己的 setXXX
方法, 如果传入的是同一个引用的话, 是不会刷新视图,如下:
let obj = { num: 5 };
function App() {
let [a, setA] = React.useState(obj);
// 改变obj的a
const changeA = () => {
obj.num += 1;
console.log(obj.num);
setA(obj);
};
return (
<>
<div>{a.num}</div>
<button onClick={changeA}> +</button>
</>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
虽然数据再变, 但视图没有刷新, 所以对于引用数据类型,必须保证每次传入的不是同一个引用才会引起视图的更新,所以每次都是一个新的引用地址,对于复杂的数据结构,可以使用Object.create(), {},[],Object.assign(),等
处理一下
let obj = { num: 5 };
function App() {
let [a, setA] = React.useState(obj);
// 改变obj的a
const changeA = () => {
setA({ num: a.num + 1 });
};
return (
<>
<div>{a.num}</div>
<button onClick={changeA}> +</button>
</>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
不允许调用 forceUpdate
方法, 因为此方式只能类组件使用,就像 hooks 只能用在函数组件中一样
更多推荐
所有评论(0)