1.hooks组件本质
2.useState
①基础用法
import React, { useState } from "react";
import { Button } from 'antd';
/*
useState:React Hook函数之一,目的是在函数组件中使用状态,并且后期基于状态的修改,可以让组件更新
let [num,setNum] = useState(initialValue);
+ 执行useState,传递的initialValue是初始的状态值
+ 执行这个方法,返回结果是一个数组:[状态值,修改状态的方法]
+ num变量存储的是:获取的状态值
+ setNum变量存储的是:修改状态的方法
+ 执行 setNum(value)
+ 修改状态值为value
+ 通知视图更新
函数组件「或者Hooks组件」不是类组件,所以没有实例的概念「调用组件不再是创建类的实例,而是把函数执行,产生一个私有上下文而已」,所以在函数组件中不涉及this的处理!!
*/
const Demo = function Demo() {
let [num, setNum] = useState(0);
const handle = () => {
setNum(num + 10);
};
return <div className="demo">
<span className="num">{num}</span>
<Button type="primary" size="small" onClick={handle}>新增</Button>
</div>;
};
②useState底层机制
函数组件的每一次渲染(或者是更新),都是把函数(重新)执行,产生一个全新的“私有上下文”!
+ 内部的代码也需要重新执行
+ 涉及的函数需要重新的构建{这些函数的作用域(函数执行的上级上下文),是每一次执行DEMO产生的闭包}
+ 每一次执行DEMO函数,也会把useState重新执行,但是:
+ 执行useState,只有第一次,设置的初始值会生效,其余以后再执行,获取的状态都是最新的状态值「而不是初始值」
+ 返回的修改状态的方法,每一次都是返回一个新的
var _state;
function useState(initialValue) {
if (typeof _state === "undefined") {
if(typeof initialValue==="function"){
_state = initialValue();
}else{
_state = initialValue
}
};
var setState = function setState(value) {
if(Object.is(_state,value)) return;
if(typeof value==="function"){
_state = value(_state);
}else{
_state = value;
}
// 通知视图更新
};
return [_state, setState];
}
let [num1, setNum] = useState(0); //num1=0 setNum=setState 0x001
setNum(100); //=>_state=100 通知视图更新
// ---
let [num2, setNum] = useState(0); //num2=100 setNum=setState 0x002
一个有关底层机制的例子
const Demo = function Demo() {
let [num, setNum] = useState(0);
const handle = () => {
setNum(100);
setTimeout(() => {
console.log(num); // 0
}, 2000);
};
return <div className="demo">
<span className="num">{num}</span>
<Button type="primary"
size="small"
onClick={handle}>
新增
</Button>
</div>;
};
export default Demo;
原理: 定时器中输出为0,因为函数组件的每一次渲染,都是把函数(重新)执行,产生一个全新的“私有上下文”! 涉及的函数需要重新的构建{这些函数的作用域(函数执行的上级上下文),是每一次执行DEMO产生的闭包}
执行setNum(100)使得num变为100,Demo会直接重新执行形成下图中的Demo2,不会等待定时器。2秒之后打印num,会去第一次Demo渲染handle执行形成的上下文中寻找num(下图中的Demo1),此时num是0。
useState细节知识和同步异步
③ 更新多个状态
/*
执行一次useState:把需要的状态信息都放在对象中统一管理
+ 执行setState方法的时候,传递的是啥值,就把状态“整体”改为啥值
setState({
supNum: state.supNum + 1
})
=> 把状态值修改为 {supNum:11} ,oppNum成员就丢失了
=> 并不会像类组件中的this.setState一样,不支持部分状态的更新
+ 应该改为以下的处理方案(官方不推荐)
setState({
...state, //在修改值之前,先把原有的所有状态,都展开赋值给新对象,再去修改要改动的那一项值即可
supNum: state.supNum + 1
});
*/
const Vote = function Vote(props) {
let [state, setState] = useState({
supNum: 10,
oppNum: 5
});
const handle = (type) => {
if (type === 'sup') {
setState({
...state,
supNum: state.supNum + 1
});
return;
}
setState({
...state,
oppNum: state.oppNum + 1
});
};
return <div className="vote-box">
<div className="header">
<h2 className="title">{props.title}</h2>
<span className="num">{state.supNum + state.oppNum}</span>
</div>
<div className="main">
<p>支持人数:{state.supNum}人</p>
<p>反对人数:{state.oppNum}人</p>
</div>
<div className="footer">
<Button type="primary" onClick={handle.bind(null, 'sup')}>支持</Button>
<Button type="primary" danger onClick={handle.bind(null, 'opp')}>反对</Button>
</div>
</div>;
};
/* 官方建议是:需要多个状态,就把useState执行多次即可 */
const Vote = function Vote(props) {
let [supNum, setSupNum] = useState(10),
[oppNum, setOppNum] = useState(5);
const handle = (type) => {
if (type === 'sup') {
setSupNum(supNum + 1);
return;
}
setOppNum(oppNum + 1);
};
return <div className="vote-box">
<div className="header">
<h2 className="title">{props.title}</h2>
<span className="num">{supNum + oppNum}</span>
</div>
<div className="main">
<p>支持人数:{supNum}人</p>
<p>反对人数:{oppNum}人</p>
</div>
<div className="footer">
<Button type="primary" onClick={handle.bind(null, 'sup')}>支持</Button>
<Button type="primary" danger onClick={handle.bind(null, 'opp')}>反对</Button>
</div>
</div>;
};
④更新队列机制
handle执行,修改三个状态x/y/z,但是RENDER渲染只执行了一次(第一次执行Demo会输出一次除外),说明setX/setY/setZ是异步的
import React, { useState } from "react";
import { Button } from 'antd';
import './Demo.less';
import { flushSync } from 'react-dom';
const Demo = function Demo() {
console.log('RENDER渲染');
let [x, setX] = useState(10),
[y, setY] = useState(20),
[z, setZ] = useState(30);
// flushSync可以将异步立马执行,变同步
// const handle = () => {
// flushSync(() => {
// setX(x + 1);
// setY(y + 1);
// });
// setZ(z + 1);
// };
const handle = () => {
setX(x + 1);
console.log(x) // 10 不是 11,与setX是同步异步没关系,变量作用域链往上找
setY(y + 1);
setZ(z + 1);
};
return <div className="demo">
<span className="num">x:{x}</span>
<span className="num">y:{y}</span>
<span className="num">z:{z}</span>
<Button type="primary"
size="small"
onClick={handle}>
新增
</Button>
</div>;
};
export default Demo;
setState是异步的
放在哪里执行没关系,都是异步的,即便放在定时器中也是
想同步更新怎么办,比如让setX和setY同步执行,用flushSync把setX和setY包起来
例子:渲染几次
渲染1次,x: 11
修改 -->
只渲染一次,点击新增后不会更新视图(不会打印渲染),因为useState做了优化机制,前后修改的值一样,不会触发更新。
【usestate自带了性能优化的机制: 每一次修改状态值的时候,会拿最新要修改的值和之前的状态值做比较「基于bject.is作比较」如果发现两次的值是一样的,则不会修改状态,也不会让视图更新可以理解为: 类似于Pureomponent,在shouldComponentupdate中做了浅比较和优化】
修改 --> 不是更新10次,实际更新2次(点击按钮后)【理论上点击按钮后应该只执行一次,因为x=11,值相同不会再执行了】
修改 -->
import React, { useState } from "react";
import { Button } from 'antd';
import './Demo.less';
import { flushSync } from 'react-dom';
/*
useState自带了性能优化的机制:
+ 每一次修改状态值的时候,会拿最新要修改的值和之前的状态值做比较「基于Object.is作比较」
+ 如果发现两次的值是一样的,则不会修改状态,也不会让视图更新「可以理解为:类似于PureComponent,在shouldComponentUpdate中做了浅比较和优化」
*/
// 需求:让函数只更新一次,但是最后的结果是20
const Demo = function Demo() {
console.log('RENDER渲染');
let [x, setX] = useState(10);
const handle = () => {
for (let i = 0; i < 10; i++) {
setX(prev => {
// prev:存储上一次的状态值
console.log(prev);
return prev + 1; //返回的信息是我们要修改的状态值
});
}
};
return <div className="demo">
<span className="num">x:{x}</span>
<Button type="primary"
size="small"
onClick={handle}>
新增
</Button>
</div>;
};
export default Demo;
执行原理
⑤设置初始值时使用惰性
useState中传入的函数只在第一次渲染组件时触发,这是用来计算初始值的
import React, { useState } from "react";
import { Button } from 'antd';
import './Demo.less';
const Demo = function Demo(props) {
// 我们需要把基于属性传递进来的x/y,经过其他处理的结果作为初始值
// 此时我们需要对初始值的操作,进行惰性化处理:只有第一次渲染组件处理这些逻辑,以后组件更新,这样的逻辑就不要再运行了!!
let [num, setNum] = useState(() => {
let { x, y } = props,
total = 0;
for (let i = x; i <= y; i++) {
total += +String(Math.random()).substring(2);
}
return total;
});
const handle = () => {
setNum(1000);
};
return <div className="demo">
<span className="num">{num}</span>
<Button type="primary"
size="small"
onClick={handle}>
新增
</Button>
</div>;
};
export default Demo;