React Hook 使用
本文章主要介绍如下hook:useState, useEffect, memo, useCallback, useMemo。
useState
useState可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
import React, { useState, useEffect, memo, useCallback, useMemo } from "react";
import { Button } from "antd";
const Main = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>count:{count}</p>
<Button onClick={() => { setCount(count + 1) }}>cllick(count)</Button>
</div>
);
};
export default Main;
useEffect
Effect Hook 可以让你在函数组件中执行副作用操作,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
import React, { useState, useEffect, memo, useCallback, useMemo } from "react";
import { Button } from "antd";
const Main = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`You clicked ${count} times.`);
// useEffect 的第二个参数是数组
// [count] 表示 count 与原来的值不同时,才会调用回调函数
}, [count]);
return (
<div>
<p>count:{count}</p>
<Button onClick={() => { setCount(count + 1) }}>cllick(count)</Button>
</div>
);
};
export default Main;
此时的useEffect相当于componentDidMount与componentDidUpdate,在render完成以及state,props改变时会执行里面的回调函数。
当 useEffect 的 effect 返回一个函数,React 将会在执行清除操作时调用它,也即相当于componentWillUnmount:
useEffect(() => {
console.log(`You clicked ${count} times.`);
return () => {
console.log('componentWillUnmount');
}
}, [count]);
React.memo()
先抛出一个问题:React 中当组件的 props 或 state 改变时,当前组件及其子组件会重新 render,即使子组件没有用到 props 或 state,看下面的例子。
import React, { useState, useEffect, useCallback, useMemo } from "react";
import { Button } from "antd";
const Child = function () {
useEffect(() => {
console.log("Child render.");
};
return (<div>123</div>);
};
const Main = () => {
const [count, setCount] = useState(0);
return (
<div>
<div>
<p>
<Button
onClick={() => {
setCount(count + 1);
}}
>
click(count)
</Button>
</p>
<p>count: {count}</p>
</div>
<Child />
</div>
);
};
export default Main;
点击父组件中按钮,会修改 count 变量的值,进而导致父组件重新渲染,此时子组件压根没有任何变化(props、state),从下图可以看到,Child重新渲染了。
使用 memo 即可以解决:
import React, { memo } from "react";
const Child = memo(function () {
useEffect(() => {
console.log("Child render.");
};
return (<div>123</div>);
});
这种写法是 React 的高阶组件写法,将组件作为memo()函数的参数,函数的返回值是一个新的组件。
useCallback
当前Child没有props传入,我们传入props看看:
import React, { useState, useEffect, memo, useCallback, useMemo } from "react";
import { Button } from "antd";
interface ICProps {
name: string;
onChangeName: React.MouseEventHandler<HTMLElement>;
}
const Child = memo(function (props: ICProps) {
const { onChangeName, name } = props;
useEffect(() => {
console.log("Child render.");
});
return (
<div>
<p>name: {name}</p>
<p>
<Button onClick={onChangeName}>click name</Button>
</p>
</div>
);
});
const Main = (props: IProps) => {
const [count, setCount] = useState(0);
const [name, setName] = useState("sarah");
const onChangeName = () => { setName("jack"); };
return (
<div>
<div>
parent:
<p>
<Button
onClick={() => {
setCount(count + 1);
}}
>
click(count)
</Button>
</p>
<p>
count: {count}
</p>
</div>
<hr />
child:
<Child onChangeName={onChangeName} name={name} />
</div>
);
};
export default Main;
Child 组件传入props:onChangeName,name。当count改变时,发现Child会重新渲染。
我们使用了memo,Child依然渲染了,说明Child的props被改变了。
分析原因:
- 点击父组件按钮,改变了父组件中 count 变量值(父组件的 state 值),进而导致父组件重新渲染;
- 父组件重新渲染时,会重新创建 onChangeName 函数,即传给子组件的 onChangeName props 发生了变化,导致子组件渲染;
使用 useCallback 解决:
const Main = (props: IProps) => {
const [count, setCount] = useState(0);
const [name, setName] = useState("sarah");
// useCallback 第二个参数传 [],则无论 state 怎么变,useCallback 只会执行一次
// 这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。
// 故 onChangeName 不会再次改变
const onChangeName = useCallback(() => {
setName("jack");
}, []);
return (
// ...
);
};
上面代码并不需要 onChangeName 函数,抽离出 onChangeName 函数是为了演示,父组件重新渲染时,会重新创建 onChangeName 函数。
同理,如果在父组件定义其他类型 a,将 a 传入子组件,那么父组件重新渲染时,是否会重新创建 a?
测试发现,只有引用数据类型,才会重新创建,useMemo()便是用于解决此问题的。
useMemo
import React, { useState, useEffect, memo, useCallback, useMemo } from "react";
import { Button } from "antd";
interface ICProps {
name: string;
onChangeName: React.MouseEventHandler<HTMLElement>;
info: { count: number; };
}
const Child = memo(function (props: ICProps) {
const { onChangeName, name } = props;
useEffect(() => {
console.log("Child render.");
});
return (
<div>
<p>name: {name}</p>
<p>
<Button onClick={onChangeName}>click name</Button>
</p>
</div>
);
});
const Main = (props: IProps) => {
const [count, setCount] = useState(0);
const [name, setName] = useState("sarah");
const info = { count: 18 };
return (
<div>
<div>
parent:
<p>
<Button
onClick={() => {
setCount(count + 1);
}}
>
click(count)
</Button>
</p>
<p>
count: {count}
</p>
</div>
<hr />
child:
<Child onChangeName={onChangeName} name={name} info={info} />
</div>
);
};
export default Main;
useMemo 有两个参数:
- 第一个参数是个函数,返回的对象指向同一个引用,不会创建新对象;
- 第二个参数是个数组,只有数组中的变量改变时,第一个参数的函数才会返回一个新的对象。
const info = useMemo(() => ({ name, age }), [name, age]) // 包一层
再次点击count不会重新渲染Child。