React Hooks 使用詳解及實際項目中遇到的坑

React Hooks 是 React 在16.8版本中更新的新特性,在 React 中一直提倡使用函數組件,老版本中函數組件沒有組件實例,沒有 state,沒有生命週期函數,致使不少狀況不得不使用類組件,可是 Hooks 出來後咱們能夠在不使用類組件的狀況下使用state及其餘React特性!react

一. useState

1. useState基本使用

import React, { useState } from 'react';

function Example() {
  // 聲明一個叫 "count" 的 state 變量
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
複製代碼
  • 這是官方的一個基本使用示例,import引入useState函數
  • 調用函數時傳入初始化的值
  • 函數返回一個數組:一個爲初始化的state,一個爲更新state的函數
  • 最後在其餘地方使用state,或者調用更新state的函數
  • 注意:更新state的函數會直接替換state,而不是像之前setState會合並新老state

2. 使用push,pop,splice等直接更改數組對象的坑

由於useState的更新函數會直接替換老的state,因此咱們在對對象或者數組的state作增刪的時候不能像之前直接對數組使用push,pop,splice等直接改變數組的方法ajax

錯誤示例:redux

import React, { useState } from "react";

function Comment() {
  const [counts, setCounts] = useState([1, 2]);
  const handleAdd = () => {
    const randomCount = Math.round(Math.random()*100)
    // 在此地方咱們使用push增長一個隨機數,程序報錯
    setCounts(counts.push(randomCount))
  }
  return (
    <div>
      {counts.map((count) => (
        <div key={count}>{count}</div>
      ))}
      <button onClick={handleAdd}>增長</button>
    </div>
  );
}

export default Comment;

複製代碼

正確的方法應該是使用數組解構生成一個新數組,在數組後面加上咱們新增的隨機數達成數組新增項,使用filter數組過濾方法來實現咱們刪除其中項的操做。數組

數組新增項:瀏覽器

import React, { useState } from "react";

function Comment() {
  const [counts, setCounts] = useState([1, 2]);
  const handleAdd = () => {
    const randomCount = Math.round(Math.random()*100)
    // 在此咱們用數組結構生成新數組,並在後面加上咱們要新增的隨機數
    setCounts([
      ...counts,
      randomCount
    ])
  }
  return (
    <div>
      {counts.map((count) => (
        <div key={count}>{count}</div>
      ))}
      <button onClick={handleAdd}>增長</button>
    </div>
  );
}

export default Comment;

複製代碼

刪除其中項緩存

import React, { useState } from "react";

function Comment() {
  const [counts, setCounts] = useState([1, 2, 3, 4]);
  const handleDel = () => {
    // 使用數組filter方法,過濾刪除其中不須要的項
    setCounts(counts.filter((count, index) => index !== counts.length - 1))
  }
  return (
    <div>
      {counts.map((count) => (
        <div key={count}>{count}</div>
      ))}
      <button onClick={handleDel}>刪除</button>
    </div>
  );
}

export default Comment;

複製代碼

此外還有一個方法是相似之前使用redux的reducer中對老的數組對象作深拷貝,而後作增刪操做,最後返回性能優化

import React, { useState } from "react";

function Comment() {
  const [counts, setCounts] = useState([1, 2]);
  const handleAdd = () => {
    setCounts(counts => {
      const randomCount = Math.round(Math.random()*100)
      // 簡單使用JSON.parse及JSON.stringify深拷貝一個新的數組和對象(實際項目中建議本身寫遞歸深拷貝函數),而後對其操做返回
      let newCounts = JSON.parse(JSON.stringify(counts))
      newCounts.push(randomCount)
      return newCounts
    })
  }
  return (
    <div>
      {counts.map((count) => (
        <div key={count}>{count}</div>
      ))}
      <button onClick={handleAdd}>增長</button>
    </div>
  );
}

export default Comment;

複製代碼

3. 每次渲染都是獨立閉包的坑

當咱們先執行異步增長函數(handleSyncAdd),再執行同步函數(handleAdd),同步執行完畢再執行異步時,異步函數裏面的count爲以前執行時閉包裏面的值(0),錯誤示例:bash

import React, { useState } from "react";

function Comment() {
  const [count, setCount] = useState(0);
  const handleAdd = () => setCount(count + 1);
  const handleSyncAdd = () => {
    setTimeout(() => {
    // 獲取的是閉包中的state
      setCount(count + 1);
    }, 1000);
  };
  return (
    <div>
      <p>{count}</p>
      <button onClick={handleAdd}>增長</button>
      <button onClick={handleSyncAdd}>異步增長</button>
    </div>
  );
}

export default Comment;

複製代碼

這種狀況咱們要使用回調式函數更新閉包

正確示例:dom

import React, { useState } from "react";

function Comment() {
  const [count, setCount] = useState(0);
  const handleAdd = () => setCount(count + 1);
  const handleSyncAdd = () => {
    setTimeout(() => {
    // 改爲回調函數更新,每次回調函數執行時會接收以前的state,而不是閉包中的state
      setCount(count => count + 1);
    }, 1000);
  };
  return (
    <div>
      <p>{count}</p>
      <button onClick={handleAdd}>增長</button>
      <button onClick={handleSyncAdd}>異步增長</button>
    </div>
  );
}

export default Comment;
複製代碼

2、useEffect

  • effect(反作用),能夠理解爲咱們在使用類組件時的生命週期函數
  • useEffect 能夠實現咱們在類組件中的componentDidMountComponentDidUpdatecomponentWillUnmount的功能呢,只不過被合併成爲一個API
  • componentDidMountcomponentDidUpdate 不一樣的是,使用 useEffect 不會阻塞瀏覽器更新屏幕,這讓你的應用看起來響應更快。大多數狀況下,effect 不須要同步地執行。在個別狀況下(例如測量佈局),有單獨的 useLayoutEffect 供你使用,其 API 與 useEffect 相同。

1. useEffect 實現 componentDidMount及ComponentDidUpdate

直接使用useEffect傳入一個回調函數,會在組件初次渲染及每次更新渲染時執行

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

function Parent() {
  const [count, setCount] = useState(0)
  const handleAdd = () => setCount(count + 1)
  // 使用useEffect傳入一個回調函數使用類組件componentDidMount和componentDidUpdate功能
  useEffect(() => {
    console.log('parent effect');
  })
  return (
    <div>
      parent, {count}
      <button onClick={handleAdd}>增長</button>
    </div>
  )
}

export default Parent

複製代碼

2. 使用useEffect 實現componentDidMount功能

不少時候咱們只須要組件初次加載作一些事,如ajax獲取數據等,咱們只須要在useEfffect的第二個參數傳入一個空數組,這個數組的意思是數組裏面監聽的值發生更新update時執行effect

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

function Parent() {
  const [count, setCount] = useState(0)
  const handleAdd = () => setCount(count + 1)
  // 第二個參數傳入空數組,不須要根據其餘值執行effect,只會在組件初次加載執行
  useEffect(() => {
    console.log('parent didMount');
  }, [])
  return (
    <div>
      parent, {count}
      <button onClick={handleAdd}>增長</button>
    </div>
  )
}

export default Parent

複製代碼

也能夠在第二個數組中傳入值,代表根據這個值update時執行effect

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

function Parent() {
  const [count, setCount] = useState(0)
  const handleAdd = () => setCount(count + 1)
  // 第二個參數傳入含有count的數組,count更新時執行effect
  useEffect(() => {
    console.log('count update');
  }, [count])
  return (
    <div>
      parent, {count}
      <button onClick={handleAdd}>增長</button>
    </div>
  )
}

export default Parent

複製代碼

3.使用useEffect實現componentWillUnmout功能

在項目中咱們須要在組件卸載時清除定時器、監聽等,使用useEffect返回一個函數,這個函數會在組件卸載時調用完成componentWillUnmout的功能

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

function Parent() {
  const [count, setCount] = useState(0)
  const handleAdd = () => setCount(count + 1)
  // 在useEffect中返回一個函數完成componentWillUnmoun的功能
  useEffect(() => {
    console.log('component mount');
    return () => {
      console.log('component unmount');
    }
  })
  return (
    <div>
      parent, {count}
      <button onClick={handleAdd}>增長</button>
    </div>
  )
}

export default Parent

複製代碼

3、useMemo

useMemo能夠初略理解爲Vue中的計算屬性,在依賴的某一屬性改變的時候自動執行裏面的計算並返回最終的值(並緩存,依賴性改變時才從新計算),對於性能消耗比較大的必定要使用useMemo否則每次更新都會從新計算。

示例:

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

function Parent() {
  const [count, setCount] = useState(0)
  const [price, setPrice] = useState(1)
  const handleCountAdd = () => setCount(count + 1)
  const handlePriceAdd = () => setPrice(price + 1)
  // 使用useMemo在count和price改變時自動計算總價
  const all = useMemo(() => count * price, [count, price])
  return (
    <div>
      parent, {count}
      <button onClick={handleCountAdd}>增長數量</button>
      <button onClick={handlePriceAdd}>增長價格</button>
      <p>count: {count}, price: {price} all: {all}</p>
    </div>
  )
}

export default Parent

複製代碼

4、useCallback

useCallback不一樣於useMemo的是,useMemo是緩存的值,useCallback是緩存的函數,父組件給子組件傳遞參數爲普通函數時,父組件每次更新子組件都會更新,可是大部分狀況子組件更新是不必的,這時候咱們用useCallback來定義函數,並把這個函數傳遞給子組件,子組件就會根據依賴項再更新了

示例:

import React, { useState, useCallback, useEffect } from 'react';
function Parent() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        return count;
    }, [count]);
    return <div>
        <h4>{count}</h4>
        <Child callback={callback}/>
        <div>
            <button onClick={() => setCount(count + 1)}>+</button>
            <input value={val} onChange={event => setVal(event.target.value)}/>
        </div>
    </div>;
}
 
