构建React Hook 共享逻辑场景及传统做法

一、共享逻辑场景及传统做法
二、创建自定义 Hook
三、使用自定义 Hook
1、上面代码是否功能相同?
2、是否必须以 use 开头设置自定义 Hook?
3、使用相同的 Hook 的两个 Component 会共享 state 吗?
4、自定义 Hook 如何获取隔离 state?
四、Tip:在 Hook 之间传递信息
扩展

 

一、共享逻辑场景及传统做法

构建自己的 Hook 能够将组件逻辑提取到可重用的函数中。

下面代码是 React Hook 文档中使用 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 (
    
  • {props.friend.name}
  • ); }

    现在,我们希望在 FriendStatus 和 FriendListItem 之间分享这个逻辑。

    传统的做法中,React 有两种方式共享组件之间的 state 或者逻辑:render props 和 高阶组件。但是使用 Hook 能够不在 tree 中添加更多组件的情况下解决相同的问题。

    二、创建自定义 Hook

    当我们想要在两个 JavaScript 函数之间共享逻辑,我们将其提取到第三个函数。组件和Hook 都是 function 这也适用于两者。

    自定义 Hook 其实就是一个 JavaScript 方法,其名称以 “use” 开头,可以调用其他 Hook。例如下面的 useFriendStatus Hook 就是一个自定义的 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;
    }

    上面没有任何新内容,逻辑是从上面的组件中复制的。就像是在组件中一样,确保只在自定义 Hook 的顶层无条件的调用其他 Hook。

    和 React 组件不同的是,自定义的 Hook 不需要具有特定的 signature。我们可以决定它需要什么参数,以及返回什么内容(如果有返回值的话)换句话说,它就像是一个普通的 function。自定义 Hook 的名称应当始终以 use 开头,这样就能够直接去依照 Hook 的规则去使用。

    我们使用 FriendStatus Hook 的目的是订阅我们朋友的在线状态,因此需要 friendID 作为参数,并返回好友是否在线。

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

    三、使用自定义 Hook

    最初,我们的目的是从 FriendStatus 和 FriendListItem 组件中删除重复的逻辑,他们俩都想知道好友是否在线。

    现在我们已经将这个逻辑提取到 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 (
        
  • {props.friend.name}
  • ); }

    1、上面代码是否功能相同?

    是的,和之前的代码以相同的方式起作用。如果你仔细观察,可以发现对行为没有任何的变化。我们所做的只是将两个函数之间的一些公共代码提取到一个单独的函数中。自定义 Hook 是一种自然遵循 Hook 设计的约定,而不是 React 的功能。

    2、是否必须以 use 开头设置自定义 Hook?

    这是一个非常重要的约定,如果不以 use 开头,没有办法通过 lint 自动检查是否违反了 Hook 的规则,因为 React 无法判断某个函数是否包含对其中的 Hooks 调用。

    3、使用相同的 Hook 的两个 Component 会共享 state 吗?

    不会。

    自定义 Hook 是一种重用有 state 逻辑的机制(比如设置订阅和记住当前值),但每次使用自定义 Hook 时,其中所有的 state 和 effect 都是完全隔离的。

    4、自定义 Hook 如何获取隔离 state?

    每次对 Hook 的调用都是被隔离的。因为我们直接调用 useFriendStatus,从 React 角度来说,我们的组件只调用了 useState和 useEffect

    React 允许我们可以在一个组件中多次的调用 useState 和 useEffect,它们是完全独立的。

    四、Tip:在 Hook 之间传递信息

    因为 Hook 是函数,我们可以在它们之间传递信息。

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

    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 (
        <>
          
          
        
      );
    }

    我们将当前选择的 friendID 保留在 recipientID 状态变量中,如果用户在