React Hooks的學習筆記

"Unlearn what you have learned" -- Yodajavascript

前言

有一天我在逛Medium的時候,忽然發現了一篇介紹React Hooks的文章,我認真看了一遍後,計劃好好了解一下它。html

在學習Hooks以前,官網上說了Hooks是徹底可用的(v16.8.0),並無破壞性變動,並且徹底向後兼容,與其說是一種新API,不如說是React Team他們把React更核心的操做數據與UI的能力挖掘了出來。java

嗯美滋滋~學完應該能夠在工做項目裏用了!開始學習吧!react

Hooks的起步使用

其實Hooks主要經常使用的能夠有如下幾個:redux

  • useState
  • useEffect
  • useContext
  • useMemo
  • useRef
  • useReducer
  • useCallback

列舉的以上這幾個,其實已經算是比較經常使用的,尤爲是前兩個,接下來就會介紹它們部分幾個的使用。數組

useState

useState這個鉤子其實對應的是咱們以前class Component裏的this.setState緩存

  1. useState傳參表明默認值,能夠是原始值,也能夠是對象、數組,因此其實表達能力很豐富。
  2. useState調用後返回是一對值,對應當前的值更新這個值的函數,用數組解構的方式獲取很簡潔。
  3. useState在一個函數組件裏能夠屢次使用。
  4. useStatethis.setState區別之處在於,前者每次更新後state都是新值,換而言之實際上是不可變數據的概念。然後者使用後,其實更新state部分的值,引用自己並沒有改變。

簡單使用以下示例。bash

import React, { useState } from 'react';

export default function StateHook() {
  const [count, useCount] = useState(0);
  return (
    <> <p>You clicked {count} times</p> <button onClick={() => useCount(count + 1)}>Click me</button> </> ); } 複製代碼

useEffect

useEffect這個鉤子勢必是咱們經常使用的。架構

  1. 它基本能夠等價於componentDidMountcomponentDidUpdate的這兩個生命週期鉤子組合的效果。那麼它的調用時機大概是每次渲染結束後,因此不會阻塞組件渲染。
  2. useEffect通常用於實現設置數據請求、監聽器等有反作用的功能,傳入的第一個參數函數A1用於設置反作用,而是傳入的這個函數能夠返回一個函數A2用於取消函數A1的反作用。這兩個函數的React調用它們時機分別在於,註冊反作用的函數A1在當次渲染結束後當即執行,取消反作用的函數A2在下次渲染開始以前當即執行。再次強調,這麼設計的理由仍是爲了避免阻塞組件渲染。
  3. useEffect第二個參數用於設置反作用的依賴數組。什麼意思?思惟靈活的同窗已經想到了,若是每次渲染都執行反作用,有可能形成性能浪費,那麼能夠經過告訴React,這個鉤子依賴某些props或者states,在這些依賴不發生改變時,這個反作用不會再重複執行。在如下的例子中,能夠傳空數組,告訴React該反作用什麼也不依賴,那麼它只會在第一次渲染時執行一次(可是通常不推薦這麼作)。若是不傳第二個參數,則意味着每次渲染都必然執行一次,此時應當注意內存泄露。
  4. 同窗們有沒有發現,使用useEffect後,一個反作用的註冊監聽與對應的取消註冊邏輯所有放在了一塊兒,對比與以往的分別在componentDidMountcomponentDidUpdatecomponentWillUnmount裏分散同一反作用的邏輯。useEffect的使用更有吸引力和說服力了。
import React, { useState, useEffect } from 'react';

export default function EffectHook({ dep }) {
  const [width, setWidth] = useState(window.innerWidth);
  
  function handleWindowResize() {
    const w = window.innerWidth;
    setWidth(w);
  }
  
  useEffect(() => {
    window.addEventListener('resize', handleWindowResize);
    return () => {
      window.removeEventListener('resize', handleWindowResize);
    };
  }, 
    // deps
    []
  );

  return (
    <> <p>window.innerWidth: {width}</p> </> ); } 複製代碼

useContext

這個鉤子仍是和原有的Context.ProviderContext.Consumer同樣的理解便可。用法示例以下,理解方便,再也不贅述。app

import React, { useContext } from 'react';

export const souliz = {
  name: 'souliz',
  description: 'A normal human named by his cat.'
};

export const UserContext = React.createContext(souliz);

export default function ContextHook() {
  const context = useContext(UserContext);

  return (
    <>
      <p>UserContext name: {context.name}</p>
      <p>UserContext description: {context.description}</p>
    </>
  );
}
複製代碼

useMemo

有時候咱們會遇到一個極耗性能的函數方法,但因爲依賴了函數組件裏一些狀態值,又不得不放在其中。那麼若是咱們每次渲染都去重複調用的發,組件的渲染必然會十分卡頓。

所以寫了如下示例驗證,一個計算斐波那契的函數(衆所周知的慢),讀者能夠拷貝這段代碼,註釋useMemo那一行,使用直接計算來看,點擊按鈕觸發組件從新渲染,會發現很卡頓(固然了),那麼此時useMemo做用就發揮出來了,其實理解上仍是和原有的React.memo同樣,可用於緩存一下計算緩慢的函數,若是依賴沒有發生改變,則重複使用舊值。前提必然是這個函數是一個純函數,不然必然會引起問題。

useCallback其實也和useMemo道理相似,不過它解決的問題其實若是依賴不改變,使用舊的函數引用,在useEffect的依賴是函數時,可使用useCallback的特性來避免重複觸發反作用的發生,所以再也不贅述useCallback

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

let fib = n => (n > 1 ? fib(n - 1) + fib(n - 2) : n);
let renders = 0;