function Child({ callback }) {
    const [count, setCount] = useState(() => callback());
    useEffect(() => {
      console.log(123);
        setCount(callback());
    }, [callback]);
    return <div>
        {count}
    </div>
}

export default Parent
複製代碼

5、useReducer

useReducer和redux中reducer相似,useState 的替代方案。它接收一個形如 (state, action) => newState 的 reducer,並返回當前的 state 以及與其配套的 dispatch 方法。(若是你熟悉 Redux 的話,就已經知道它如何工做了。)

在某些場景下,useReducer 會比 useState 更適用,例如 state 邏輯較複雜且包含多個子值,或者下一個 state 依賴於以前的 state 等。而且,使用 useReducer 還能給那些會觸發深更新的組件作性能優化,由於你能夠向子組件傳遞 dispatch 而不是回調函數 。

import React, { useReducer } from 'react'

function Parent() {
  const reducer = (state, action) => {
    switch (action.type) {
      case 'add':
        return {count: state.count + 1}
      case 'reduce':
        return {count: state.count - 1}
      default:
        throw new Error()
    }
  }
  let initialState = 0
  const init = (initialState) => ({
    count: initialState
  })
  // 第三個參數爲惰性初始化函數,能夠用來進行復雜計算返回最終的initialState,若是initialState較簡單能夠忽略此參數
  const [state, dispatch] = useReducer(reducer, initialState, init)
  return (
    <div>
      <p>{state.count}</p>
      <button onClick={() => dispatch({type: 'add'})}>add</button>
      <button onClick={() => dispatch({type: 'reduce'})}>reduce</button>
    </div>
  )
}

