React Hooks 從認識到使用_hooks優缺點

前言

HookReact 16.8 的新增特性。它是完全可選的,並且100%向後兼容它可以讓你使用函數組件的方式,運用類組件以及 react 其他的一些特性,比如管理狀態生命週期鉤子等。從概念上講,React 組件一直更像是函數。而 Hook 則擁抱了函數,同時也沒有犧牲 React 的精神原則。

優點

1、代碼可讀性更強,原本同一塊功能的代碼邏輯被拆分在了不同的生命週期函數中,容易使開發者不利於維護和迭代,通過 React Hooks 可以將功能代碼聚合,方便閱讀維護。例如,每個生命週期中常常會包含一些不相關的邏輯。一般我們都會在 componentDidMountcomponentDidUpdate獲取數據。但是,同一個 componentDidMount 中可能也包含很多其它的邏輯,如設置事件監聽,而之後需在 componentWillUnmount 中清除。相互關聯且需要對照修改的代碼被進行了拆分,而完全不相關代碼卻在同一個方法組合在一起。如此很容易產生 bug,並且導致邏輯不一致
2、組件樹層級變淺。在原本的代碼中,我們經常使用 HOC/render props 等方式來複用組件的狀態,增強功能等,無疑增加了組件樹層數及渲染,在 React DevTools 中觀察過 React 應用,你會發現由 providersconsumers高階組件render props 等其他抽象層組成的組件會形成“嵌套地獄”。而在 React Hooks 中,這些功能都可以通過強大的自定義的 Hooks 來實現。
3、不用再去考慮 this 的指向問題。在類組件中,你必須去理解 JavaScript 中 this 的工作方式。

缺點
對一些鉤子函數不支持。當下 v16.8 的版本中,還無法實現 getSnapshotBeforeUpdatecomponentDidCatch 這兩個在類組件中的生命週期函數。

Hook 規則

  • 不在循環,條件或嵌套函數中調用 Hook, 確保總是在你的 React 函數的最頂層調用他們。
  • 不在普通的 JavaScript 函數中調用 Hook,在 React 的函數組件或者自定義 Hook 中調用 Hook。

Hook API

名稱 描述
useState 在函數組件中維護自己的狀態
useEffect 在函數組件中實現生命週期鉤子函數
useContext 用來處理多層級傳遞數據的方式,減少組件嵌套
useReducer 跟react-redux的使用方式一樣,算是提供一個 mini 的 Redux 版本
useCallback 獲得一個記憶函數,避免在某些情況下重新渲染子組件,用來做性能優化
useMemo 獲得一個記憶組件,和useCallback非常類似,它適用於返回確定的值
useRef 生成對 DOM 對象的引用,它是一個真正的引用,而不是把值拷過去
useImperativeHandle 透傳ref,用於讓父組件獲取子組件內的引用
useLayoutEffect 同步執行副作用,在頁面完全渲染完成後,操作DOM

useState

在類組件中,我們使用 this.state來保存組件狀態,並對其修改觸發組件重新渲染。而在函數組件中,由於沒有 this 這個黑魔法,可能通過 useState 來幫我們保存組件的狀態。

  • useState(),返回一個 state,以及更新 state 的函數。
  • useState() 中第一個參數是值或者對象,初始渲染期間,返回的狀態 (state) 是傳入的第一個參數相同。
  • 如果依賴於先前的state,在第二個參數中接收先前的 state,並返回一個更新後的值。
import React, { useState } from "react";
function App() {
  const [count, setCount] = useState(0);	//0是count的默認值
  return (
    <div className="App">
      Count: {count}
      <button onClick={() => setCount((preState=>preState+1))}>+</button>
      <button onClick={() => setCount(0)}>還原</button>
    </div>
  );
}

注意:與類組件中的 setState 方法不同,useState 不會自動合併更新對象。當第一個參數是一個對象時,你可以用函數式的 setState 結合展開運算符來達到合併更新對象的效果。

import React, { useState } from "react";
function App1(){
  const [obj,setObj]=useState({
    name:'admin',
    age:18
  })
  return(
    <div>
      姓名:{obj.name}-年齡:{obj.age}
      <button onClick={()=>setObj({...obj,name:obj.name+"用戶"})}>只修改姓名</button>
    </div>
  )
}

useEffect

語法:useEffect(fn,Array)

  • 第一個參數傳遞函數,可以用來做一些副作用比如異步請求,修改外部參數等行爲。
  • 第二個參數是個數組,數組中的值發生變化纔會觸發 useEffect 第一個參數中的函數。
  • 如果第二個參數是個空數組的話,默認會在頁面加載後執行一次
  • 如果第一個參數有返回值,會在組件銷燬或者調用函數前調用。
  • 可以使用useEffect模擬componentDidMountcomponentDidMountcomponentWillUnmount鉤子函數。
