React Hook

React Hook

Hook 是一些可以让你在函数组件里 “ 钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。

hook出现的原因

  1. 避免地狱式嵌套,可读性提高。
  2. 函数式组件,比class更容易理解。
  3. class组件生命周期太多太复杂,使函数组件存在状态。
  4. 解决HOC和Render Props的缺点。
  5. UI 和 逻辑更容易分离。

hook使用规则

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
  • Hook 在 class 内部是起作用的。但你可以使用它们来取代 class 。

State Hook

  • useState: 通过在函数组件里调用它来给组件添加一些内部 state。React 会在重复渲染时保留这个 state。useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并。

    import React, { useState } from 'react';
    
    function Example() {
      // 声明一个叫 “count” 的 state 变量。
      const [count, setCount] = useState(0);
      const [fruit, setFruit] = useState('banana');
      const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
      return (
        <div>
          <p> You clicked {count} times </p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }
    
  • 对比State变量 在class和函数组件的区别。正常非class组件会被称作无状态组件,但是现在这些非Class组件中可也使用Hook来添加state。所以我们称之为函数组件。下面对比一下两个的区别。

    // class组件
    class Example extends Component {
       state = {
          count: 0
        };
    	render(){
            return(
            // 读取 state
              <p>You clicked {this.state.count} times</p>
            // 更新 state
              <button onClick={() => this.setState({ count: this.state.count + 1 })}>
                Click me
              </button>
            )
        }
     }
    
    // 函数组件
    import React, { useState } from 'react';
    function Example() {
      // 声明一个叫 “count” 的 state 变量,0是初始值
      const [count, setCount] = useState(0);
      return{
         // 读取state
         <p>You clicked {count} times</p>
         // 更新state\
         <button onClick={() => setCount(count + 1)}>
            Click me
         </button>
      }
    }
    

Effect Hook

  • useEffect: 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 API。可以多次调用

  • 每次REACT重新渲染的时候(包括第一次)都会自动调用useEffect,类似挂载和更新组件的函数作用

  • 可以通过返回一个函数来指定如何 “清除” useEffect,每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起

  • 与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。

    无需清除的Effect示例

    import React, { useState, useEffect } from 'react';
    
    function Example() {
      const [count, setCount] = useState(0);
    
      // 相当于 componentDidMount 和 componentDidUpdate:如果在类组件中完成这个功能需要使用两个生命周期函数才可以实现这个功能,useEffect不用考虑是挂载还是更新
        /* 注:不能这么写,这不是个类组件,只是对比一下两个的区别
      componentDidMount() {
        document.title = `You clicked ${this.state.count} times`;
      }
      componentDidUpdate() {
        document.title = `You clicked ${this.state.count} times`;
      }
        */
      useEffect(() => {
        // 使用浏览器的 API 更新页面标题
        document.title = `You clicked ${count} times`;
      });
      
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }
    

    需要清除的Effect示例

    • 但由于添加和删除订阅的代码的紧密性,所以 useEffect 的设计是在同一个地方执行。如果你的 effect 返回一个函数,React 将会在执行清除操作时调用它

    例如,在下面的组件中使用副作用函数来订阅好友的在线状态,并通过取消订阅来进行清除操作,在这个示例中,React 会在组件销毁时取消对 ChatAPI 的订阅,然后在后续渲染时重新执行副作用函数。(如果传给 ChatAPIprops.friend.id 没有变化,你也可以[告诉 React 跳过重新订阅]。

    import React, { useState, useEffect } from 'react';
    // 已经被改写为一个自定义Hook
    function useFriendStatus(props) {
      const [isOnline, setIsOnline] = useState(null);
    
      function handleStatusChange(status) {
        setIsOnline(status.isOnline);
      }
      // 在 React class 中,你通常会在 componentDidMount 中设置订阅,并在 componentWillUnmount 中清除它
        /* 注:不能这么写,这不是个类组件,只是对比一下两个的区别
      componentDidMount() {
        ChatAPI.subscribeToFriendStatus(
          this.props.friend.id,
          this.handleStatusChange
        );
      }
      componentWillUnmount() {
        ChatAPI.unsubscribeFromFriendStatus(
          this.props.friend.id,
          this.handleStatusChange
        );
      }
        */
        
    
      useEffect(() => {
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        return () => {
            //通过返回一个函数来指定如何“清除”useEffect
          ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
      });
        return isOline;
    /*
      if (isOnline === null) {
        return 'Loading...';
      }
      return isOnline ? 'Online' : 'Offline';
      */
    }
    

自定义Hook

  • 有一些需要重用的逻辑,可以写在自定义Hook中,上面的代码不包括注释部分已经被改写为一个自定义Hook,这样可以在多个组件中使用它。

  • 自定义 Hook 更像是一种约定而不是功能。如果函数的名字以 “use” 开头并调用其他 Hook,我们就说这是一个自定义 Hook。

  • **在两个组件中使用相同的 Hook 会共享 state 吗?**不会。自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全隔离的。

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

维护使用规则的插件

ESLint

npm install eslint-plugin-react-hooks --save-dev

配置package.json

// 你的 ESLint 配置
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则
    "react-hooks/exhaustive-deps": "warn" // 检查 effect 的依赖
  }
}

参考资料:https://react.docschina.org/docs/hooks-effect.html


版权声明:本文为NumbSilver原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。