对 react hooks 的理解
通常情况下,React 会有多余的 render。
1. 常用的场景一:子组件依赖父组件数据,当父组件数据更新时,会重新渲染子组件。
// index.tsx
function Index() {
const [count, setCount] = useState(0);
const handleAdd = () => {
setCount((i) => i + 1);
};
return (
{count}
);
}
// child.tsx
function Child(props: any) {
console.log("child 被渲染了!!");
return (
child 被渲染了({props.data})
);
}
2. 常见的场景二:子组件依赖父组件数据,当父组件其他数据(非子组件依赖的数据)更新时,子组件也会重新渲染!
// index.tsx
// const data = 0; // 定义一个常量
function Index() {
const [count, setCount] = useState(0);
const [data, setData] = useState(0);
const handleAdd = () => {
setCount((i) => i + 1);
};
return (
{count}
);
}
// child.tsx
function Child(props: any) {
console.log("child 被渲染了!!");
return (
child 被渲染了({props.data})
);
}
3. 常见的场景三:子组件不依赖父组件数据,当父组件数据更新时,子组件也会重新渲染!!
// index.tsx
function Index() {
const [count, setCount] = useState(0);
const handleAdd = () => {
setCount((i) => i + 1);
};
return (
{count}
);
}
// child.tsx
function ChildPure() {
console.log("childPure 被渲染了!!");
return (
childPure 被渲染了
);
}
【总结】不论子组件是否有依赖父组件的数据,当父组件数据更新时(也就是调用 setXXX()),react 会重新渲染整个视图,其中包括了子组件。多数情况下,这种渲染组件操作是多余的、浪费的,react.memo() 可以用来解决这个问题。
react.memo
将代码里的 Child 用 React.memo(Child) 包裹,当 props 没有更新时,React 会跳过渲染组件的操作并直接服用最近一次渲染的结果。
// index.tsx
function Index() {
const [count, setCount] = useState(0);
const handleAdd = () => {
setCount((i) => i + 1);
};
return (
{count}
);
}
// child.tsx
function ChildPure() {
console.log("childPure 被渲染了!!");
return (
childPure 被渲染了
);
}
export const ChildPureMemo = React.memo(ChildPure);
但是这里还有个bug,当给组件添加了监听事件后,又回到了最初的重复渲染状态。
// index.tsx
function Index() {
const [count, setCount] = useState(0);
const handleAdd = () => {
setCount((i) => i + 1);
};
const handleData = () => {};
return (
{count}
);
}
// child.tsx
function Child(props: any) {
console.log("child 被渲染了!!");
return (
child 被渲染了({props.data})
);
}
export const ChildMemo = React.memo(Child);
[总结] 使用 react.memo() 包裹的子组件,当 props 没有变化时,不会重新渲染子组件。但是监听事件在父组件重新渲染时,虽然功能一样,但引用地址变化了!!所以对于子组件来说,props发生了变化,进行重新渲染。这种情况下,需要使用 useCallback 解决。
useCallback
useCallback 的第二个参数是依赖项,当依赖发生变化时,才会重新计算新的value,如果依赖不变,就重用之前的value。
// index.tsx
function Index() {
const [count, setCount] = useState(0);
const handleAdd = () => {
setCount((i) => i + 1);
};
const handleData = useCallback(() => {
console.log(111);
setData((i) => i + 1);
}, []);
return (
{count}
);
}
// child.tsx
function Child(props: any) {
console.log("child 被渲染了!!");
return (
child 被渲染了({props.data})
);
}
export const ChildMemo = React.memo(Child);
useMemo
useMemo 的用途和 useCallback 一样,只是第一个参数的类型不一样。
useCallback(value, []) => useMemo(()=>value,[])
如果 value 是一个函数时,useMemo 的写法会相对不易理解
// index.tsx
function Index() {
const [count, setCount] = useState(0);
const handleAdd = () => {
setCount((i) => i + 1);
};
const handleData = useMemo(
() => () => {
console.log(111);
setData((i) => i + 1);
},
[]
);
return (
{count}
);
}
// child.tsx
function Child(props: any) {
console.log("child 被渲染了!!");
return (
child 被渲染了({props.data})
);
}
export const ChildMemo = React.memo(Child);