自定义 Hook

Posted 前端e站

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义 Hook相关的知识,希望对你有一定的参考价值。

自定义Hooks可以让你把组件逻辑抽到可复用的方法里。

当我们学effect hooks的时候,我们用这个组件来显示朋友是否在线

import React,  useState, useEffect  from 'react';
function FriendStatus(props) 
const [isOnline, setIsOnline] = useState(null);
  useEffect(() => 
    function handleStatusChange(status) 
      setIsOnline(status.isOnline);
    
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
      return () => 
        ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    ;
  );
  if (isOnline === null) 
      return 'Loading...';
  
  return isOnline ? 'Online' : 'Offline';

        现在我们假设聊天应用中有一个联系人列表,当用户在线时需要把名字设置为绿色。我们可以把上面类似的逻辑复制并粘贴到 FriendListItem 组件中来,但这并不是理想的解决方案:

import React,  useState, useEffect  from 'react';
​
function FriendListItem(props) 
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => 
    function handleStatusChange(status) 
      setIsOnline(status.isOnline);
    
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => 
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    ;
  );
​
  return (
    <li style= color: isOnline ? 'green' : 'black' >
      props.friend.name
    </li>
  );

所以我们应该在FriendStatus和FriendListItem之间共享逻辑。

一般在React中,我们有两种流行的方式来共享有状态逻辑——render props和高阶组件。我们来看一下Hooks在不需要更多关注组件的情况下怎么来解决这个问题。

抽取到自定义Hook

当我们想要在javascript方法里复用逻辑时,我们会把它抽取到第三方的方法里。组件和Hooks本质上都是方法,所以同样适用。

自定义Hook是一个用use开头命名的JavaScript方法,它能调其他Hooks。比如下面的useFriendStatus是我们第一个自定义Hook。

import  useState, useEffect  from 'react';
​
function useFriendStatus(friendID) 
  const [isOnline, setIsOnline] = useState(null);
​
  useEffect(() => 
    function handleStatusChange(status) 
      setIsOnline(status.isOnline);
    
​
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => 
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    ;
  );
​
  return isOnline;

里面没有新的东西——就复制了组件里的逻辑。跟在组件里一样,保证只在自定义Hook的顶层调用其他Hooks。

跟React组件不同的是,自定义Hook不需要有特殊的方法签名。我们可以自己决定有什么参数和返回。换句话说,它就跟普通的方法完全一样。它的名字应该用use开头。

我们的useFriendStatus目的是订阅朋友的在线状态。它接受friendID作为传入参数,返回朋友是否在线的状态。

function useFriendStatus(friendID) 
  const [isOnline, setIsOnline] = useState(null);
​
  // ...
​
  return isOnline;

现在让我们看看应该如何使用自定义 Hook。

使用自定义Hook

一开始,我们的目标是移除在FriendStatus和FriendListItem之间的重复逻辑。它们都是想要获得某个朋友是否在线。

现在我们把逻辑抽到了useFriendStatus里。

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的特性。

我是否必须把自定义Hook的命名用use开头?请如此做。这个约定非常重要。不这样,我们没法按照Hooks使用规则这一章所阐述的来检查代码,因为没法判断某个方法是否在里面调用了Hooks。

两个使用相同Hook的组件是否共享了state?不。自定义Hooks是一个复用有状态逻辑的机制(比如订阅和记忆当前值),但每次你使用自定义Hook的时候,所有其中的stat和effect是独立的。

自定义Hook如何获得独立state的?每一次调用Hook都获得的是独立的state。因为我们是直接调useFriendStatus的,从React的角度看我们仅是调用了useState和useEffect。如我们之前所学,我们可以在一个组件里调用useState和useEffect多次,每次都是完全独立的结果。

在Hooks之间传递消息

因为Hooks就是一个方法,我们可以在它们之间传递消息。

我们用聊天例子中的另一个组件来说明。这是一个聊天消息接收选择器用来显示当前所选朋友是否在线的。

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> 中选择其他好友时更新这个 state。

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

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

​如此可以让我们知道当前选中的好友是否在线。当我们选择不同的好友并更新 recipientID 状态变量时,useFriendStatus Hook 将会取消订阅之前选中的好友,并订阅新选中的好友状态。

发挥你的想象力

自定义Hooks提供了在React组件里不曾有的共享逻辑的灵活性。你可以把自定义Hooks用在很多地方,比如表单处理、动画、声明式订阅、定时处理以及我们之前都没想到过的方向上。另外,你可以用Hooks像用React内置特性一样简单。

不要太早地去抽象。现在function组件可以干更多的事情,你代码库里的funtion组件代码将会变得更长。这很正常——不要觉得你必须立刻把什么都抽到Hooks里面去。但我们鼓励你开始去看看Hooks是如何把复杂的逻辑隐藏到简单的接口后去的,以及如何帮助简化一个复杂的组件。

例如,假使你有一个复杂的组件包含了很多的点对点的内部state。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 非常便于单独测试,且易于扩展,以表达复杂的更新逻辑。如有必要,您可以将它们分成更小的 reducer。但是,你可能还享受着 React 内部 state 带来的好处,或者可能根本不想安装其他库。

那么,为什么我们不编写一个 useReducer 的 Hook,使用 reducer 的方式来管理组件的内部 state 呢?其简化版本可能如下所示:

function useReducer(reducer, initialState) 
  const [state, setState] = useState(initialState);
​
  function dispatch(action) 
    const nextState = reducer(state, action);
    setState(nextState);
  
​
  return [state, dispatch];

在组件中使用它,让 reducer 驱动它管理 state: 

function Todos() 
  const [todos, dispatch] = useReducer(todosReducer, []);
​
  function handleAddClick(text) 
    dispatch( type: 'add', text );
  
​
  // ...

在复杂组件中使用 reducer 管理内部 state 的需求很常见,我们已经将 useReducer 的 Hook 内置到 React 中。你可以在 Hook API 索引中找到它使用,搭配其他内置的 Hook 一起使用。

往期阅读

1、​​​​​​​React Hooks介绍

2、React Hooks概览

3、使用 State Hook

4、使用 Effect Hook

5、Hooks使用规则

以上是关于自定义 Hook的主要内容,如果未能解决你的问题,请参考以下文章

自定义 Hook

自定义 Hook

返回组件数组的 React 自定义 Hook

vue3自定义hook函数

前端学习(3299):自定义hook

自定义Hook