day-076-seventy-six-20230523-Hooks组件useState()-Hooks组件useEffect()-Hook函数组件说明
Hooks组件useState()
React Hooks 组件化开发
-
React 组件分类
-
函数组件
-
不具备
状态、ref、周期函数等内容,第一次渲染完毕后,无法基于组件内部的操作来控制其更新,因此称之为静态组件! -
但是具备属性及插槽,父组件可以控制其重新渲染!
-
渲染流程简单,渲染速度较快!
-
基于
FP函数式编程思想设计,提供更细粒度的逻辑组织和复用!- FP-
Funtional Programming-函数式编程思想。 - oop-面向对象编程思想。
- pop-面向过程。
- FP-
-
-
类组件
- 具备
状态、ref、周期函数、属性、插槽等内容,可以灵活的控制组件更新,基于钩子函数也可灵活掌控不同阶段处理不同的事情! - 渲染流程繁琐,渲染速度相对较慢!
- 基于
OOP面向对象编程思想设计,更方便实现继承等!
- 具备
-
-
React Hooks组件,就是基于React中新提供的Hook函数,可以让函数组件动态化!
Hook 函数概览
-
Hook是React16.8的新增特性!并且只能运用到函数组件中!- Hook
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VZtMC5FY-1684857369285)(./ReactHooks的所有HookAPI.jpg)]
-
React Hooks组件化开发:-
2023前端当下和未来的主流编程思想:
函数式编程FP,也叫去面向对象化。 -
在
React开发中,目前最常用的组件类型,应该是函数式组件,类组件偶尔使用。 -
而
React提供的各种HookAPI,就是让函数组件可以像类组件一样,具备状态、周期函数、Ref等操作,让函数组件从静态化转为动态化。 -
所有的HookAPI特点:-
都是以
useXxx的格式命名的。 -
只能用在
函数组件中,在类组件中用不了。- 类组件中也没必要用。
-
-
在函数式组件内部中,
this为undefined。
-
-
HookAPI:
-
useState():在函数组件中使用状态,并且提供修改状态让视图更新的办法。-
let [xxx,setXxx] = useState(初始值)-
每一次执行
useState()就创建一个状态。- 如果需要创建多个状态,则可以让其执行多次,或者使用
useReducer()这个Hook函数。
- 如果需要创建多个状态,则可以让其执行多次,或者使用
-
useState()执行的返回结果是一个数组:[当前状态值,修改状态并让视图更新的函数]。- 声明一个
叫做xxx的变量,用于存储状态值。 setXxx(值):把状态值修改为传递的值,并且让组件更新。
- 声明一个
-
-
-
-
Hook组件与类组件的对比:
-
样式组件
/src/views/VoteBoxStyle.jsx:import styled from "styled-components" const VoteBoxStyle = styled.div` box-sizing: border-box; margin: 20px auto; padding:10px 20px; width: 300px; border: 1px solid #DDD; .title{ display: flex; justify-content: space-between; align-items: center; line-height: 50px; font-size: 18px; border-bottom:1px dashed #DDD; span{ color: #ff4d4f; } } .main-box{ padding: 10px 0; p{ font-size: 14px; line-height: 30px; } } .footer-box{ .ant-btn{ margin-right: 10px; } } ` export default VoteBoxStyle -
Hook组件
/src/views/VoteBox.jsx:import React, { useState } from "react"; import { Button } from "antd"; import VoteBoxStyle from "./VoteBoxStyle"; export default function VoteBox() { // console.log("this", this);//undefined; // console.log(useState(6)); let [supNum, setSupNum] = useState(6); let [oppNum, setOppNum] = useState(2); let total = supNum + oppNum; let ratio = `--`; if (total > 0) { ratio = `${((supNum / total) * 100).toFixed(2)}%`; } // 定义普通函数; const handle = (type) => { if (type === `sup`) { setSupNum(supNum + 1) return } setOppNum(oppNum + 1) } return ( <VoteBoxStyle> <h2 className="title"> React其实也不难! <span>{total}</span> </h2> <div className="main-box"> <p>支持人数:{supNum} 人</p> <p>反对人数:{oppNum} 人</p> <p>支持比率:{ratio}</p> </div> <div className="footer-box"> <Button type="primary" onClick={handle.bind(null, `sup`)}> 支持 </Button> <Button type="primary" danger onClick={handle.bind(null, `opp`)}> 反对 </Button> </div> </VoteBoxStyle > ); }- Hook组件中关于useState()中的Xxx与setXxx在更新和渲染时内存变动.jpg
- 主要用到作用域链、闭包。
-
类组件
/src/views/VoteBox.jsx:import React from "react" import { Button } from 'antd' import VoteBoxStyle from "./VoteBoxStyle" console.log(React); // 基于类组件完成需求: export default class VoteBox extends React.Component { state = { supNum: 6, oppNum: 2, } handle = (type) => { let { supNum, oppNum } = this.state; if (type === `sup`) { this.setState({ supNum: supNum + 1 }) return } this.setState({ oppNum: oppNum + 1 }) } render() { let { supNum, oppNum } = this.state; let total = supNum + oppNum; let ratio = `--`; if (total > 0) { ratio = `${((supNum / total) * 100).toFixed(2)}%` } return <VoteBoxStyle> <h2 className="title"> React其实也不难! <span>{total}</span> </h2> <div className="main-box"> <p>支持人数:{supNum} 人</p> <p>反对人数:{oppNum} 人</p> <p>支持比率:{ratio}</p> </div> <div className="footer-box"> <Button type="primary" onClick={this.handle.bind(null,`sup`)}>支持</Button> <Button type="primary" danger onClick={this.handle.bind(null,`opp`)}>反对</Button> </div> </VoteBoxStyle> } }- 主要用到面向对象,this指向,原型链。
-
模拟useState的源码
// 模拟useState的源码
let state //这个是用于存储当前状态值的。
const useState = function useState(initialValue){
// 只有第一次执行useState时,才会给状态赋值初始值。
if(typeof state ==='undefined'){
state = initialValue
}
// 修改状态的函数
const change = (value)=>{
state = value
// 让视图更新
// ...
}
return [state,change]
}
-
也就是更新时的
useState(initialValue),不会再走初始值了。-
但是在更新执行那个函数时,依旧会执行Hook函数,只是这个Hook函数返回的值变化了。
- 也就是说:调用的Hook方法该重新执行还是重新执行。
import React, { useState } from "react"; import { Button } from "antd"; import VoteBoxStyle from "./VoteBoxStyle"; export default function VoteBox() { let [supNum, setSupNum] = useState(6);//第一次渲染时,会执行这个函数`useState(6)`,返回的值为[6,修改状态值的函数]。而到了非第一次渲染的更新时,依旧还是会执行`let [supNum, setSupNum] = useState(6)`,只是此时`useState(6)`返回的值变成了`[上一次传递给函数的状态值,修改当前状态值的函数]` return ( <h2 className="title"> React其实也不难! <span>{supNum}</span> </h2> ); }
-
函数组件的闭包
-
函数组件的每一次渲染和更新,都是把函数重新执行,产生一个新的闭包,多次调用组件并多次渲染,都是在自己的闭包中,进行相关的处理,相互之间不会有影响。
-
每一次函数执行,props始终都在,都是父组件传的属性值。
-
每一次函数执行,函数中的代码都会重新自上而下执行一遍。
-
调用的Hook方法该重新执行还是重新执行。
-
如果遇到的是创建函数:则每个闭包中都会重新创建一个新的函数,而此函数的作用域就是当前这个闭包。
- 新创建的函数依旧还是会被重新创建,得到一个新的堆内存地址。
-
如果是创建私有变量:则每个闭包中都有这个变量,用于存储闭包作用域中属于自己的值。
-
如果是useState:则第一次渲染会让状态等于初始值,后期再更新的时候,状态等于最新状态值。
-
…
-
-
如果要判断某个变量是什么值,需要缕清楚其作用域链(或者是其属于那个闭包),才能知道值具体是多少!
-
import React, { useState } from "react";
import { Button } from "antd";
import VoteBoxStyle from "./VoteBoxStyle";
export default function VoteBox() {
// console.log("this", this);//undefined;
// console.log(useState(6));
let [supNum, setSupNum] = useState(6);
let [oppNum, setOppNum] = useState(2);
let total = supNum + oppNum;
let ratio = `--`;
if (total > 0) {
ratio = `${((supNum / total) * 100).toFixed(2)}%`;
}
// 定义普通函数;
const handle = (type) => {
if (type === `sup`) {
setSupNum(supNum + 1)
setTimeout(()=>{
console.log(supNum);//依旧是当前闭包中的值。
})
return
}
setOppNum(oppNum + 1)
}
return (
<VoteBoxStyle>
<h2 className="title">
React其实也不难!
<span>{total}</span>
</h2>
<div className="main-box">
<p>支持人数:{supNum} 人</p>
<p>反对人数:{oppNum} 人</p>
<p>支持比率:{ratio}</p>
</div>
<div className="footer-box">
<Button type="primary" onClick={handle.bind(null, `sup`)}>
支持
</Button>
<Button type="primary" danger onClick={handle.bind(null, `opp`)}>
反对
</Button>
</div>
</VoteBoxStyle >
);
}
组件中多个状态和处理
-
useState中多个状态的处理:
-
官方推荐:需要多个状态,则执行useState,创建多个状态及修改状态的方法,让每个状态单独管理。
// 多状态的处理-多次useState()。 import React, { useState } from "react"; import { Button } from "antd"; import VoteBoxStyle from "./VoteBoxStyle"; export default function VoteBox() { let [supNum, setSupNum] = useState(6); let [oppNum, setOppNum] = useState(2); // 定义普通函数; const handle = (type) => { if (type === `sup`) { setSupNum(supNum + 1) return } setOppNum(oppNum + 1) } return ( <VoteBoxStyle> <h2 className="title"> React其实也不难! <span>{supNum + oppNum}</span> </h2> <div className="main-box"> <p>支持人数:{supNum} 人</p> <p>反对人数:{oppNum} 人</p> </div> <div className="footer-box"> <Button type="primary" onClick={handle.bind(null, `sup`)}> 支持 </Button> <Button type="primary" danger onClick={handle.bind(null, `opp`)}> 反对 </Button> </div> </VoteBoxStyle > ); }-
好处:
- 状态分离,增删都方便处理。
-
-
当前也可以执行一次useState,只不过创建的这个状态,需要是一个对象,对象中包含了需要的其它状态值。
// 单状态的处理-单次调用useState()-传递的为对象。 import React, { useState } from "react"; import { Button } from "antd"; import VoteBoxStyle from "./VoteBoxStyle"; export default function VoteBox() { let [state, setState] = useState({ supNum: 6, oppNum: 2 }); const handle = (type) => { if (type === `sup`) { setState({ ...state, supNum: state.supNum + 1 }) return } setState({ ...state, oppNum: state.oppNum + 1 }) } return ( <VoteBoxStyle> <h2 className="title"> React其实也不难! <span>{state.supNum + state.oppNum}</span> </h2> <div className="main-box"> <p>支持人数:{state.supNum} 人</p> <p>反对人数:{state.oppNum} 人</p> </div> <div className="footer-box"> <Button type="primary" onClick={handle.bind(null, `sup`)}> 支持 </Button> <Button type="primary" danger onClick={handle.bind(null, `opp`)}> 反对 </Button> </div> </VoteBoxStyle > ); }-
但是这种方式我们不推荐:
-
基于useState获取的修改状态方法,setXxx,在修改状态值的时候,不支持部分状态的更改。
setState({ supNum: state.supNum + 1 })- 只有React.prototype.setState才支持部分状态更改。
-
setXxx()中传递什么值,就把状态值整体改为什么。
setState({ supNum: state.supNum + 1 })//会直接用这个`{ supNum: state.supNum + 1 }的内存地址`把旧的state对象的内存地址改了。也就是说,旧state对象被完全修改了。-
如何解决?
-
把要修改的值浅拷贝一份。
setState({ ...state,supNum: state.supNum + 1})-
在修改成为新的状态对象之前,先把现有的状态信息浅拷贝一份,在修改的时候,没改的用原始值,改变的地方用用新改的值。
setState({ ...state,//把要修改的值浅拷贝一份。 supNum: state.supNum + 1 })
-
-
-
-
-
-
隋性初始state值
-
如果状态的初始值需要计算,而且是经过复杂计算、消耗很多性能算出来的。而且只需要第一次使用,不想在后续更新时再把逻辑走一遍。
// 隋性初始化初始状态值-正常的用非函数的值来初始化。 import React, { useState } from "react"; import { Button } from "antd"; import VoteBoxStyle from "./VoteBoxStyle"; export default function VoteBox() { // 如果状态的初始值需要计算,而且是经过复杂计算、消耗很多性能算出来的。而且只需要第一次使用。 let ran = Math.round(Math.random() * 9 + 1)//每次更新时都会执行一遍。 console.log('ran',ran);//每次更新时都会执行一遍。 let [supNum, setSupNum] = useState(ran); let [oppNum, setOppNum] = useState(2); // 定义普通函数; const handle = (type) => { if (type === `sup`) { setSupNum(supNum + 1) return } setOppNum(oppNum + 1) } return ( <VoteBoxStyle> <h2 className="title"> React其实也不难! <span>{supNum + oppNum}</span> </h2> <div className="main-box"> <p>支持人数:{supNum} 人</p> <p>反对人数:{oppNum} 人</p> </div> <div className="footer-box"> <Button type="primary" onClick={handle.bind(null, `sup`)}> 支持 </Button> <Button type="primary" danger onClick={handle.bind(null, `opp`)}> 反对 </Button> </div> </VoteBoxStyle > ); }-
可以发现,初始化状态值所用的代码每次更新时都会执行一遍。
- 但是执行后的结果,在更新时都不再被需要了。造成性能浪费。
-
解决方案:执行useState()初始化状态时,传递给useState()的是一个函数。
-
此函数只对第一次执行useState(),并把返回值,作为状态的初始值。后续更新时,该函数都不再执行。
// 隋性初始化初始状态值-基于函数的方式处理。 import React, { useState } from "react"; import { Button } from "antd"; import VoteBoxStyle from "./VoteBoxStyle"; export default function VoteBox() { let [supNum, setSupNum] = useState(() => { // 如果状态的初始值需要计算,而且是经过复杂计算、消耗很多性能算出来的。而且只需要第一次使用,我们基于函数的方式处理。 //此函数只对第一次执行useState(),并把返回值,作为状态的初始值。 let ran = Math.round(Math.random() * 9 + 1) console.log('ran', ran); //... return ran }); let [oppNum, setOppNum] = useState(2); // 定义普通函数; const handle = (type) => { if (type === `sup`) { setSupNum(supNum + 1) return } setOppNum(oppNum + 1) } return ( <VoteBoxStyle> <h2 className="title"> React其实也不难! <span>{supNum + oppNum}</span> </h2> <div className="main-box"> <p>支持人数:{supNum} 人</p> <p>反对人数:{oppNum} 人</p> </div> <div className="footer-box"> <Button type="primary" onClick={handle.bind(null, `sup`)}> 支持 </Button> <Button type="primary" danger onClick={handle.bind(null, `opp`)}> 反对 </Button> </div> </VoteBoxStyle > ); }
-
-
useState()返回的数组中的修改值函数setXxx()的异步更新机制
import { useState } from "react";
import { Button } from "antd";
export default function Demo() {
console.log('render')
let [x, setX] = useState(10);
let [y, setY] = useState(20);
let [z, setZ] = useState(30);
const handle = () => {
setX(x + 1);
setY(y + 1);
setZ(z + 1);
};
return (
<div className="demo" style={{ padding: "50px" }}>
<p>
x:{x}-y:{y}-z:{z}
</p>
<Button type="primary" size="small" onClick={handle}>
按钮
</Button>
</div>
);
}
-
基于useState创建状态,会拿到修改状态的方法setXxx()。
-
在React18中,执行setXxx()是异步去修改状态和让视图更新的。
-
底层机制:updater更新队列机制:
- 当然也可以基于flushSync让其变为类似于同步的效果(立即刷新渲染队列)!
-
不论修改状态是同步还是异步,此处获取的x的值,永远都不会是最新修改的。用的都是现在闭包中的值。最新修改的状态值,只能在下一个闭包中获取!
-
即便把修改状态的操作,基于flushSync()处理了,也仅仅是让其立即更新渲染一次,在它的下面,依然无法获取最新修改的状态值。
-
而在类组件中:
-
我们还可以基于
this.setState()中的callback()函数,通过this.state.xxx获取最新状态值。this.setState({x:x+1},()=>{ //this.state可以拿到最新的x。 console.log(this.state.x); }) -
或者基于
flushSync()把状态修改变为类似于同步效果,然后在flushSync()下面,就可以获取最新状态值。this.setState({x:x+1},()=>{ //this.state可以拿到最新的x。 console.log(this.state.x); })
-
import { useState } from "react"; import { Button } from "antd"; import { flushSync } from "react-dom"; export default function Demo() { console.log("render"); //只打印一次,证明setXxx()是异步修改状态的。 let [x, setX] = useState(10); let [y, setY] = useState(20); let [z, setZ] = useState(30); console.log("新一次渲染的一次闭包中的x", x); const handle = () => { flushSync(() => { setX(x + 1);//会先更新,但下方的x依旧是旧的,只有新的闭包中的x才是本次闭包操作中的x+1; }) setY(y + 1); setZ(z + 1); console.log("点击后x", x);//不论修改状态是同步还是异步,此处获取的x的值,永远都不会是最新修改的。用的都是现在闭包中的值。最新修改的状态值,只能在下一个闭包中获取!即便把修改状态的操作,基于flushSync处理了,也仅仅是让其立即更新渲染一次,在它的下面,依然无法获取最新修改的状态值。 // 而在类组件中,我们还可以基于this.setState中的callback函数,通过this.state.xxx获取最新状态值。或者基于flushSync()处理等。 }; return ( <div className="demo" style={{ padding: "50px" }}> <p> x:{x}-y:{y}-z:{z} </p> <Button type="primary" size="small" onClick={handle}> 按钮 </Button> </div> ); } -
-
-
在React16中,setXxx有时候是异步操作,有时候是同步操作。
- 在其它异步操作中,它本身是同步的。
- 在非异步操作中,它本身是异步操作的!
-
-
在同步异步性的处理上,和类组件中的this.setState()/this.forceUpdate()是保持一致的!
useState()返回的数组中的修改值函数setXxx()的累计更新
-
直接调用setXxx(),不能直接修改值。
-
并且在同步的for循环过程中,x依旧都是旧闭包作用域中的x。
import { useState } from "react"; import { Button } from "antd"; import { flushSync } from "react-dom"; export default function Demo() { console.log("render"); //只打印一次,证明setXxx()是异步修改状态的。 let [x, setX] = useState(10); let [y, setY] = useState(20); let [z, setZ] = useState(30); console.log("新一次渲染的一次闭包中的x", x); const handle = () => { for (let i = 0; i < 10; i++) { setX(x +1); } }; return ( <div className="demo" style={{ padding: "50px" }}> <p> x:{x}-y:{y}-z:{z} </p> <Button type="primary" size="small" onClick={handle}> 按钮 </Button> </div> ); }
-
-
基于函数来做到更新:
import { useState } from "react"; import { Button } from "antd"; import { flushSync } from "react-dom"; export default function Demo() { console.log("render"); //只打印一次,证明setXxx()是异步修改状态的。 let [x, setX] = useState(10); let [y, setY] = useState(20); let [z, setZ] = useState(30); console.log("新一次渲染的一次闭包中的x", x); const handle = () => { for (let i = 0; i < 10; i++) { setX(prev => { return prev + 1 }); } }; return ( <div className="demo" style={{ padding: "50px" }}> <p> x:{x}-y:{y}-z:{z} </p> <Button type="primary" size="small" onClick={handle}> 按钮 </Button> </div> ); } -
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l4Sg3kmD-1684857369287)(./useState()]返回的数组中的修改值函数setXxx()的累计更新.jpg)
useState()返回的数组中的修改值函数setXxx()的新旧值比对优化
-
基于setXxx修改状态,函数组件内部做了优化机制:
-
基于Object.is()方法,对新老状态值做对比,如果发现是一致的,则视图不更新。
- 类似于设置了优化处理的shouldComponentUpdate()钩子函数。
import { useState } from "react"; import { Button } from "antd"; export default function Demo() { console.log("render"); let [x, setX] = useState(10); let [y, setY] = useState(20); let [z, setZ] = useState(30); const handle = () => { console.log("click"); setX(10);//执行了该函数,但不会更新视图。可以看作内部用了Object.is()做了比对,如果是true,就不更新视图。 }; return ( <div className="demo" style={{ padding: "50px" }}> <p> x:{x}-y:{y}-z:{z} </p> <Button type="primary" size="small" onClick={handle}> 按钮 </Button> </div> ); }
-
Hooks组件useEffect()
在函数组件中使用生命周期函数
-
useEffect()/useLayoutEffect() 在函数组件中使用生命周期函数
-
useEffect(callback)-
在第一次渲染完毕和每一次组件更新完毕后,触发callback执行。
-
等价于componentDidMount()和componentDidUpdate()。
import { useState, useEffect, useLayoutEffect } from "react"; import { Button } from "antd"; export default function Demo() { console.log('render'); let [x, setX] = useState(10); let [y, setY] = useState(20); let [z, setZ] = useState(30); const handle = () => { setX(x + 1); setY(y + 1); setZ(z + 1); console.log('click'); }; useEffect(() => { console.log('AA'); }) return ( <div className="demo" style={{ padding: "50px" }}> <p> x:{x}-y:{y}-z:{z} </p> <Button type="primary" size="small" onClick={handle}> 按钮 </Button> </div> ); }
-
-
useEffect(callback,[])-
只在第一次渲染完毕后触发执行。
-
等价于componentDidMount()。
useEffect(() => { console.log('componentDidMount'); }, [])
-
-
-
useEffect(callback,[依赖的状态])-
在第一次渲染完毕后触发执行,以及依赖的状态发生改变后触发执行。
-
类似于Vue2中watch监听器。
// 类似于监听器,监听了x与y。 useEffect(() => { console.log('@3 第一次渲染完毕 与 x或y状态改变'); }, [x, y])
-
-
-
useEffect(返回一个函数的callback,[])组件销毁之前做些事情。-
给
useEffect()传递的callback函数,其内部可以不写返回值。- 如果设置返回值,则
必须返回一个函数!
useEffect(() => { return () => { console.log('@4 组件销毁之前'); } }, [])useEffect(() => { //第一次渲染完毕做的事情; return () => { //组件销毁之前做的事情; } }, []) - 如果设置返回值,则
-
-
不写数组或者设置依赖项:
-
useEffect(返回一个函数A的callback)但凡有状态更新,会先把上一次闭包中callback返回的函数A执行,之后才执行本次闭包中的返回一个函数A的callback。 -
useEffect(返回一个函数A的callback,[依赖的状态])依赖的状态发生改变,会先把上一次闭包中callback返回的函数A执行,之后才执行本次闭包中的返回一个函数A的callback。useEffect(() => { return ()=>{ //但凡有状态更新,或者依赖的状态发生改变,在组件更新之前,会先把此函数执行。-->通俗理解:产生新的闭包之前,处理的事情。 } },不写数组或者设置依赖项)
-
-
-
useEffect()内部回调处理机制
-
每一次函数组件更新/渲染,除了产生一个新的闭包外,还会生成一个新的effect链表!
-
effect链表:存储基于useEffect创建的callback(),或者是callback返回的函数,以及依赖的状态。
-
组件渲染中:
- 每次遇到useEffect(callback,依赖项/空数组/没有),基于MountEffect方法,把依赖项和callback放在effect链表中。
-
组件渲染完毕:会基于UpdateEffect方法通知effect链表中的callback,按照各自的特征,去逐一执行!
-
-
callback执行的时候,其宿主上下文,就是本次视图渲染产生的闭包!
-
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IQTvnLlp-1684857369288)(./useEffect()]内部回调处理机制.jpg)
Hook函数组件说明
-
Hook函数组件的每一次更新和渲染,都是让函数重新执行。
-
产生新的私有上下文,代码会自上而下执行一遍,最后返回新的VirtualDOM。
-
如果是第一次渲染:
-
当前函数组件当前执行时产生了私有上下文1。 -
当前函数组件当前执行时产生的私有上下文1中的this一般是undefined。- 因为经过React处理过了。
-
通过
当前函数组件第一个形参得到父组件传递的props。 -
通过执行
useState(),得到初始状态值和修改该初始状态值的函数。-
返回的
初始状态值是第一次传入useState()函数内的值。 -
虽然看起来初始状态值没什么变化,但React内部已经帮你做了很多操作。
-
比如会返回一个
修改状态值的函数,以及记录了当前的作用域、当前所在函数、当前父节点之类的操作。- 同时,在调用修改状态值的函数时,还会重新执行一遍当前函数,产生的虚拟DOM也可以在父组件中被更新。
-
-
-
自上而下执行一遍,中间可能会创建一些
局部函数,会返回一个虚拟DOM,而父组件会将它缓存下来,并渲染到页面上。- 这些创建的局部函数中,上级作用域链是
当前函数组件当前执行时产生的私有上下文1,也就意味着,如果它内部找不到值,会在当前函数组件当前执行时产生的私有上下文1中查找。 - 同时父组件还会缓存当前返回出去的虚拟DOM,以便下次更新时进行比较。
- 这些创建的局部函数中,上级作用域链是
-
该组件中执行的方法和使用的值,基本上都是
当前函数组件当前执行时产生的私有上下文1中的值或得到的函数或新创建的函数。
-
-
如果是后续更新,重新自上而下执行一遍,但Hook组件中提供的Hook函数,可以让返回值变动,进而让函数返回虚拟DOM产生新的变化。
-
当前函数组件当前执行时产生了私有上下文2。 -
当前函数组件当前执行时产生的私有上下文2中的this一般是undefined。- 因为经过React处理过了。
-
通过第一个形参得到父组件传递的props。
- 函数组件的每次更新,都会把第一次渲染时获取的属性,在后期每一次执行中,传递进来。
-
通过执行useState(),得到最新状态值和修改该最新状态值的函数。
-
返回的最新状态值是上一次传入函数内的值。
-
虽然看起来初始状态值没什么变化,但React内部已经帮你做了很多操作。
- 比如会返回一个修改状态值的函数,以及记录了当前的作用域、当前所在函数、当前父节点、当前props之类的操作,同时,在调用修改状态值的函数时,还会重新执行一遍当前函数,产生的虚拟DOM也可以在父组件中被更新。
-
-
自上而下执行一遍,中间可能会创建一些局部函数,会返回一个虚拟DOM,而父组件会将它缓存下来,并渲染到页面上。
- 这些创建的局部函数中,上级作用域链是
当前函数组件当前执行时产生的私有上下文1,也就意味着,如果它内部找不到值,会在当前函数组件当前执行时产生的私有上下文1中查找。 - 同时父组件还会缓存当前返回出去的虚拟DOM,以便下次更新时进行比较。
- 这些创建的局部函数中,上级作用域链是
-
该组件中执行的方法和使用的值,基本上都是
当前函数组件当前执行时产生的私有上下文1中的值或得到的函数或新创建的函数。import { useState, useEffect, useLayoutEffect } from 'react' import { Button } from 'antd' export default function Demo() { let [x, setX] = useState(10), [y, setY] = useState(20), [z, setZ] = useState(30) useEffect(() => { console.log('@1 第一次渲染完毕/更新完毕', x, y, z) return () => { console.log('@5 组件更新之前', x, y, z); } }) useEffect(() => { console.log('@2 第一次渲染完毕', x, y, z) setTimeout(() => { console.log('哈哈哈', x, y, z) //获取的永远是闭包1中的状态值 10/20/30 }, 5000) return () => { console.log('@4 组件销毁之前', x, y, z); } }, []) useEffect(() => { console.log('@3 第一次渲染完毕 & x/y状态改变', x, y, z) }, [x, y]) return <div className="demo" style={{ padding: '50px' }}> <p>{x} - {y} - {z}</p> <Button type='primary' size='small' onClick={() => setX(x + 1)}>修改X</Button> <Button type='primary' size='small' onClick={() => setY(y + 1)}>修改Y</Button> <Button type='primary' size='small' onClick={() => setZ(z + 1)}>修改Z</Button> </div> }
-
-
-
import { useState, useEffect, useLayoutEffect } from "react";
import { Button } from "antd";
export default function Demo() {
console.log('render');
let [x, setX] = useState(10);
let [y, setY] = useState(20);
let [z, setZ] = useState(30);
// useEffect(() => {
// console.log('@1 第一次渲染完毕 或 更新完毕');
// })
// useEffect(() => {
// return ()=>{
// console.log('@5 组件更新之前');
// }
// })
useEffect(() => {
console.log('@1 第一次渲染完毕 或 更新完毕', x, y, z);
return () => {
console.log('@5 组件更新之前', x, y, z);
}
})
// useEffect(() => {
// console.log('@2 第一次渲染完毕');
// }, [])
// useEffect(() => {
// return () => {
// console.log('@4 组件销毁之前');
// }
// }, [])
// 类似于监听器,监听了x与y。
useEffect(() => {
console.log('@3 第一次渲染完毕 与 x或y状态改变', x, y, z);
}, [x, y])
useEffect(() => {
console.log('@2 第一次渲染完毕', x, y, z);
setTimeout(() => {
console.log(`延时`, x, z, y);//因为该useEffect只在第一次执行,所以依旧是第一次的值。x-10,y-20,z-30;
}, 5000)
return () => {
console.log('@4 组件销毁之前', x, y, z);
}
}, [])
return (
<div className="demo" style={{ padding: "50px" }}>
<p>
x:{x}-y:{y}-z:{z}
</p>
<Button type="primary" size="small" onClick={() => setX(x + 1)}>
修改x
</Button>
<Button type="primary" size="small" onClick={() => setY(y + 1)}>
修改y
</Button>
<Button type="primary" size="small" onClick={() => setZ(z + 1)}>
修改z
</Button>
</div>
);
}