React Hooks

Hooks

Hooks是React v16.7.0-alpha的新特新,可以與react state相結合, 使函數組件功能類似於class一樣。但是在Class類內部,不可以使用hooks.
React 提供了幾個像useState,useEffect, useContext 等的內置鉤子函數,我們也可以自定義鉤子函數在組件之間來複用state。

useState(內置hook)

import {useState} from 'reaact';
function Example(){
const [count, setCount]=useState(0);
return(
	<div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
)}

在函數組件內部,不存在指向函數自身的this, 因此不可以聲明或者調用this.state.
useState hooks提供給函數等同於class內部this.state的作用。state變量通過react進行維護。
useState 是一個HOOK, 在函數內部調用,可以給函數本身添加state,react會保存並渲染頁面。useState返回一組值:當前State, 和更新state的方法,這個方法可以在函數內部或其他地方調用。與react this.setState方法類似,但是不同的是,它並非將舊值與新值進行合併得到新值.

useState只接受一個參數,即state的初始值,eg. useState(0), 0即爲state的初始值。並且只在初次渲染時調用。與this.state不同的是,hook 的state不必一定爲一個對象,但如果想定義爲對象也是可以的。

定義多個State變量

function ManyStates(){
	const [age,setAge]=useState(42);
	const [fruit,setFruit]=useState('orange');
	const [todos,setTodos]=useState([{text:'learn hooks'}])

數組的解構賦值使我們可以定義多個變量。如果我們多次調用useState,react會同時進行編譯。

Effect Hook(內置hook)

在react 組件內部,我們通常會進行一些數據獲取,提交,或手動改變DOM狀態的操作, 這種對其他環境有影響或依賴的又稱爲:side effect(副作用), Effect Hook: useEffect ,提供函數組件同樣的功效,作用等同於class組件內部的componentDidMount, componentDidUpdate, componentWillUnmount,只是將這些統一成單一API。

import {useState, useEffect} from 'react';
function Eample(){
	const [count,setCount]=useState(0);
	// Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });
  return (
  	<div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
)
}

當調用useEffect,即告訴React在DOM變化後執行’effect’方法。Effects在組件內部聲明,因此可以讀取組件內的props, state。默認情況下,react會在每次render後都去執行effects裏的方法,也包括第一次render.
Effects也可以通過返回一個函數選擇性的指定如何’clean up’當函數unMount時。
eg.

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

上例中,React在組件卸載時會調用ChatAPI的unsubscribe方法,同樣在隨後的render中也會調用。(如果props.friend.id沒有變化,你也可以告訴React忽略re-subscribing,如果你想。)

與useState類似,你也可以使用多個useEffect在函數組件中:

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  // ...

上例中,多個useEffect裏面傳入的函數是彼此獨立的,由此可以保證effect裏面讀取的state值將會是最新的,在網頁每次重新編譯後,我們執行一個新的effect,替代前面的state值。useEffect並不會阻塞瀏覽器更新頁面,它們爲異步執行。在某些特殊情況下需要同步執行,例如計算頁面佈局,這時可以使用useLayoutEffect Hook 替代useEffect. useLayoutEffect在DOM計算完成後同步執行,與componentDidMount, componentDidUpdate執行時間類似,在不確定用哪種effect時,使用useLayoutEffect風險較小。

Hooks可以將組件中相關的生命週期函數寫在一起,而不是將它們分開在不同的生命週期函數中。

Effects without cleanup

在class組件中我們執行side effects在componentDidMount/componentDidUpdate中,如果我們想執行同一方法在組件每次render之後,必須將相同方法在didMount, didUpdate兩個階段都調用一次。
在Hooks中我們只需要在useEffects中將此方法寫一次即可每次render後都進行調用。
默認情況下,useEffect在首次編譯和每次更新數據都會執行,即每次編譯後都執行。我們也可以進行優化:忽略某些編譯。
在Class組件中,componentDidUpdate可以通過prevProps,prevState來阻止update裏函數的執行eg.

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

在useEffect Hook API中內置了這種比較,你可以向useEffect(()=>{}, [compareState])傳入第二個參數:state數組。eg.

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

只有count值發生改變-上次render和此次render count值不同,react纔會忽略此次render,不執行effect裏的方法。
這個也同樣作用於 含有clean up 的effects

useEffect(() => {
  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // Only re-subscribe if props.friend.id changes

如果使用這個優化,請確保數組內包含的值是從外部作用域獲取,並且會隨着時間變化,effect裏面也會使用的。否則你effect裏的代碼將會從之前的編譯中取值。
如果你想執行一個effect和clean up 僅一次,你可以將useEffect第二個參數傳入一個空數組[], 類似componentDidMount, componentWillUnmount.

Effects with Cleanup
useEffects第一個參數會返回一個函數作爲cleanup 執行的方法。會在unmount,和每次re-render時執行。

useContext(內置hook)

const context=useContext(Context);

class組件中context使用,會將組件包含在context.provider && context.consumer之間,consumer之間的組件會以render props的模式(傳遞一個函數作爲children)來獲得provider傳遞的參數,如下

import React from "react";
import ReactDOM from "react-dom";

// Create a Context
const NumberContext = React.createContext();
// It returns an object with 2 values:
// { Provider, Consumer }

function App() {
  // Use the Provider to make a value available to all
  // children and grandchildren
  return (
    <NumberContext.Provider value={42}>
      <div>
        <Display />
      </div>
    </NumberContext.Provider>
  );
}

function Display() {
  // Use the Consumer to grab the value from context
  // Notice this component didn't get any props!
  return (
    <NumberContext.Consumer>
      {value => <div>The answer is {value}.</div>}
    </NumberContext.Consumer>
  );
}

ReactDOM.render(<App />, document.querySelector("#root"));

在useContext hook 中,我們可以使用useContext(Context)-傳入的參數爲通過React.createContext獲得的context對象,直接獲取provider傳遞的值,改寫consumer組件如下:

// import useContext (or we could write React.useContext)
import React, { useContext } from 'react';

// ...

function Display() {
  const value = useContext(NumberContext);
  return <div>The answer is {value}.</div>;
} 

如果consumer組件中有多層嵌套獲得多個provider值,那麼使用hook將會更簡單:

function HeaderBar() {
  return (
    <CurrentUser.Consumer>
      {user =>
        <Notifications.Consumer>
          {notifications =>
            <header>
              Welcome back, {user.name}!
              You have {notifications.length} notifications.
            </header>
          }
      }
    </CurrentUser.Consumer>
  );
}

//useContext改寫如下:
function HeaderBar() {
  const user = useContext(CurrentUser);
  const notifications = useContext(Notifications);

  return (
    <header>
      Welcome back, {user.name}!
      You have {notifications.length} notifications.
    </header>
  );
}

useReducer Hook(內置)

Hooks規則

  1. 不要在循環,條件,或嵌套函數中使用,應該在頂層的React function中使用。由此確保在組件編譯過程中,Hooks以相同的順序進行調用,這保證了在衆多useState, useEffect中React正確保存state狀態。
  2. Hooks只能在React 函數組件調用,常規函數不能調用。
  3. eslint-plugin-react-hooks插件可以強化前兩條規則: npm install eslint-plugin-react-hooks@next
// ------------
// First render
// ------------
useState('Mary')           // 1. Initialize the name state variable with 'Mary'
useEffect(persistForm)     // 2. Add an effect for persisting the form
useState('Poppins')        // 3. Initialize the surname state variable with 'Poppins'
useEffect(updateTitle)     // 4. Add an effect for updating the title

// -------------
// Second render
// -------------
useState('Mary')           // 1. Read the name state variable (argument is ignored)
useEffect(persistForm)     // 2. Replace the effect for persisting the form
useState('Poppins')        // 3. Read the surname state variable (argument is ignored)
useEffect(updateTitle)     // 4. Replace the effect for updating the title

// ...

自定義Hook

在傳統React class組件中,如果組件之間公用大部分邏輯和頁面,通常會通過props傳值, 或使用高階組件的方式來返回不同組件。
在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;
}`

useFriendStatus主要用於通過傳入id返回online狀態,所有組件共用同一個Hook,state也是相互獨立的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章