export default function MemoHook() {
  const defaultInput = 37;
  const [input, setInput] = useState(defaultInput);
  const [time, setTime] = useState(0);
  const value = useMemo(() => fib(input), [input]);
  // 來來來,看看不使用Memo的後果就是卡頓
  // const value = fib(input);

  return (
    <>
      <p>fib value is {value}</p>
      <input
        type="number"
        value={input}
        onChange={e => setInput(e.target.value)}
      />
      <button onClick={() => setTime(time + 1)}>Trigger render {time}</button>
      <footer>render times: {renders++}</footer>
    </>
  );
}
複製代碼

useRef

useRef這個鉤子須要更通用的理解方式,不一樣於咱們以前使用的React.createRef(),這個鉤子用於建立的是一個引用對象,那麼能夠用於突破useState所帶來的侷限。什麼意思呢?useState每次渲染都是新的值,也就是下面示例中,若是我點擊3次按鈕,分別更新了值觸發5次組件從新渲染,那麼經過延時5秒後獲取current值如示例二,若是須要在某些操做中獲取組件最新的某些state是最新的值的時候,useRef能夠派上大用場。

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

export default function RefHook() {
  const [count, setCount] = useState(0);
  const latestCount = useRef(count);

  latestCount.current = count;
  useEffect(() => {
    setTimeout(() => {
      console.log(`Ref: You clicked ${latestCount.current} times`);
      console.log(`state: You clicked ${count} times`);
    }, 5000);
  });

  return (
    <> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </> ); } 複製代碼
Ref: You clicked 3 times
state: You clicked 1 times
Ref: You clicked 3 times
state: You clicked 2 times
Ref: You clicked 3 times
state: You clicked 3 times
複製代碼

useReducer

相信同窗們都使用過redux,React Team考慮到這種使用方式常見,因而設計出來了這麼一個鉤子。這樣的話其實解決了咱們常見寫redux的多文件跳躍編寫的煩惱,並且十分易於理解。(固然還有比較高級的用法)。如下代碼示例。

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

const defaultTodos = [
  {
    id: 1,
    text: 'Todo 1',
    completed: false
  },
  {
    id: 2,
    text: 'Todo 2',
    completed: false
  }
];

function todosReducer(state, action) {
  switch (action.type) {
    case 'add':
      return [
        ...state,
        {
          id:  Date.now(),
          text: action.text,
          completed: false
        }
      ];
    case 'complete':
      return state.map(todo => {
        if (todo.id === action.id) {
          todo.completed = true;
        }
        return todo;
      });
    default:
      return state;
  }
}

export default function ReducerHook() {
  const [todos, dispatch] = useReducer(todosReducer, defaultTodos);
  const [value, setValue] = useState('');

  function handleTextChange(e) {
    setValue(e.target.value);
  }

  function handleAddTodo() {
    if (value === '') {
      return;
    }
    dispatch({
      type: 'add',
      text: value
    });
    setValue('');
  }

  function handleCompleteTodo(id) {
    dispatch({
      type: 'complete',
      id
    });
  }

  return (
    <>
      <section>
        <input
          type="text"
          onChange={handleTextChange}
          value={value}
        />
        <button onClick={handleAddTodo}>Add Todo</button>
      </section>
      <ul className="todos">
        {todos.map(todo => (
          <ol id={todo.id} key={todo.id}>
            <span
              style={{
                textDecoration: todo.completed ? 'line-through' : 'none'
              }}
            >
              {todo.text}
            </span>
            <input
              type="checkbox"
              disabled={todo.completed}
              onClick={() => handleCompleteTodo(todo.id)}
            />
          </ol>
        ))}
      </ul>
    </>
  );
}
複製代碼

其實useReducer的原理大概也能夠這麼來實現。

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }

  return [state, dispatch];
}
複製代碼

相信學完這些Hooks的使用後,許多同窗都是心裏充滿了不少疑惑的同時也想要嘗試看看怎麼使用到實際項目了。

固然如今React官方的建議是:

  • 能夠小規模的使用了,可是無需重寫之前的組件實現。React是不會移除class Component這些原有API的。
  • 若是決定使用Hooks的話,能夠加上React提供的eslint-plugin-react-hooks,用於檢測對於Hooks的不正當使用。(據說create-react-app很快將會加上這個配置)
  • 學習與使用React Hooks其實更須要的是換一種心智模型去理解,Hooks更多的像是一個同步處理數據的過程。

Hooks存在的意義以及緣由?

傳統組件的開發有如下幾個侷限:

  1. 難以複用含有state(狀態)的組件邏輯。HOC、render props這兩種作法雖然能夠解決,可是一是須要從新架構組件,可能會使代碼更復雜。二是可能會形成wrapper hell。
  2. 複雜組件難以理解消化。由於狀態邏輯、消息訂閱、請求、以及反作用在不一樣的生命鉤子混亂穿插,彼此耦合,使得一個組件難以再細化拆分。即便使用了Redux這種狀態管理的庫後,也引進了更高層的抽象,同時須要在不一樣的文件之間穿插跳躍,複用組件也不是一件容易的事。
  3. class讓人困惑。(先別急着反對)一個是this讓人困惑,經常須要綁定、第二是class轉譯和壓縮出來的代碼其實至關冗長。

Hooks的注意事項

  1. 只能在函數的頂層使用,不能嵌套於循環體、判斷條件等裏面。緣由是由於須要確保Hooks每次在組件渲染中都是按照一樣的順序,這個十分重要,具體緣由將會是一個很大的篇幅
  2. 只能在React函數組件裏,或者自定義鉤子(custom Hooks)裏使用。

總結

寫到這裏,文章篇幅已經很長了。一篇文章是說不完Hooks的。學習Hooks的最推薦的實際上是看官網文檔以及Dan Abramov的博文,以及多多動手實踐。

謝謝你們閱讀~~

相關文章
相關標籤/搜索