构建自定义 React Hooks
React Hooks 简化了创建可重用、简洁且通用的代码的过程,并且像 memoization 这样的高级优化技术现在也更易于访问和使用。React 的官方文档只涵盖了基本 Hooks,并未详细介绍自定义 Hooks,因此本文将主要关注构建自定义 React Hooks 及其最佳实践。
要充分利用本文,您需要了解 React Hooks 的基本知识。如果您还不熟悉这些基础知识,可以参考许多优秀的文章。例如,React 官方文档就是一个不错的起点。
心态
为了构建一个多功能、高性能和可重用的自定义钩子,需要记住几件事。
每次组件重新渲染时都会运行钩子
由于我们使用的是函数式组件和钩子,我们不再需要生命周期方法。每次状态或属性发生变化时,函数式组件都会重新渲染,因此,我们的自定义钩子会被一遍又一遍地调用。
尽可能使用基本钩子
基本 React Hook 是任何自定义 Hook 的核心。我们可以使用memoization和Hook 依赖数组来控制自定义 Hook 的哪些部分会在每次重新渲染时更改,哪些部分不会更改。为了有效地使用它们并构建高性能 Hook,了解每个基本 Hook 在自定义 Hook 中的作用至关重要。
钩子的规则
有一些重要的规则需要牢记。这些规则在React hooks 文档中有详细的解释。
构建自定义钩子
既然我们已经了解了基础知识,现在就可以构建自己的自定义钩子了。在下面的示例中,我们将建立一个构建自定义钩子的可靠模式,并介绍一些最佳实践。
假设我们正在开发一个项目,用户可以玩多个以掷骰子为游戏机制的游戏。有些游戏只需要一个骰子,而有些游戏可能需要多个骰子。我们还假设在某些游戏中,使用的骰子数量可能会发生变化。
牢记这一点,我们将构建具有以下功能的useGameDice钩子:
- 自定义钩子可以用正在使用的骰子数量和初始值进行初始化
- 设置所用骰子数量的函数
- 掷骰子的函数。返回一个 1 到 6 之间的随机数数组。长度由使用的骰子数量决定。
- 将所有骰子值重置为初始值的函数
设置钩子(导入和钩子函数)
我们将自定义钩子声明为常规箭头函数,遵循自定义钩子的命名规范——名称应以“use”关键字开头。我们还导入了稍后将在实现中使用的 React 钩子。我们还可以导入常量、其他函数、其他自定义钩子等。
我们的钩子可以用两个可选变量来初始化:
- initialNumberOfDice - 将使用多少个骰子
- initialDiceValue - 确定初始值和重置后的值
这两个变量的默认值都是 1,以避免任何错误并简化钩子设置。
import { useState, useMemo, useCallback, useEffect } from "react";
export const useGameDice = (initialNumberOfDice = 1, initialDiceValue = 1) => {
/* We'll be adding code here in order */
};
添加状态和记忆私有变量
首先,我们需要设置状态。我们将声明两个简单的状态:
- diceValue - 数组,其大小由 numberOfDice 定义,并保存每个骰子的值
- numberOfDice - 确定将使用的骰子数量(diceValue 数组大小)
我们还初始化了initialDiceState变量,该变量创建了初始数组值,该值将在首次渲染和状态重置时分配。此值会被记忆,以避免在每次重新渲染时初始化数组并填充默认值。
const [diceValue, setDiceValue] = useState();
const [numberOfDice, setNumberOfDice] = useState(initialNumberOfDice);
const initalDiceState = useMemo(
() => Array(numberOfDice).fill(initialDiceValue),
[numberOfDice, initialDiceValue]
);
添加记忆钩子函数
接下来,我们将创建以下函数:
- generateRandomDiceNumber - 生成 1 到 6 之间的随机数(一次掷骰子)
- rollDice - 为数组中的每个元素(骰子)调用随机数生成器
- resetDice - 将骰子值状态重置为初始值
const generateRandomDiceNumber = useCallback(() => {
return Math.floor(Math.random() * 6) + 1;
}, []);
const rollDice = useCallback(() => {
const arrayConfig = { length: numberOfDice };
const newDiceValues = Array.from(arrayConfig, generateRandomDiceNumber);
setDiceValue(newDiceValues);
}, [numberOfDice, generateRandomDiceNumber]);
const resetDice = useCallback(() => {
setDiceValue(initalDiceState);
}, [initalDiceState]);
我们使用useCallback hook 来控制函数何时重新初始化。只有当依赖数组中的任何变量发生变化时,函数才会重新初始化。以generateRandomDiceNumber函数为例,它在首次渲染和初始化后永远不会重新初始化,因为该函数不依赖于任何外部变量或状态。
添加副作用 - 钩子初始化和更新
我们需要设置一个监听器来监视骰子初始状态的更新。这个副作用有两个作用:
- 当钩子首次初始化时,它将骰子状态设置为初始值
- 当骰子数量(数组大小)发生变化时,它将骰子状态更新为初始值
useEffect(() => {
setDiceValue(initalDiceState);
}, [initalDiceState]);
API 设置和返回语句
最后,我们定义 state 和 api 对象,并按照 useState 的约定将它们返回到一个数组中。我们来看看每个对象:
- state - 保存所有状态值。我们预计此对象几乎每次重新渲染时都会发生变化
- api - 包含所有函数。我们将返回useCallback中声明的一些函数以及useState hook中的一个函数。此对象被记录下来,因为我们预计它不会在每次重新渲染时发生变化。
const state = {
diceValue,
numberOfDice
};
const api = useMemo(
() => ({
setNumberOfDice,
rollDice,
resetDice
}),
[setNumberOfDice, rollDice, resetDice]
);
return [state, api];
我们将对象以数组的形式返回,因为我们希望这个钩子更加灵活。这样,我们允许开发人员重命名返回的变量,并允许他们在需要时初始化此钩子的多个实例。
const [diceFirst_state, diceFirst_api] = useGameDice();
const [diceSecond_state, diceSecond_api] = useGameDice();
Git 存储库和演示
您可以在以下GitHub 存储库中查看最终实现和完整代码以及演示。
React 自定义 hooks 模式概述
现在,你可能已经注意到,我们将要添加的代码按部分分组了。这种结构清晰的模式遵循以下逻辑路径:
- 状态初始化(useState、useReducer)、局部变量初始化(useMemo)、ref 初始化(useRef)和外部自定义 hooks 初始化
- 记忆函数(useCallback)
- 副作用(useEffect)
- API 设置(状态和记忆 API)
- Return 语句
结论
Hooks 受到 React 社区的热烈欢迎并不令人意外。开发者能够更轻松地在组件之间共享逻辑,为每个自定义 Hook 创建多个组件(接口),并挑选 Hook 的状态和 API 部分用于组件中,等等。
这种可重用性和多功能性使 Hooks 成为 React 应用开发中真正的颠覆者。在构建自定义 React Hooks 时,凭借既定的模式和最佳实践,开发人员能够交付质量一致、结构清晰且性能最佳的代码。
这些文章都是咖啡带来的灵感。所以,如果你喜欢我的文章,觉得它有用,不妨请我喝杯咖啡!我会非常感激的。
感谢您花时间阅读这篇文章。如果您觉得它有用,请点赞 ❤️ 或 🦄,分享并评论。
本文也可在Medium上阅读,如果您喜欢,请随意给它一个👏。
文章来源:https://dev.to/prototyp/building-custom-react-hooks-11h2