跟着 React 官方文檔學 Hooks

這篇文章兩個月以前寫的,看了一下官網文檔沒啥變化,就發出來了。若是有什麼錯誤,歡迎指出~html

前言:一直對這個新特性很是感興趣,終於今天有時間,花了大半天時間,把 Hooks的官方教程過了一遍,收穫頗多,驚歎這個新特性真 TM 好用,之後開發用這個怕是要起飛了😆。react

狀態鉤子(State Hook)

const [state, setState] = useState(initialState);
複製代碼
  1. 多個useState時,React依賴於每次渲染時鉤子的調用順序都是同樣的(存在與每一個組件關聯的「存儲單元」的內部列表存放JavaScript對象),從而實現鉤子與狀態的一一對應關係。
  2. setState()接收新的state或者一個返回state的函數(setCount(prevCount => prevCount - 1)})。
  3. 不一樣於類組件中的setStateuseState返回的setState 不會自動合併更新對象到舊的state中(可使用useReducer)。
  4. useState能夠接收一個函數返回initialState,它只會在初次渲染時被調用。
  5. setState中的state和當前的state相等(經過Object.is判斷),將會退出更新。
  6. 建議將一個狀態根據哪些須要值一塊兒變化拆分爲多個狀態變量。
const [rows, setRows] = useState(createRows(props.count));  // `createRows()`每次將會渲染將會被調用
複製代碼

優化一下:git

const [rows, setRows] = useState(() => createRows(props.count));  // `createRows()`只會被調用一次
複製代碼

其中的() => createRows(props.count)會賦值給rows,這樣就保證了只有在rows調用時,纔會建立新的值。npm

做用鉤子(Effect Hook)

useEffect(didUpdate);
複製代碼
  1. 至關於生命週期函數componentDidMount, componentDidUpdate, componentWillUnmount的組合。
  2. 能夠返回一個函數(cleanup)用於清理。
  3. 每次從新渲染都將會發生cleanup phase
useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
複製代碼
componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  // ====== 緣由在這裏 ======
  componentDidUpdate(prevProps) {
    // Unsubscribe from the previous friend.id
    ChatAPI.unsubscribeFromFriendStatus(
      prevProps.friend.id,
      this.handleStatusChange
    );
    // Subscribe to the next friend.id
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
複製代碼
// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange);     // Run first effect

// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange);     // Run next effect

// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange);     // Run next effect

// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // Clean up last effect
複製代碼
  1. useEffect(() => {document.title = You clicked ${count} times;}, [count]); ,指定第二個參數(這裏爲[count])變化時才發生cleanup phase,而後執行effect
  2. 上面狀況,若是useEffect第二個參數爲爲[]則表示只運行一次(componentDidMount中執行effectcomponentWillUnmount中進行cleanup),永遠不從新運行。
  3. componentDidMount/componentDidUpdate有區別的地方在於,useEffect中的函數會在layoutpaint結束後才被觸發。(可使用useLayoutEffect在下一次渲染以前(即 DOM 突變以後)同步觸發)
  4. useEffect雖然被推遲到瀏覽器繪製完成以後,可是確定在有任何新的呈現以前啓動。由於React老是在開始更新以前刷新以前渲染的效果。

其餘鉤子

useContext

const context = useContext(Context);
複製代碼

接受一個上下文對象(由React.createContext建立),返回當前上下文值(由最近的上下文提供)。數組

附加鉤子(Additional Hooks)

基本鉤子的變體或用於特定邊緣狀況的鉤子。瀏覽器

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);
複製代碼
  1. 第三個參數init爲函數,將會這樣調用:init(initialArg),返回初始值。
  2. 若是返回state和如今的state同樣,將會在不影響子孫或者觸發效果的狀況下退出渲染。

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);
複製代碼

傳入一個內聯回調和一個輸入數組,返回一個帶有記憶的函數,只有輸入數組中其中一個值變化纔會更改。useCallback(fn, inputs) 等價於 useMemo(() => fn, inputs)app

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
複製代碼

傳入一個建立函數和一個輸入數組,返回一個帶有記憶的,只有輸入數組中其中一個值變化纔會從新計算。dom

useRef

const refContainer = useRef(initialValue);
// ...
<input ref={refContainer} />
...
複製代碼

返回一個可變的ref對象,能夠自動將ref對象中的current屬性做爲初始值傳遞的參數,保持到組件的整個生命週期。函數

與在類中使用實例字段的方式相似,它能夠保留任何可變值佈局

如保存前一個狀態:

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

  const prevCountRef = useRef();
  useEffect(() => {
    prevCountRef.current = count;
  });
  const prevCount = prevCountRef.current;

  return <h1>Now: {count}, before: {prevCount}</h1>;
}
複製代碼

useImperativeHandle

useImperativeHandle(ref, createHandle, [inputs])
複製代碼

自定在使用 ref 時,公開給父組件的實例值,必須和forwardRef一塊兒使用。

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />; } FancyInput = forwardRef(FancyInput); 複製代碼
<FancyInput ref={fancyInputRef} />

// 調用
fancyInputRef.current.focus()
複製代碼

useLayoutEffect

使用方法和useLayoutEffect一致,不過它是在 DOM 讀取佈局時同步觸發(至關於componentDidMountcomponentDidUpdate階段)。(建議儘量使用useEffect避免阻塞可視化更新)

useDebugValue

useDebugValue(value)
複製代碼

用於在React DevTools中顯示自定義鉤子的標籤,對於自定義鉤子中用於共享的部分有更大價值。

自定義顯示格式:

useDebugValue(date, date => date.toDateString());
複製代碼

鉤子(Hooks)規則

1. 只能在頂層調用,不能再循環、條件語句和嵌套函數中使用。 (緣由:[State Hook](#State Hook) 第1條)

正確作法:

useEffect(function persistForm() {
      // 👍 We're not breaking the first rule anymore
      if (name !== '') {
        localStorage.setItem('formData', name);
      }
    });
複製代碼

2. 只能在React函數組件中被調用。(能夠經過自定義鉤子函數解決)

可使用eslint-plugin-react-hooks來強制自動執行這些規則。

自定義鉤子(Hook)

  1. use開頭,一種公約。
  2. 自定鉤子是一種複用狀態邏輯的機制(例如設置訂閱和記住當前值),每次使用,內部全部狀態和做用都是獨立的。
  3. 自定義鉤子每一個狀態獨立的能力源於useStateuseEffect是徹底獨立的。

測試鉤子(Hook)

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>
  );
}
複製代碼

使用ReactTestUtils.act()

import React from 'react';
import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils';
import Counter from './Counter';

let container;

beforeEach(() => {
  container = document.createElement('div');
  document.body.appendChild(container);
});

afterEach(() => {
  document.body.removeChild(container);
  container = null;
});

it('can render and update a counter', () => {
  // Test first render and effect
  act(() => {
    ReactDOM.render(<Counter />, container); }); const button = container.querySelector('button'); const label = container.querySelector('p'); expect(label.textContent).toBe('You clicked 0 times'); expect(document.title).toBe('You clicked 0 times'); // Test second render and effect act(() => { button.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(label.textContent).toBe('You clicked 1 times'); expect(document.title).toBe('You clicked 1 times'); }); 複製代碼

建議使用react-testing-library

參考

相關文章
相關標籤/搜索