import React, { useState, useEffect } from "react";
function App(){
	const [count, setCount] = useState(0);
	useEffect(()=>{
	    //只要count有變化,就會執行這裏
	},[count])
	
	useEffect(()=>{
	    //如果在下面沒有參數的話,頁面加載後執行,執行一次
	    return()=>{
	        //頁面退出的時候執行
	    }
	},[])
}

useContext

用來處理多層級傳遞數據的方式,使用 useContext 可以解決 Consumer 多狀態嵌套的問題

import React, { useContext } from "react";

const colorContext = React.createContext("gray");
function Bar() {
  const color = useContext(colorContext);
  return <div>{color}</div>;
}
function Foo() {
  return <Bar />;
}
function App() {
  return (
    <colorContext.Provider value={"red"}>
      <Foo />
    </colorContext.Provider>
  );
}

useReducer

useReducer 這個 Hooks 在使用上幾乎跟 React-Redux 一模一樣,唯一缺少的就是無法使用 redux 提供的中間件,算是提供一個 mini 的 Redux 版本。

import React, { useReducer } from "react";
const initialState = {
  count: 0
};
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.payload };
    case "decrement":
      return { count: state.count - action.payload };
    default:
      throw new Error();
  }
}
function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "increment", payload: 5 })}>
        +
      </button>
      <button onClick={() => dispatch({ type: "decrement", payload: 5 })}>
        -
      </button>
    </>
  );
}

useCallback

通過 useCallback 獲得一個記憶後的函數,避免函數組件在每次渲染的時候如果有傳遞函數的話,重新渲染子組件。用來做性能優化。

import React, { useCallback } from "react";
function App() {
  const memoizedHandleClick = useCallback(() => {
    console.log('Click happened')
  }, []); // 空數組代表無論什麼情況下該函數都不會發生改變
  return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}

useMemo

記憶組件,和useCallback類似,不同的是:useCallback 不會執行第一個參數函數,而是將它返回給你,而 useMemo 會執行第一個函數並且將函數執行結果返回給你。所以在前面的例子中,可以返回 handleClick 來達到存儲函數的目的。

所以 useCallback 常用記憶事件函數,生成記憶後的事件函數並傳遞給子組件使用。而 useMemo 更適合經過函數計算得到一個確定的值,比如記憶組件。

import React, { useMemo } from "react";
function App() {
  const memoizedHandleClick = useMemo(() => () => {
    console.log('Click happened')
  }, []); // 空數組代表無論什麼情況下該函數都不會發生改變
  return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}

useRef

跟 createRef 類似,都可以用來生成對 DOM 對象的引用。不同點在於,它是一個真正的引用,而不是把值拷過去。

import React, { useState, useRef } from "react";
function App() {
  let [name, setName] = useState("Nate");
  let nameRef = useRef();
  const submitButton = () => {
    setName(nameRef.current.value);
  };
  return (
    <div className="App">
      <p>{name}</p>

      <div>
        <input ref={nameRef} type="text" />
        <button type="button" onClick={submitButton}>
          Submit
        </button>
      </div>
    </div>
  );
}

useImperativeHandle

透傳ref,用於讓父組件獲取子組件內的引用。

import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react";
function ChildInputComponent(props, ref) {
  const inputRef = useRef(null);
  useImperativeHandle(ref, () => inputRef.current);
  return (
    <div>
      <input type="text" name="child input" ref={inputRef} placeholder="我聚焦了"/>
      <input type="text" name="child input1" placeholder="我沒有聚焦"/>
    </div>
  )
}
const ChildInput = forwardRef(ChildInputComponent);
function App() {
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return (
    <div>
      <ChildInput ref={inputRef} />
    </div>
  );
}

useLayoutEffect

大部分情況下,使用 useEffect 就可以幫我們處理組件的副作用,但是如果想要同步調用一些副作用,比如對 DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用會在 DOM 更新之後同步執行。

import React, { useState, useEffect, useLayoutEffect } from "react";
function App() {
  const [width, setWidth] = useState(0);
  useLayoutEffect(() => {
    const title = document.querySelector("#title");
    const titleWidth = title.getBoundingClientRect().width;
    console.log("useLayoutEffect");
    if (width !== titleWidth) {
      setWidth(titleWidth);
    }
  });
  useEffect(() => {
    console.log("useEffect");
  });
  return (
    <div>
      <h1 id="title">hello</h1>
      <h2>{width}</h2>
    </div>
  );
}

在上面的例子中,useLayoutEffect 會在 render,DOM 更新之後同步觸發函數,會優於 useEffect 異步觸發函數。

參考資料:https://react.docschina.org/docs/hooks-reference.html
https://github.com/happylindz/blog/issues/19

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