React Hooks 文档翻译 - 6 - Building Your Own Hooks(建立你自己的 Hooks)

目录

  1. 1. Extracting a Custom Hook
  2. 2. Using a Custom Hook
    1. 2.1. Tip: Pass Information Between Hooks
  3. 3. useYourImagination()

翻译自:https://reactjs.org/docs/hooks-custom.html

Hooks 是 React 16.8 新增的功能。它允许你在不编写类的情况下使用状态和其他 React 特性。

构建你自己的 Hooks 可以将组件逻辑提取到可重用的函数中。

我们在学习 Effect Hook 时,在聊天应用程序中看到过这个组件,该组件显示一条指示朋友是在线还是离线消息:

import { useState, useEffect } from 'react';

function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);

function handleStatusChange(status) {
setIsOnline(status.isOnline);
}

useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});

if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}

现在,假如我们的聊天应用程序也有一个联系人列表,想要呈现绿色的在线用户名。我们可以将上面类似的逻辑复制粘贴到 FriendListItem 组件中,但这样并不理想:

import { useState, useEffect } from 'react';

function FriendListItem(props) {
const [isOnline, setIsOnline] = useState(null);

function handleStatusChange(status) {
setIsOnline(status.isOnline);
}

useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});

return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}

相反,我们更想在 FriendStatusFriendListItem 之间分享这个逻辑。

传统上,在 React 中,有两种流行的方式来共享组件之间的状态逻辑:渲染 props高阶组件。我们来看看 Hook 如何在不强迫你在组件树中添加更多组件的情况下解决同样的问题。

Extracting a Custom Hook

提取自定义 Hook

当我们想要在两个 JavaScript 函数之间共享逻辑时,我们将它提取到第三个函数。组件和 Hooks 都是函数,所以这一点也适用于它们!

自定义 Hook 是一个 JavaScript 名字以“use”开头函数,并且可以调用其他的 Hooks。 例如,下面的 useFriendStatus 是我们的第一个自定义 Hook:

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);

function handleStatusChange(status) {
setIsOnline(status.isOnline);
}

useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});

return isOnline;
}

里面没有任何新内容 – 逻辑是从上面的组件中复制的。就像在组件中使用 Hooks 的限制,确保自定义的 Hooks 只在顶层非条件性地调用其他的 Hooks。

与 React 组件不同,自定义 Hook 不需要具有特定签名。我们可以自由地决定它需要什么参数,以及它返回什么(如果有返回的话)。换句话说,它就像一个普通的函数。它的名字应始终以 use 开始,这样你就可以一眼就看出适用于它的 钩子的规则

我们使用 FriendStatus Hook 的目的是订阅朋友的状态。这就是为什么它将 friendID 作为参数,并返回此朋友是否在线:

function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);

// ...

return isOnline;
}

现在来看看怎么使用我们自定义的 Hook。

Using a Custom Hook

使用自定义的 Hook

最初,我们的目标是从 FriendStatusFriendListItem 组件中删除重复的逻辑。它们都想知道朋友是否在线。

现在我们已经将这个逻辑提取到 useFriendStatus hook 里,我们可以直接使用它

function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);

if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);

return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}

这段代码是否等同于原始示例? 是的,它以完全相同的方式工作。如果你仔细观察,你会注意到我们没有对行为做任何改变。我们所做的只是将两个函数之间的一些公共代码提取到一个单独的函数中。自定义 Hooks 是自然地遵循 Hooks 设计的约定,而不是一个新的 React 功能。

我是否必须以“use”开头命名我的自定义 Hook? 请这样做。这个约定非常重要。没有它,我们将无法自动检查是否违反了 Hook 规则,因为我们无法判断某个函数是否在内部包含对 Hooks 的调用。

使用相同的 Hook 的两个组件共享同一个状态吗? 并不是。自定义 Hooks 是一种重用有状态逻辑的机制(例如设置订阅和记住当前值),但每次使用自定义 Hooks 时,其中的所有 state 和 effects 都是完全隔离的。

