Hook API 理念篇

336 阅读4分钟

本篇文章会介绍Hook API,为了接下来对React底层的解析。

什么是Hook

Hook是一些可以让你在函数组件里钩入React state以及生命周期等特性的函数。

Hook 动机

Hook解决了下面这个问题:

在组件之间复用状态逻辑很难

这里会涉及自定义Hook,用来从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。

由于React抽象层组成的组件会形成“嵌套地狱”,所以React需要为共享状态的逻辑提供更好的原生途径。

复杂组件变得难以理解

这里会涉及Effect Hook,用来解决组件中的状态逻辑和副作用。每个生命周期常常包含一些不相关的逻辑。

为了解决这个问题,Hook将组件中相关联的部分拆分成更小的函数,而并非按照生命周期划分。

难以理解的Class

使用者不仅要区分函数组件和class组件的使用场景,还要对class不是很好压缩,并且会使热重载出现不稳定的情况进行处理。

为了解决这些问题,Hook使你在非class的情况下使用更多的React特性。

渐进策略

没有计划从React当中移除Class。Hook和现有的代码可以同时工作,可以渐进式的使用他们。

State Hook

下面是一个计数器例子:

import React, { useState } from 'react';
function Example() {
  // 声明一个叫 “count” 的 state 变量。  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

代码中的useState就是一个Hook,他会返回一对值:当前状态和让你更新这个状态的函数。

这个函数类似于class中的this.setState,但是不会将新的state和旧的state合并。

useState参数只有一个,代表初始state,这里的初始state是0。也可以给state初始值定义成对象,这需要看你的需求了。

Effect Hook

我们可能在React组件中执行过数据获取、订阅,或者手动修改过DOM。这些操作被统一称为"副作用"。

而 useEffect 这个Effect Hook,给函数增加了操作副作用的能力。跟class组件中的componentDidMount、componentDidUpdate、componentWillUnmount具有相同的用途,只不过被合并成一个API了。

例如,我们在React 更新了 DOM 之后,修改标题:

import React, { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);

  // 相当于 componentDidMount 和 componentDidUpdate:  
  useEffect(() => {    
      // 使用浏览器的 API 更新页面标题    
      document.title = `You clicked ${count} times`;  
  });
  return (
    <div>
      <div>{document.title}</div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

当调用useEffect的时候,就是通知React在完成对DOM的更改之后运行这个“副作用”函数。React会在每次渲染之后调用副作用函数,包括第一次渲染。

副作用函数可以通过返回一个函数,用来指定如何清除“副作用”。

通过使用Hook,我们可以将组件内相关的副作用组织在一起。而不是将他们拆分到不同的生命周期函数里。

自定义Hook

有时候我们想要在组件之间重用一些状态逻辑,目前有两种 高阶组件render props。而自定义Hook可以让你在不增加组件的情况下达到我们的目的。

比如我们有一个组件来知道好友是否在线,他通过调用useState和useEffect的状态来知道好友在线的状态。

import React, { 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';
}

如果另一个组件里面要重用这部分逻辑,怎么办呢?

首先,把这部分逻辑抽取出来,我们把他抽取到我们自己定义的 useFriendStatus Hook中。

import React, { 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;
}

将friendID作为参数,返回好友是否在线的状态。

这样我们就可以在多个组件中使用它了,比如下面这两个组件:

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

虽然用的是同一个自定义Hook,但是每个组件的state是独立的。所以Hook复用的是逻辑本身,他不复用state状态。也就是说每次调用都会有一个独立的 state,所以你也可以在一个组件中多次调用。

什么是自定义Hook?

他更像是一种约定,而不是功能。如果函数名称用use开头,并且调用了其他Hook,我们可以说它是一个自定义的Hook。

Hook 使用规则

两个额外的规则:

  • 只能在函数最外层调用,不要在循环、条件判断或者子函数中使用。
  • 只能在React函数组件中使用。

后序会继续介绍Hook规则,这里先简单介绍这两个。

总结

本篇文章是对Hook诞生的理解,什么是 Hook? 为什么会存在 Hook? Hook 能做到什么?