擁抱 React Hooks

React Hooks

爲何須要Hooks?

咱們知道,React 提供的單向數據流以及組件化幫助咱們將一個龐大的項目變爲小型、獨立、可複用的組件。但有時,咱們沒法進一步拆分很複雜的組件,由於它們內部的邏輯是有狀態的,沒法抽象爲函數式組件。因此有時咱們可能會寫出很是不適合複用性開發的:react

  • 巨大的組件 難以重構
  • 重複的邏輯 須要在多個組件的多個生命週期中寫重複的代碼
  • 複雜的應用模式 相似於 render props 於 高階組件

但謝天謝地,Hooks 的出現,讓咱們把組件內部的邏輯組織成爲了可複用的隔離單元api

Hooks 要解決的問題:

跨組件地複用包含狀態的邏輯,經過 Hooks 能夠將含有 state 的邏輯從組建抽象出來,同時也能夠幫助咱們在不重寫組件結構的狀況下複用邏輯。Hooks 通常是用於函數式組件的,在類class組件中無效。讓咱們根據代碼的做用將它們拆分,而不是生命週期。簡而言之, Hooks 實現了咱們在函數式組件中使用狀態變量相似於生命週期的操做。數組

使用 Hooks 的語法規則

  • 只能在頂層調用鉤子。不在循環、控制流和嵌套的函數中調用鉤子。
  • 只能從React的函數式組件中調用鉤子。不在常規的JS函數中調用鉤子。

建立Hooks

  • 使用useState建立Hook
import {useState} from 'react';

function hooks(){
    // 聲明一個名爲 count 的新狀態變量
    const [count, setCount] = useState(0);
    // 第二個參數 setCount 爲一個能夠更新狀態的函數
    // useState 的參數即爲初始值
    
    return (
        <div>
        	<p>當前的狀態量爲: {count}</p>
            <button onClick={() => setCount(count + 1)}>點擊加一</button>
        </div>
    )
}
複製代碼
  • 使用 useEffect 來執行相應操做
import {useState, useEffect} from 'react';

function hooks(){
    const [count, setCount] = useState(0);
    // 相似於 componentDidMount 和 componentDidUpdate
    // 在 useEffect 中可使用組建的 state 和 props
    // 在每次渲染後都執行 useEffect
    useEffect(() => {
        window.alert(`You have clicked ${count} times`);
    })
    return (
        <div>
        	<p>當前的狀態量爲: {count}</p>
            <button onClick={() => setCount(count + 1)}>點擊加一</button>
        </div>
    )
}
複製代碼

鉤子是獨立的

咱們在兩個不一樣的組件使用同一個鉤子,他們是相互獨立的,甚至在一個組件使用兩個鉤子他們也是相互獨立的。瀏覽器

React如何保證useState相互獨立

React 實際上是根據useState傳出現的順序來保證useState之間相互獨立。緩存

// 首次渲染
const [num, setNum] = useState(1); // 將num初始化爲1
const [str, setStr] = useState('string'); // 將str初始化爲'string'
const [obj, setObj] = useState({id:1}); // ....
// 第二次渲染
const [num, setNum] = useState(1); // 讀取狀態變量num的值, 此時傳入的參數已被忽略,下同
const [str, setStr] = useState('string'); // 讀取狀態變量str的值
const [obj, setObj] = useState({id:1}); // ....
複製代碼

同時正是因爲根據順序保證獨立,因此 React 規定咱們必須把 hooks 寫在最外層,而不能寫在條件語句之中,來確保hooks的執行順序一致,若要進行條件判斷,咱們應該在 useEffect 的函數中寫入條件閉包

Effect Hooks

useEffect 來傳遞給 React 一個方法,React會在進行了 DOM 更新以後調用。咱們一般將 useEffect 放入組件內部,這樣咱們能夠直接訪問 state 與 props。記得,useEffect 在每次 render 後都要調用。異步

須要清理的Effect

咱們有時須要從外部數據源獲取數據,此時咱們就要保證清理Effect來避免內存泄露 ,此時咱們須要在 effect 中返回一個函數來清理它, React 會在組件每次接觸掛載的時候清理。一個比較使用的場景就是咱們在 useEffect中若執行了異步請求,因爲異步的時間不肯定性,咱們很須要在執行下一次異步請求時先結束上一次的請求,所以咱們就須要清理。async

useEffect(() => {
    let canceled = false;
    const getData = async () => {
        const res = await fetch(api);
        if(!canceled) {
            // 展現 res
        }
    }
    
    getData();
    
    // return 的即爲咱們的清理函數
    return () => {
        canceled = true;
    }
});
複製代碼

此時咱們在進行從新渲染時,就能夠避免異步請求帶來的競態問題,從而避免數據的不穩定性。函數

配置根據條件執行的Effect

咱們能夠給useEffect傳入第二個參數只有當第二個參數(數組)裏的全部的state 值發生變化時,才從新執行Effect組件化