自定义 Hook 如何获得被隔离的状态? 每次对 Hooks 的调用都会获得隔离的 state。因为我们直接调用了 useFriendStatus,从 React 的角度来看,我们的组件只是调用了 useStateuseEffect 。正如我们之前所了解的,我们可以在一个组件中多次调用 useStateuseEffect ,它们将完全独立。

Tip: Pass Information Between Hooks

提示:在 Hooks 之间传递信息

由于 Hooks 是函数,我们可以在它们之间传递信息。

To illustrate this, we’ll use another component from our hypothetical chat example. This is a chat message recipient picker that displays whether the currently selected friend is online:

为了说明这一点,我们将使用聊天示例中的另一个组件。这是一个聊天消息收件人选择器,显示当前所选朋友是否在线:

const friendList = [
{ id: 1, name: 'Phoebe' },
{ id: 2, name: 'Rachel' },
{ id: 3, name: 'Ross' },
];

function ChatRecipientPicker() {
const [recipientID, setRecipientID] = useState(1);
const isRecipientOnline = useFriendStatus(recipientID);

return (
<>
<Circle color={isRecipientOnline ? 'green' : 'red'} />
<select
value={recipientID}
onChange={e => setRecipientID(Number(e.target.value))}
>
{friendList.map(friend => (
<option key={friend.id} value={friend.id}>
{friend.name}
</option>
))}
</select>
</>
);
}

我们将当前选择的朋友的 ID 保存在 recipientID 状态变量中,如果用户在 <select> 选择器中选择其他朋友,则更新它。

因为 useState Hook 的调用提供了 recipientID 状态变量的最新值,所以我们可以将它作为参数传递给自定义的 useFriendStatus Hook:

const [recipientID, setRecipientID] = useState(1);
const isRecipientOnline = useFriendStatus(recipientID);

这让我们知道当前选择的朋友是否在线。如果我们选择其他朋友并更新了 recipientID 状态变量,useFriendStatus Hook 将取消订阅之前选择的朋友,并订阅新选择的朋友的状态。

useYourImagination()

Custom Hooks 提供了以前在 React 组件中无法实现的共享逻辑的灵活性。你可以编写自定义 Hook,涵盖广泛的用例,如表单处理,动画,声明订阅,计时器,以及可能还有更多我们没有想过的。更重要的是,你可以构建像 React 内置功能一样易于使用的 Hook。

尽量避免过早添加抽象。既然函数组件可以做得更多,那么代码库中的函数组件平均长度可能会变得更长。这是正常的 – 不要觉得你必须将它们拆分到多个 Hooks。但我们也鼓励你开始了解自定义 Hook 可以隐藏简单接口背后的复杂逻辑,这或有助于解开混乱组件的情况。

例如,你可能有一个复杂的组件,其中包含许多以 ad-hoc 的方式管理的本地状态。useState 不会使更新逻辑更容易集中化,因此你可能希望将其编写为 Redux reducer

function todosReducer(state, action) {
switch (action.type) {
case 'add':
return [...state, {
text: action.text,
completed: false
}];
// ... other actions ...
default:
return state;
}
}

Reducers 非常便于单独测试,并且可以扩展以表达复杂的更新逻辑。如有必要,你可以将它们分成更小的 reducers 。但是,你可能享受使用 React 本地状态的好处,或者可能不想安装其他库。

那么,如果我们可以写一个 useReducer Hook,让我们用 reducer 管理组件的本地状态呢?它的简化版本可能如下所示:

function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);

function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}

return [state, dispatch];
}

现在我们可以在组件中使用它,让 reducer 驱动它的状态管理:

function Todos() {
const [todos, dispatch] = useReducer(todosReducer, []);

function handleAddClick(text) {
dispatch({ type: 'add', text });
}

// ...
}

在复杂组件中使用 reducer 管理本地状态的需求很常见,我们已经将 useReducer Hook 构建到 React中。你可以在 Hooks API 参考 中找到它与其他内置 Hook。

知识共享许可协议 知识共享许可协议 知识共享许可协议 本网站原创内容(非转载文章)采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。