Hooks函数
React16.8版本中引入了Hooks函数,用于解决常年以来在ClassComponent中存在的各种问题,实现更高效的编写React组件。目前使用Hooks和FunctionComponent是编写React程序的标准方式,而ClassComponent虽然仍能继续使用,但已经趋于废弃,React周边生态也逐渐不再支持。
这篇笔记我们介绍React中常用的Hooks函数功能及适用场景。
Hooks为什么叫Hooks?
Hooks函数这一概念最早可以追溯到计算机早期的操作系统中断处理程序,它们可以在某些事件发生时被调用,用来扩展或改变操作系统的某些行为。后来比较广泛的应用是早期的GUI框架,比如Win32中的SetWindowsHookEx函数就可以向操作系统注册钩子函数,用来捕捉键盘、鼠标等事件(也广泛用于木马程序的开发)。
React Hooks之所以叫Hooks,是因为它们可以让你将外部的数据“钩入”到函数的执行中,当数据被更新时自动触发函数的重新执行,从而实现外部数据和UI组件渲染结果的绑定。理解了这一点,其实我们就会发现使用Hooks比ClassComponent更加直观。
useState
useState用于访问和设置组件的状态,前面的章节中我们已经多次使用。下面例子代码中,我们实现了点击按钮1次就将cnt值加1的逻辑。
import { useState } from "react";
const App = () => {
const [cnt, setCnt] = useState(0);
const incrementCnt = () => {
setCnt(cnt + 1);
};
return (
<div>
<button onClick={incrementCnt}>ClickMe</button>
{cnt}
</div>
);
};
export default App;
useState返回值是包含2个元素的数组,第1个元素用于访问State值,第2个元素是一个Set函数,用于设置State值。直接修改cnt是没有任何效果的,我们必须通过调用setCnt()修改其值。
此外还有一点要尤其注意,State的Set函数本质上是异步的,代码中如果出现调用Set函数设置State后立即取值,一定是无法取得当前值的,典型的错误写法如下:
const incrementCnt = () => {
// 设置cnt值
setCnt(cnt + 1);
// 取cnt当前值
console.log(cnt);
};
在早期版本的React中设置State值的setState()函数支持回调函数参数,但Hooks中不支持,这需要通过useEffect()来实现,下面是一个正确写法的例子:
const incrementCnt = () => {
setCnt(cnt + 1);
};
useEffect(() => {
console.log(cnt);
}, [cnt]);
useEffect
useEffect可以用于执行带副作用的操作,所谓的副作用是指发送网络请求、修改DOM、订阅或取消订阅事件等和组件渲染没有关系的操作,它默认会在组件每次绘制完成后下一次重新绘制前执行。比如设置当前页面的标题,它需要操作DOM的属性,因此必须通过useEffect来实现。
useEffect(() => {
document.title = 'Hello React';
});
上面代码会在每次组件重新绘制时执行,如果不需要useEffect()在每次组件重新绘制时都执行,可以传递useEffect()的第二个参数,它是个数组参数,只有当数组的值变化时才重新执行。
import { useEffect } from "react";
const App = (props) => {
useEffect(() => {
document.title = props.text;
}, [props.text]);
return <div></div>;
};
export default App;
上面例子代码中,只有当props.text改变时,useEffect内部的函数才会再次执行。
有时我们需要在组件卸载时,一同卸载useEffect产生的副作用,这需要在useEffect指定一个清除副作用函数。
import { useEffect } from "react";
const App = () => {
useEffect(() => {
const timer = setInterval(() => {
console.log("hello");
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
return <div></div>;
};
export default App;
useEffect第1个参数函数的返回值就是清除函数,我们在其中卸载副作用即可。
useEffect另一个常见的用法是替代原ClassComponent的mount和update生命周期函数,常用于实现页面初始化请求之类功能的实现。这只需要将useEffect的第2个参数设置为空数组即可。
useEffect(() => {
// ... 具体的代码逻辑
}, []);
useContext
useContext在前面数据传递章节我们已经使用过了,它用于在多个组件之间共享数据。
ComponentA.jsx
import React, { useState } from "react";
import ComponentB from "./ComponentB";
export const UserContext = React.createContext({});
const ComponentA = () => {
const [user, setUser] = useState({
username: "tom",
password: "abc123",
});
return (
<UserContext.Provider
value={{
user,
setUser,
}}
>
<ComponentB></ComponentB>
</UserContext.Provider>
);
};
export default ComponentA;
ComponentB.jsx
import ComponentC from "./ComponentC";
const ComponentB = () => {
return (
<>
<ComponentC></ComponentC>
</>
);
};
export default ComponentB;
ComponentC.jsx
import { useContext } from "react";
import { UserContext } from "./ComponentA";
const ComponentC = () => {
const { user } = useContext(UserContext);
return <div>{JSON.stringify(user)}</div>;
};
export default ComponentC;
上面代码中,组件A通过Context.Provider设置了数据,组件C中通过useContext()可以访问到。
useReducer
使用useReducer前首先我们得知道什么是Reducer。Reducer描述的是这样的一种函数:纯函数,接收参数是state和action,根据action进行对应处理,返回新的state。useReducer这个Hooks函数就是按这个思路设计的。
import { useReducer } from "react";
const reducer = (n, action) => {
if (action.type === "inc") {
return n + 1;
} else {
return n - 1;
}
};
const App = () => {
const [cnt, dispatch] = useReducer(reducer, 0);
return (
<div>
<button onClick={() => dispatch({ type: "inc" })}>Inc</button>
<button onClick={() => dispatch({ type: "dec" })}>Dec</button>
{cnt}
</div>
);
};
export default App;
我们可以看到useReducer是一个功能更加强大的useState,它能将更新逻辑抽离到reducer函数中,将不同的动作通过dispatch的action暴露出来,这样能让组件状态的更新逻辑更加清晰。
useMemo
useMemo用于缓存计算结果以优化组件的渲染性能。如果我们的组件需要计算一个值,useMemo能够缓存它的计算结果,仅在依赖项改变时重新触发计算。
import { useMemo } from "react";
const App = (props) => {
const sum = useMemo(() => props.a + props.b, [props.a, props.b]);
return <div>{sum}</div>;
};
export default App;
上面例子代码中,我们使用useMemo缓存了props.a与props.b相加的和sum,除非这两个值发生改变,否则sum是不会重新计算的。
自定义Hooks函数
在实际开发中,除了使用React内置的Hooks函数,我们也可以通过自定义Hooks函数的方式抽离代码逻辑实现代码复用。自定义Hooks函数并不复杂,函数名需要以use开头,内部可以调用React内置的Hooks函数,其使用规则与组件中使用Hooks函数基本一致。
myhooks.js
import { useEffect, useState } from "react";
export const useMousePosition = () => {
const [position, setPosition] = useState({
x: null,
y: null,
});
useEffect(() => {
const moveHandler = (e) => {
setPosition({
x: e.screenX,
y: e.screenY,
});
};
document.addEventListener("mousemove", moveHandler);
return () => {
document.removeEventListener("mousemove", moveHandler);
};
}, []);
return position;
};
App.jsx
import { useMousePosition } from "./myhooks";
const App = () => {
const pos = useMousePosition();
return <div>{pos.x + " " + pos.y}</div>;
};
export default App;
上面例子代码中我们自定义了useMouse函数,它用于实时获取鼠标的位置,useMouse函数内部我们通过useEffect绑定鼠标移动事件到状态数据中。App组件中我们引用了这个Hooks函数,并将鼠标的位置实时显示了出来。