useEffect(() => {
    window.alert(`you had clicked ${count} times`);
}, [count]); //只有當 count 發生變化時纔會從新執行effect
複製代碼

在函數式組件使用實例

因爲函數式組件中沒有 this ,因此咱們沒法使用ref,但hooks幫助咱們解決了這個問題,他提供了useRef方法來爲咱們建立一個實例,而傳入的參數會被掛載在這個實例的.current屬性上,返回的實例會持續到整個生命週期結束爲止。

function RefExample() {
    const ref1 = useRef(null);
    return (
    	<div>
            <input ref={ref1} type="text" />
            <button onClick={() => {ref1.current.focus()}}
    	</div>
    )
}
複製代碼

類型的Hooks

若是比起上面的狀態變量類型,你更想要使用 Redux 類型的狀態管理,OK,React 也給咱們提供了useReducer這個方法。做爲useState 的一種替代,咱們可使用dispatch方法來改變狀態變量。

// 初始化的狀態變量
const initState = {count:0};
// 編寫 reducer 處理函數
function reducer(state, action) {
    switch(action.type) {
        case 'increment': return {count: state.count + 1};
        case 'decrement': return {count: state.count - 1};
    }
}

function counter({initState}) {
    const [state, dispatch] = useReducer(reducer, initState);
    return (
    <div>
            <p>Count: {state.count}</p>
            <button onClick={() => dispatch({type: 'increment'})}>+</button>
            <button onClick={() => dispatch({type: 'decrement'})}>-</button>
   	</div>
    )
}
複製代碼

回調形式的Hooks

咱們能夠經過監聽狀態變量並在變換後執行回調函數來執行 Effect ,此時你可能會問,爲何使用 Hooks 會使用這麼多的 inline 函數,豈不是很影響性能? 謝天謝地,JavaScript 中的閉包函數的性能十分的快,它幫助了咱們不少。回調形式的 Hooks 有兩種,useCallbackuseMemo.

兩者的轉換關係爲:

useCallback(fn, inputs) === useMemo(() => fn, inputs)

useCallback是如何幫助咱們提高性能的呢? 實際上,它實際上是緩存了每次渲染時的 inline 回調函數的實例,以後不管是配合shouldComponentUpdate 或者是 React.memo都可以達到減小沒必要要的渲染的做用。這也提示咱們,React.memoReact.useCallback通常是配合使用,缺了其一均可能沒法達到提高性能的功效。

下面以一個表單組件表示使用方法

function FormComponent() {
    const [text, setText] = useState(' ');
    
    const handleSubmit = useCallback(() => {
        console.log(`new test is ${text}`);
    }, [text]);
    
    return (
    	<div>
        	<input value={text} onChange={(e) => setText(e.target.value)} />
            <BigTree onSubmit={handleSubmit} /> // 巨大無比的組件,不優化卡的不行
        </div>
    )
}
複製代碼

但此時有一個很嚴重的問題,就是咱們的 BigTree 依賴於一個太容易變化的 state, 只要咱們在input框隨意輸入, BigTree 就會從新渲染好屢次來獲取最新的callback,此時這個callback就沒法使用緩存了。

一個解決辦法是咱們定義一個新的實例,這個實例只有在 re-render 時纔會更新最新的值,這樣咱們就能夠不根據一個常常變換的state,而是根據一個在 useLayoutEffect中更新的ref實例來更新。

function FormComponent() {
    const [text, setText] = useState(' ');
    const textRef = useRef();
    
    useLayoutEffect(() => {
        textRef.current = text;
    })
    
    const handleSubmit = useCallback(() => {
        console.log(`new test is ${text}`);
    }, [textRef]); // 只根據 textRef 的變化而產生變化,並不會在 text 改變就變化
    
    return (
    	<div>
        	<input value={text} onChange={(e) => setText(e.target.value)} />
            <BigTree onSubmit={handleSubmit} /> // 巨大無比的組件,不優化卡的不行
        </div>
    )
}
複製代碼

Hooks的多重 Effect 更新場景

useLayoutEffect

DOM 突變以後,從新繪製以前同步觸發

它與 useEffect 的做用相同,都是用來執行反作用的,但不一樣的是,它會在全部的 DOM 變動結束後同步地調用 effect。一個與 useEffect很大的區別是,useLayoutEffect是同步地,而useEffect是異步的,在瀏覽器從新繪製頁面佈局前,useLayoutEffect內部的更新將會同步刷新,但官方給出的建議是儘可能使用useEffect來避免阻塞視覺更新。

Hooks 的好處

  • 避免了咱們在複用含狀態組件(classes) 時使用 render props高階組件時產生的

誇張的層級嵌套。

  • 防止咱們爲了實現功能而在生命週期函數中寫入了大量的重複的代碼。
  • classes 中的 this 指向十分的迷惑。
相關文章
相關標籤/搜索