React Hooks 解析(上):基礎

歡迎關注個人公衆號睿Talk,獲取我最新的文章:
clipboard.pngjavascript

1、前言

React Hooks 是從 v16.8 引入的又一開創性的新特性。第一次瞭解這項特性的時候,真的有一種豁然開朗,發現新大陸的感受。我深深的爲 React 團隊天馬行空的創造力和精益求精的鑽研精神所折服。本文除了介紹具體的用法外,還會分析背後的邏輯和使用時候的注意事項,力求作到知其然也知其因此然。java

這個系列分上下兩篇,這裏是下篇的傳送門:
React Hooks 解析(下):進階react

2、Hooks 的由來

Hooks的出現是爲了解決 React 長久以來存在的一些問題:redux

  • 帶組件狀態的邏輯很難重用

爲了解決這個問題,須要引入render propshigher-order components這樣的設計模式,如react-redux提供的connect方法。這種方案不夠直觀,並且須要改變組件的層級結構,極端狀況下會有多個wrapper嵌套調用的狀況。segmentfault

Hooks能夠在不改變組件層級關係的前提下,方便的重用帶狀態的邏輯。設計模式

  • 複雜組件難於理解

大量的業務邏輯須要放在componentDidMountcomponentDidUpdate等生命週期函數中,並且每每一個生命週期函數中會包含多個不相關的業務邏輯,如日誌記錄和數據請求會同時放在componentDidMount中。另外一方面,相關的業務邏輯也有可能會放在不一樣的生命週期函數中,如組件掛載的時候訂閱事件,卸載的時候取消訂閱,就須要同時在componentDidMountcomponentWillUnmount中寫相關邏輯。數組

Hooks能夠封裝相關聯的業務邏輯,讓代碼結構更加清晰。網絡

  • 難於理解的 Class 組件

JS 中的this關鍵字讓很多人吃過苦頭,它的取值與其它面嚮對象語言都不同,是在運行時決定的。爲了解決這一痛點,纔會有剪頭函數的this綁定特性。另外 React 中還有Class ComponentFunction Component的概念,何時應該用什麼組件也是一件糾結的事情。代碼優化方面,對Class Component進行預編譯和壓縮會比普通函數困可貴多,並且還容易出問題。app

Hooks能夠在不引入 Class 的前提下,使用 React 的各類特性。函數

3、什麼是 Hooks

Hooks are functions that let you 「hook into」 React state and lifecycle features from function components

上面是官方解釋。從中能夠看出 Hooks 是函數,有多個種類,每一個 Hook 都爲Function Component提供使用 React 狀態和生命週期特性的通道。Hooks 不能在Class Component中使用。

React 提供了一些預約義好的 Hooks 供咱們使用,下面咱們來詳細瞭解一下。

4、State Hook

先來看一個傳統的Class Component:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

使用 State Hook 來改寫會是這個樣子:

import React, { useState } from 'react';

function Example() {
  // 定義一個 State 變量,變量值能夠經過 setCount 來改變
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

能夠看到useState的入參只有一個,就是 state 的初始值。這個初始值能夠是一個數字、字符串或對象,甚至能夠是一個函數。當入參是一個函數的時候,這個函數只會在這個組件初始渲染的時候執行:

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

useState的返回值是一個數組,數組的第一個元素是 state 當前的值,第二個元素是改變 state 的方法。這兩個變量的命名不須要遵照什麼約定,能夠自由發揮。要注意的是若是 state 是一個對象,setState 的時候不會像Class Component的 setState 那樣自動合併對象。要達到這種效果,能夠這麼作:

setState(prevState => {
  // Object.assign 也能夠
  return {...prevState, ...updatedValues};
});

從上面的代碼能夠看出,setState 的參數除了數字、字符串或對象,還能夠是函數。當須要根據以前的狀態來計算出當前狀態值的時候,就須要傳入函數了,這跟Class Component的 setState 有點像。

另一個跟Class Component的 setState 很像的一點是,當新傳入的值跟以前的值同樣時(使用Object.is比較),不會觸發更新。

5、Effect Hook

解釋這個 Hook 以前先理解下什麼是反作用。網絡請求、訂閱某個模塊或者 DOM 操做都是反作用的例子,Effect Hook 是專門用來處理反作用的。正常狀況下,在Function Component的函數體中,是不建議寫反作用代碼的,不然容易出 bug。

下面的Class Component例子中,反作用代碼寫在了componentDidMountcomponentDidUpdate中:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

能夠看到componentDidMountcomponentDidUpdate中的代碼是同樣的。而使用 Effect Hook 來改寫就不會有這個問題:

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

useEffect會在每次 DOM 渲染後執行,不會阻塞頁面渲染。它同時具有componentDidMountcomponentDidUpdatecomponentWillUnmount三個生命週期函數的執行時機。

此外還有一些反作用須要組件卸載的時候作一些額外的清理工做的,例如訂閱某個功能:

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }

  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}

componentDidMount訂閱後,須要在componentWillUnmount取消訂閱。使用 Effect Hook 來改寫會是這個樣子:

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 function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

useEffect的返回值是一個函數的時候,React 會在下一次執行這個反作用以前執行一遍清理工做,整個組件的生命週期流程能夠這麼理解:

組件掛載 --> 執行反作用 --> 組件更新 --> 執行清理函數 --> 執行反作用 --> 組件更新 --> 執行清理函數 --> 組件卸載

上文提到useEffect會在每次渲染後執行,但有的狀況下咱們但願只有在 state 或 props 改變的狀況下才執行。若是是Class Component,咱們會這麼作:

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}

使用 Hook 的時候,咱們只須要傳入第二個參數:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 只有在 count 改變的時候才執行 Effect

第二個參數是一個數組,能夠傳多個值,通常會將 Effect 用到的全部 props 和 state 都傳進去。

當反作用只須要在組件掛載的時候和卸載的時候執行,第二個參數能夠傳一個空數組[],實現的效果有點相似componentDidMountcomponentWillUnmount的組合。

6、總結

本文介紹了在 React 以前版本中存在的一些問題,而後引入 Hooks 的解決方案,並詳細介紹了 2 個最重要的 Hooks:useStateuseEffect的用法及注意事項。原本想一篇寫完全部相關的內容,但發現坑有點深,只能分兩次填了:)

相關文章
相關標籤/搜索