React Hook
useRef
作用
useRef返回一个包含current属性的对象,并且改变此对象的值不会导致重新渲染。
useRef通常用于以下三种场景:
保存一个值
useRef()保存一个值,并在后面的重新渲染中仍能获取当前的值。
复制 import React , {useState , useRef} from 'react' ;
export function App (props) {
const [ val , setVal ] = useState ( 0 )
const s = useRef ( 1 )
const a = {current : 0 }
setInterval (() => {
setVal (val + 1 )
s .current += 1
a .current += 1
console .log (s , a)
} , 3000 );
return (
< div className = 'App' >
< div > {val} </ div >
</ div >
);
}
获取DOM节点
如果要获取真实的DOM节点,那么需要:
ref对象的current属性就指向了该DOM节点,如果节点被删除或不存在,则current为null。
复制 import React , {useState , useRef} from 'react' ;
export function App (props) {
const divRef = useRef ( null )
console .log (divRef)
return (
< div className = 'App' >
< div ref = {divRef}></ div >
</ div >
);
}
避免重新创建
React 会保存一次初始 ref 值,并在下一次渲染时忽略它。
复制 import React , {useState , useRef} from 'react' ;
function createData (){
console .log ( "执行了createData函数" )
return 1
}
export function App (props) {
const [ val , setVal ] = useState ( 0 )
// 每次重新渲染都会执行createData,造成一定的性能浪费
// const a = useRef(createData())
// 可以通过useRef这样来避免重复创建
const a = useRef ( null )
if ( a .current === null ){
a .current = createData ();
}
// 调用setVal来触发重新渲染
setInterval (() => {
setVal (val + 1 );
} , 1000 )
return (
< div className = 'App' >
< div ></ div >
</ div >
);
}
useMemo
useMemo是一个react Hook,它的作用是在重新渲染时缓存计算结果,来提升性能。
避免昂贵的重新计算
某些时候我们可能会有某个计算量非常庞大的函数,这个函数执行起来非常的慢,理想情况下是将这个计算的结果缓存起来,只有当某些变量改变时才执行函数重新计算,这个时候就可以使用useMemo。这其实和vue的computed相似。
复制 function slowFunction (num) {
for ( let i = 0 ; i < 10000 ; i ++ ) {
for ( let i = 0 ; i < 10000 ; i ++ ) {}
}
return num * 10 ;
}
const numValue = slowFunction (num);
复制 function slowFunction (num) {
for ( let i = 0 ; i < 10000 ; i ++ ) {
for ( let i = 0 ; i < 10000 ; i ++ ) {}
}
return num * 10 ;
}
const numValue = useMemo (() => slowFunction (num) , [num]);
第一种情况,每次组件重新渲染时都会导致slowFunction
函数重新计算,但是在第二种情况下,react会缓存slowFunction
的计算结果,只有当num
改变时,才会重新计算。
useMemo所带来的性能提升可以查看下面示例:
https://codesandbox.io/s/usememobi-mian-ang-gui-de-chong-xin-ji-suan-ilf1dt
避免子组件重新渲染
当父组件的Props或者state改变时,会导致组件自身和其所有子孙组件都重新渲染,但是有时候我们不需要让子组件重新渲染,这个时候可以使用memo
,通过memo缓存组件,如果该组件的所有Prop都没有改变,则不会重新渲染该组件。
复制 // App.jsx
import "./styles.css" ;
import Child from "./Child" ;
import { useState } from "react" ;
export default function App () {
const [ re , reRender ] = useState ( 1 );
console .log ( "父组件更新" );
return (
< div className = "App" >
< h1 >避免子组件重新渲染</ h1 >
< button onClick = {() => reRender ((n) => n + 1 )}>组件重新渲染</ button >
< div >
< Child text = "使用memo" />
</ div >
</ div >
);
}
// Child.jsx
import { memo } from "react" ;
function Child ({ text }) {
console .log ( "子组件渲染" );
return < div >Child: {text + "的组件" }</ div >;
}
export default memo (Child);
https://codesandbox.io/s/memobi-mian-zu-jian-chong-xin-xuan-ran-ydmlgo
useCallback
useCallback是一个react Hook,用于在重新渲染期间缓存函数声明。
useCallback需要传递两个参数:一个需要被缓存的函数和一个依赖数组。
react会通过 Object.is
来比较上一次渲染时的依赖数组的每一项与当前的值是否相同,如果全部相同则会返回上一次的函数,否则 useCallback
会返回此次渲染的函数。
跳过组件重新渲染
之前说过react可以通过使用memo
来跳过组件重新渲染,以此来优化性能。
复制 // App.jsx
import { useState , memo } from "react" ;
const Child = memo ( function ({ text }) {
console .log ( "子组件渲染" );
return < div >Child: {text + "的组件" }</ div >;
});
export default function App () {
const [ re , reRender ] = useState ( 1 );
console .log ( "父组件更新" );
return (
< div className = "App" >
< h1 >避免子组件重新渲染</ h1 >
< button onClick = {() => reRender ((n) => n + 1 )}>组件重新渲染</ button >
< div >
< Child text = "使用memo" />
</ div >
</ div >
);
}
只有当组件Child的props
有更改的情况下才会重新渲染,否则不会重新渲染组件。
但是有时候memo并不会按照我们想象的情况来工作,当我们传递一个函数作为prop到Child组件时,由于每次App组件重新渲染都会重新执行app组件函数,遇到函数声明或者箭头函数等都会创建一个新的函数,这时memo通过Object.is比较两个函数时会认为prop更改了,就会导致memo优化失效。
复制 import { useState , memo } from "react" ;
const Child = memo ( function ({ fn}) {
return < div >Child</ div >;
});
export default function App () {
const [ num , setNum ] = useState ( 1 )
// 每当App函数重新执行时遇到函数声明都会创建一个新的函数
function fn (num){ return num * 2 }
console .log ( "父组件更新" );
return (
< div className = "App" >
< h1 >避免子组件重新渲染</ h1 >
< button onClick = {() => setNum ((n) => n + 1 )}>组件重新渲染</ button >
< div >
< Child fn = {fn} />
</ div >
</ div >
);
}
这个时候需要利用useCallback
缓存函数,将num
作为依赖项,只有当num
改变时才会重新执行函数声明,创建一个新的函数。
复制 import { useState , memo } from "react" ;
const Child = memo ( function ({ fn}) {
return < div >Child</ div >;
});
export default function App () {
const [ num , setNum ] = useState ( 1 )
// 只有当num改变时才会重新执行函数声明创建一个新的函数。
const fn = useCallback ( function fn (num){ return num * 2 } , [num])
console .log ( "父组件更新" );
return (
< div className = "App" >
< h1 >避免子组件重新渲染</ h1 >
< button onClick = {() => setNum ((n) => n + 1 )}>组件重新渲染</ button >
< div >
< Child fn = {fn} />
</ div >
</ div >
);
}
从缓存函数中更新状态
有时,我们可能需要在缓存回调函数中基于之前的值来更新状态。
复制 import { useState , useCallback } from "react" ;
function Child ({ todo }) {
const [ num , setNum ] = useState ( 1 );
const fn = useCallback (
function () {
const n = num;
setNum (n + todo);
} ,
[num]
);
return (
< div >
< h3 >num: {num}</ h3 >
< button onClick = {fn}>num+1</ button >
</ div >
);
}
这看起来和useState更新状态很像,例如:
复制 const fn = () => {
setNum ((n) => n + todo);
};
这也能基于num
上一次渲染的值来更新num
状态,但是这里的todo
却并不是上一次渲染的值,如果我们需要根据上一次渲染的todo
和num
来更新num
,我们需要useCallback
。
复制 // 只有当num更新后,缓存函数中的todo才会获取最新值。
const fn = useCallback (
function () {
const n = num;
setNum (n + todo);
} ,
[num]
);
https://codesandbox.io/s/usecallbackcong-huan-cun-han-shu-zhong-geng-xin-zhuang-tai-upzqft
避免Effect频繁执行
以下面的代码为例:
复制 import "./styles.css" ;
import { useEffect , useState } from "react" ;
export default function App () {
const [ re , reRender ] = useState ( 0 );
const [ roomId , setRoomId ] = useState ( 0 );
function createOptions () {
return {
serverUrl : "https://localhost:1234" ,
roomId : roomId
};
}
useEffect (() => {
const options = createOptions ();
// ...
console .log ( "useEffect执行" );
} , [createOptions]);
return (
< div className = "App" >
< h1 >Hello CodeSandbox</ h1 >
< button onClick = {() => reRender ((n) => n + 1 )}>重新渲染</ button >
</ div >
);
}
当组件重新渲染后,会声明一个新的createOptions
函数,这就导致了useEffect
会重复执行。
复制 const createOptions = useCallback (
function createOptions () {
return {
serverUrl : "https://localhost:1234" ,
roomId
};
} ,
[roomId]
);
useEffect (() => {
const options = createOptions ();
// ...
console .log ( "useEffect执行" );
} , [createOptions]);
当然,也可以直接将函数声明放到useEffect
中。
复制 useEffect (() => {
function createOptions () {
return {
serverUrl : "https://localhost:1234" ,
roomId
};
}
const options = createOptions ();
// ...
console .log ( "useEffect执行" );
} , [roomId]);
useReducer