面试必备!
useState:状态管理
useState有两个状态,一个是status,一个是setStatus
setStatus修改数据后,会触发<App/>的re-render
什么是re-render?
re-render:重新渲染,re-render并不意味着dom会更新,re-render是react自身执行一系列的组件自身生命周期,render,虚拟dom之间diff之后,如果dom发生变化,则更新新的dom,如果没有变化则不更新。
以下情况会导致re-render:
- 组件内部state变化:无论这个声明的state在组件的render中是否使用,state的变化都会导致组件自身及其子节点的re-render;
- 组件的props变化:这是站在子组件的角度上得出的结论,而子组件props变化来源于父组件的state,究其根本,还是组件内部state发生了变化;
- 用户交互事件触发或者接口请求数据响应;
- 组件订阅的context发生变化:组件订阅的context发生变化,是由于provider中的state发生了变化,同理,还是因为state发生变化导致的re-render
- 父组件的render:父组件render,子组件就会re-render.除非子组件是一个纯组件(纯组件:即他的props没有发生变化,那么他就不会更新,可以使用React.memo(),React.PureComponent将组件变为纯组件。)
如何避免不必要的re-render?
- 使用类组件PureComponent:引入PureComponent,在需要用到Component的地方修改为PureComponent
import {PureComponent} from 'react'; export class Root extends PureComponent{ ... } //或者直接继承 //export class Root extends React.PureComponent{...}
- 使用函数组件Memo:父组件中修改state时,下面这个子组件如果没有用到这个state,则不会re-render。换言之,react.memo()是校验props中数据的内存地址是否改变来决定是否重新渲染
import { memo } from "react" const Memodemo=({props,children})=>{ console.log('Memodemo') return <> <p>这里用的是memo</p> </> } export default memo(Memodemo)
- 利用高阶组件shouldComponentUpdate:组件在重新渲染之前(也就是虚拟dom对比完毕并生成最终dom之后),会调用该函数,该函数将是否重新渲染的权限交给开发者,若该函数返回true,则更新dom
//下面这个示例,仅在value变化时发生dom更新,否则跳过dom更新 class MyComponent extends React.Copmonent{ shouldComponentUpdate(nextProps,nextState){ return nextProps.value!===this.props.value } } //nextProps:即将传入的新的props //nextState:即将传入的新的state
useEffect:
useEffect接受两个参数,第一个参数是一个函数,必传,可以看作componentDidMount、componentDidUpdate、componentWillUnmount这三个函数的组合
useEffect中第一个参数如果添加了return返回一个函数,切换路由时,就会执行这个return返回的函数,相当于componentWillUnmount。
第二个参数可以不传或者传一个数组
那么useEffect会出现以下四种使用情况:
- 不传递第二个参数,当前组件每次渲染都会执行,包括state的每次更新都会触发useEffect
useEffect( ()=>{console.log('useEffect')} )
- 第二个参数传递空数组:只在挂载和卸载的时候执行
useEffect(()=>{ console.log('useEffect[]') },[])
- 第二个参数传递一个值
useEffect( ()=>{ console.log('当page发生修改的时候,会输出此行'); },[page])
- 第二个参数传递多个值:数组中的任意一个或者多个发生变化,useEffect都会重新运行一次;
useEffect(()=>{ console.log('useEffect'); },[level,level1])
componentDidMount :react在组件添加到屏幕上(挂在)后调用它,一般用于进入页面后,数据初始化
componentDidUpdate:页面中的state或者model中的state定义的变量发生了变化,这个方法就会执行;
componentWillUnmount:组件被移除屏幕(卸载)之前调用
这三个类生命周期和useEffect等同方式,看下面代码:
//类生命周期
class Example extends React.Component{
constructor(props){
super(props);
this.state={
count:0,
dateNow:''
}
}
componentDidMount(){
console.log('当前count=',this.state.count)
this.timeFlag=setInterval(()=>{
this.setState.dateNow=new Date()
})
}
componentDidUpdate(){
console.log('当前更新为count=',this.state.count)
}
componentWillUnmount(){
clearInterval(this.timeFlag)
}
render(){
return(
<div>
<p>当前count={this.state.count}</P>
<p>当前时间={this.state.dateNow}</P>
<button onClick={()=>{this.state.count=this.state.count+1}}>count+1</button>
</div>
)
}
}
//上面的类生命周期代码,等同于下面的代码
import {useState,useEffect} from 'react';
const Example=()=>{
const [count,setCount]=useState(0);
const [dateNow,setDateNow]=useState('');
//下面这一行没有写第二个参数,所以,当state中的变量发生变化时,这里的useEffect就会调用
useEffect(()=>{
console.log('当前count=',count)
})
//这一行只有count发生变化,才会调用useEffect
useEffect(()=>{ console.log('当前更新为count=',count)},[count])
uesEffect(()=>{
const timeFlag=setInterval(()=>{
setDateNow(new Date())
})
return()=>{clearInterval(timeFlag)}
},[])
return(
<div>
<p>当前count={count}</P>
<p>当前时间={dateNow}</P>
<button onClick={()=>{setCount(count+1)}}>count+1</button>
</div>
)
}
useMemo:性能优化工具,主要解决使用react hooks产生的无用渲染的性能问题,类似于vue的computed,可以缓存计算结果,用于跳过昂贵的计算
useMemo使用方式为:useMemo(fn,arr);
第二个参数arr分以下几种情况:
- 不传,即useMemo(fn):每次更新都会重新计算
- arr=空数组[]:只会计算一次;
- arr=[a]:当a发生变化的时候,会重新执行fn;
useMemo的作用时跳过昂贵的计算,那我们看以下几个例子:
//当x或者y发生变化,组件re-render时,a都会进行重新计算,可以看到time的计算时间都是很长的
import { useMemo, useState } from "react"
const MemoPage=()=>{
const [x,setX]=useState(1);
const [y,setY]=useState(1);
console.time('time')
let a=0;
for(var i=0;i<100000000;i++){
a=a+i+y
}
console.timeEnd('time')
return(<>
<div>x={x}</div>
<div>y={y}</div>
<div>x={a}</div>
<div>
<button onClick={()=>setX(x+1)}>x++</button>
<button onClick={()=>setY(y+1)}>y++</button>
</div>
</>)
}
export default MemoPage
//用useMemo返回一个a,无论x还是y发生了修改, x={a}取的都是缓存中的a的值,不会进行重新计算,可以看到下图,time的计算时间变短
import { useMemo, useState } from "react"
const MemoPage=()=>{
const [x,setX]=useState(1);
const [y,setY]=useState(1);
console.time('time')
let a= useMemo(()=>{
let a=0;
for(var i=0;i<100000000;i++){
a=a+i
}
return a
},[])
//上面的[]必须写,如果不写,当x或者y发生修改,则a还是会重新计算
console.timeEnd('time')
return(<>
<div>x={x}</div>
<div>y={y}</div>
<div>x={a}</div>
<div>
<button onClick={()=>setX(x+1)}>x++</button>
<button onClick={()=>setY(y+1)}>y++</button>
</div>
</>)
}
export default MemoPage
//上面的代码a是固定不变的,假如,a是变化的呢?
//下面代码,当x发生变化时,a仍然取缓存,当y发生变化时,则重新计算
import { useMemo, useState } from "react"
const MemoPage=()=>{
const [x,setX]=useState(1);
const [y,setY]=useState(1);
console.time('time')
let a= useMemo(()=>{
let a=0;
for(var i=0;i<100000000;i++){
a=a+i+y
}
return a
},[y])
console.timeEnd('time')
return(<>
<div>x={x}</div>
<div>y={y}</div>
<div>x={a}</div>
<div>
<button onClick={()=>setX(x+1)}>x++</button>
<button onClick={()=>setY(y+1)}>y++</button>
</div>
</>)
}
export default MemoPage
上面是useMemo的主要作用
还有一些扩展作用 ,这些只是能达到效果,不是useMemo的主要作用
import { memo, useMemo, useState ,useRef} from "react";
const Child=memo(({a})=>{
console.log('子组件')
return <><div>子组件</div></>
})
const MemoPage=()=>{
const [x,setX]=useState(1);
const [y,setY]=useState(1);
// let a=['a','b','c'];//直接写这一行,当x或者y发生变化时,子组件还是会重新渲染
let a=useMemo(()=>['a','b','c'],[])//这一行子组件不会重新渲染
// const [a,setA]=useState(['a','b','c'])//这一行子组件也不会重新渲染
// let a=useRef(['a','b','c']);//下面的子组件修改为<Child a={a.current}/>,也能达到一样效果
console.log('父组件')
return(<>
<div>x={x}</div>
<div>y={y}</div>
{/* <div>x={a}</div> */}
<div>
<button onClick={()=>setX(x+1)}>x++</button>
<button onClick={()=>setY(y+1)}>y++</button>
</div>
<Child a={a}/>
</>)
}
export default MemoPage
memo和useMemo的区别和相同点是什么呢?
区别:
- 用途:useMemo是一个hook,用于记忆计算结果,可以在组件重新渲染时避免重新计算某些值,从而提升性能;react.memo是一个高阶组件,用于记忆组件的渲染结果,可以在父组件re-render时避免重新渲染子组件,从而提升性能。
- 使用场景:假设有一个昂贵的计算(简易理解为超大数字计算),可以用useMemo缓存计算结果,当依赖的变量发生变化时,再从新计算;react.memo用于当一个纯函数组件,渲染结果仅仅依赖于props时,react.memo可以避免不必要的重新渲染;
- 语法方面也有所不同;
- 返回值:useMemo返回计算结果值,用户缓存计算后的状态;react.memo翻译一个新的组件。
总结:useMemo用于记忆计算结果,避免重复计算,适用于需要缓存计算结果的场景;
react.memo用于记忆组件的渲染结果,避免重复渲染,适用于纯函数组件,避免不必要的重新渲染。相同点:
- 两者都是为了性能优化,减少不必要的计算或者渲染;
- 两者都接受一个依赖项数组,用于决定是否需要重新计算或者渲染。
useCallback:主要作用是缓存一个回调函数,确保组件在re-render时不会重新创建新的回调函数,优化函数性能。需要搭配React.memo()使用。
重点:useCallback的作用不是阻止函数创建,而是依赖不变的情况下,返回旧函数地址。
import { useCallback, useState } from "react"
const CallPage=()=>{
const [state,setState]=useState(1)
const a=useCallback(()=>{
console.log('a',state)
},[])
const b=()=>{
console.log('b',state)
}
return <>
<button onClick={()=>{
a();
b();
setState(state+1)
}}>btn</button>
</>
}
export default CallPage
比如上面的代码,点击 btn的时候,a(),返回的count不变,但是b()返回的count永远+1
每一个被useCallback的函数都会被加入到useCallback内部管理队列,一个使用了useCallback的函数在组件重新渲染时,会去useCallback内部管理队列中寻找校验依赖是否改变并校验。在这个过程中,寻找指定函数和校验都需要性能,如果滥用useCallback会增加寻找指定函数和校验依赖是否改变两个功能,为项目增加不必要的负担。
什么时候使用useCallback?
import { memo, useCallback, useState } from "react"
const Child1=memo(()=>{
console.log('Child1')
return<></>
})
const Child2=memo((props)=>{
console.log('Child2')
return<></>
})
const Child3=memo((props)=>{
console.log('Child3')
return<></>
})
const CallPage=()=>{
const [state,setState]=useState(1)
const fn=()=>{
console.log('点击btn')
}
const fns=useCallback(()=>{
console.log('点击btn')
},[])
return<>
<button onClick={()=>{
setState(state+1)
}}>btn</button>
<Child1 />
<Child2 fn={fn}/>
<Child3 fn={fns}/>
</>
}
export default CallPage
上面的代码,child1没有传参,当父组件state更新的时候,child1对比到props没有发生变化,于是不更新;
child2,不行,会重新渲染;因为父组件re-render的时候,会重构父组件中的所有函数,也就是所有函数的内存地址发生了变化,在child2中,react.memo检测到props中的数据的栈的地址发生了变化,所以child2也会重新渲染。
child3,传参fns,被useCallback保护着,父组件re-render时,从useCallback的内部管理队列返回旧的内存地址,因此,子组件不会检测到地址变化,也就不会重新渲染。
useRef:创建一个容器
用途:
- 访问dom节点
import { useEffect, useRef } from "react" const Refpage=()=>{ const ref1=useRef(null) useEffect(()=>{ console.log(ref1) },[]) return<> <div ref={ref1}>ref</div> </> } export default Refpage
- 存储任意可变的值
useContext:
还没有写完,慢慢写~~
有需要补充的,麻烦私信我