要想学习useMemo、useCallback 不要忽略了React.memo()这个高阶函数/组件。
React.memo()只用于函数组件,它有一个参数,可为函数也可以为组件,它的功能类似于shouldcomponent对组件接受的 props 属性进行浅比较来判断组件要不要进行重新渲染。
当父组件数据变化时,代码会重新执行一遍,但是子组件数据没有变化也会跟随执行,这个时候可以使用memo
将子组件封装起来,让子组件的数据只在发生改变时才会执行,节约子组件渲染的性能开销,这是memo的作用。
一定要注意区分,React.memo()、useMemo、useCallback、useRef都是做性能优化的!
React.memo()是在组件之间不存在通信时做缓存组件的用途。
useMemo,useCallback,useRef是用作组件间通信时缓存变量,函数的用途。
简单来说 useMemo(用作缓存一个值),useCallback(用来缓存一个函数) 二者都是避免子组件被重复渲染
案例:
import React, { memo, useState } from 'react';
// 子组件
const ChildComp = (props:{info:{cname, cage}}) => {
console.log('子组件渲染==>',cname,cage);
return (我是子组件大名:{cname}虚岁:{cage});
};
//用memo包裹子组件
const MemoChildComp = memo(ChildComp);
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
const [cname, setCname] = useState('acccc');
const [cage, setCage] = useState(22);
const info = { cname, cage};
return (
数值递增: {count}
);
};
export default Parent;
问题:
点击增加按钮,count 变化,父组件会重新渲染,而子组件MemoChildComp中的 const info = { name, age }也会重新生成对象,引用地址被改变,会重新渲染子组件
解决方法:
使用 useMemo 将对象属性包一层。useMemo 有两个参数:
import React, { memo, useMemo, useState } from 'react';
// 子组件
const ChildComp = (info:{info:{cname, cage}}) => {
console.log('子组件渲染==>',cname,cage);
return (我是子组件大名:{cname}虚岁:{cage});
};
//用memo包裹子组件
const MemoChildComp = memo(ChildComp);
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
const [cname, setCname] = useState('acccc');
const [cage, setCage] = useState(22);
// 使用 useMemo 将对象属性包一层
const info = useMemo(() => ({ cname, cage}), [cname, cage]);
return (
数值递增: {count}
);
};
export default Parent;
其次使用useMemo需要注意的点:
useMemo 的函数参数里不能写入渲染页面的逻辑,否则会造成页面死循环.
若真要写渲染页面的逻辑请使用useEffect这个函数钩子
他们的执行的时间点不一样,useMemo 是在渲染页面期间执行,而useEffect是在渲染页面完成后执行的副作用函数.
官方有明确的解释:
请记住,传递给的函数useMemo在渲染期间运行。不要在那里做任何你在渲染时通常不会做的事情。例如,副作用属于useEffect,而不是useMemo。
对二者执行时机不同解释说明的一个非常棒的案例:useMemo和useEffect有什么区别?怎么使用useMemo - 简书
其次useMemo 与useEffect这两者的第二个参数为空数组时,useMemo是不会执行,而useEffect是组件初始加载执行一次.
案例:
import React, { memo, useState,useMemo } from 'react';
// 子组件
const ChildComp = (props) => {
console.log('子组件渲染==>',props);
return (我是子组件);
};
//用memo包裹子组件
const MemoChildComp = memo(ChildComp);
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
const [cname, setCname] = useState('acccc');
const [cage, setCage] = useState(22);
const info = { cname, cage};
// 使用 useMemo 将对象属性包一层
const info = useMemo(() => ({ cname, cage}), [cname, cage]);
const toChild=()=>{
console.log('去子组件')
}
return (
数值递增: {count}
);
};
export default Parent;
问题:
点击增加按钮,count 变化,父组件会重新渲染,而子组件MemoChildComp中的 const toChild=()=>{
console.log('去子组件')}也会重新生成函数,引用地址被改变,会重新渲染子组件
解决方法:
使用 useCallback 将函数参数包一层,将这个函数参数缓存起来。useCallback 有两个参数:
import React, { memo,useCallback, useState,useMemo } from 'react';
// 子组件
const ChildComp = (props) => {
console.log('子组件渲染==>',props);
return (我是子组件);
};
//用memo包裹子组件
const MemoChildComp = memo(ChildComp);
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
const [cname, setCname] = useState('acccc');
const [cage, setCage] = useState(22);
const info = { cname, cage};
// 使用 useMemo 将对象属性包一层
const info = useMemo(() => ({ cname, cage}), [cname, cage]);
const toChild=useCallback(()=>{
console.log('去子组件')
},[])
return (
数值递增: {count}
);
};
export default Parent;
用useCallback包裹toChid函数后,父组件重渲染时,包裹后的函数因为依赖项不变,所以还是用记忆函数,则MemoChildComp组件中toChid函数参数并没有发生改变,子组件props也没有改变,也就不会进行重渲染。
useRef:
useRef能
返回一个可变 ref 对象,其.current
属性初始化为传递的参数 ( initialValue
)。返回的对象将在组件的整个生命周期内持续存在。
常用作表示一个dom元素
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
>
);
}
这个案例中使用useref获取input的标签,其.current就表示当前input的dom元素,可以直接使用input对应的标签属性来做调用
其次:useRef 不仅仅是用来管理 DOM ref 的,它还相当于 this , 可以存放任何变量.
案例:实时获取当前的变量值
import React, { useRef, useState,useEffect} from 'react';
const Parent = () => {
const [count, setCount] = useState(0);
const latestCount = useRef(count);
useEffect ({
//每次count更新值时组件重渲染执行将count的值赋给latestCount.current
latestCount.current=count;
})
fucntion onCountClick(){
setTimeOut(()=>{
console.log('当前的count值为====>'+latestCount.current)
},3000)
}
return (
数值递增: {count}
);
};
export default Parent
因为 useRef 每次都会返回同一个引用, 所以在 useEffect 中修改的时候 ,在 setTimeOut中也会同时被修改. 这样子, 点击的时候就可以弹出实时的 count 了.
最后:
useMemo 和useEffect用法相似,useMemo 常用作缓存值,在组件渲染期间执行,
其次useCallback常用作缓存一个函数,与useMemo都是用作函数组件约束渲染的钩子函数,
useRef常用作表示一个DOM元素以及一个值,而.current 的作用更像是类组件的this.