export default Parent

複製代碼

6、useContext

useContext能夠實現相似react-redux插件的功能,上層組件使用createContext建立一個context,並使用<MyContext.Provider>來傳遞context,下層組件使用useContext來接收context。

示例:

import React, { useState, createContext, useContext } from "react";

// 使用createContext來建立一個context
const CounterContext = createContext();

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

  return (
    // 父組件使用<MyContext.Provider>傳遞context
    <CounterContext.Provider value={{ count, setCount }}>
      {count}
      <Child />
    </CounterContext.Provider>
  );
}

function Child() {
  // 子組件使用useContext來接收context
  const { count, setCount } = useContext(CounterContext);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>add</button>
    </div>
  );
}

export default Parent;
複製代碼

7、useLayoutEffect

其使用方法與useEffect同樣,但它會在全部的 DOM 變動以後同步調用 effect。可使用它來讀取 DOM 佈局並同步觸發重渲染。在瀏覽器執行繪製以前,useLayoutEffect 內部的更新計劃將被同步刷新。useEffect爲異步,useLayoutEffect爲同步,推薦你一開始先用 useEffect,只有當它出問題的時候再嘗試使用 useLayoutEffect

8、useRef

React Hooks中用來獲取DOM節點

示例:

import React, { useRef } from 'react'

function Parent() {
  // 使用useRef建立一個ref,並在標籤中綁定到ref屬性上
  const pRef = useRef(null)
  return (
    <div>
      <p ref={pRef}>content</p>
    </div>
  )
}

export default Parent
複製代碼

9、自定義Hooks

自定義Hooks能夠實現邏輯複用等,在多個組件中能夠複用咱們自定義的Hooks,而且裏面的狀態是獨立的,自定義Hooks咱們通常按照規則以use開頭定義

示例:

import React, { useState } from "react";

// 自定義useCount的Hooks
function useCount() {
  const [count, setCount] = useState(0);
  return { count, setCount };
}

function Parent() {
  // 父組件使用,狀態獨立
  const { count, setCount } = useCount()
  return (
    <div>
      <p>parent</p>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>add</button>
      <Child />
    </div>
  );
}

function Child() {
  // 子組件使用,狀態獨立
  const { count, setCount } = useCount()
  return (
    <div>
      <p>child</p>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 2)}>add</button>
    </div>
  );
}

export default Parent;
複製代碼

以上就是React Hooks的基本使用詳解,基本上能夠用React Hooks覆蓋咱們類組件的使用,官方也更推薦咱們使用React Hooks來開發新項目!

相關文章
相關標籤/搜索