react16.7 编写自己的钩子

构建自己的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}
  • ); }

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

    传统上,在React中,我们有两种流行的方式来共享组件之间的状态逻辑:渲染道具和高阶组件。我们现在将看看Hook如何在不添加这些方法的情况下解决许多相同的问题。

    提取自定义钩子

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

    自定义Hook其实也是一个JavaScript函数,其名称以use开头,可以调用其他Hook 例如,下面的useFriendStatus是我们的第一个自定义钩子:

    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不需要具有特定签名。我们可以决定它作为参数需要什么,以及它应该返回什么(如果有的话)。换句话说,它就像一个普通的功能。它的名字应该始终使用use开头,这样你就可以一眼就看出钩子的规则适用于它。

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

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

    使用自定义钩子

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

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

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

    我是否必须以use开头命名我的自定义Hook 我们希望你做到这点。这个习惯很重要。如果没有它,我们将无法自动检查是否违反了Hook规则,因为我们无法判断某个函数是否包含对其中的Hooks的调用。

    两个组件使用相同的Hook共享状态吗? 不会。自定义挂钩是一种重用有状态逻辑的机制(例如设置订阅和记住当前值),但每次使用自定义挂钩时,自定义挂钩的所有状态和效果都是完全独立的。

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

    提示:在挂钩之间传递信息

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

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