Hooks

HooksReact v16.8 的新特性,可以在不使用類組件的情況下,使用 state 以及其他的React特性;

  • Hooks是完全可選的,無需重寫任何已有代碼就可以在一些組件中嘗試Hook
  • React v16.8發(fā)布,100%向后兼容,Hooks不包含任何破壞性改動.
  • React也沒有計劃移除class類組件,而且Hooks不會影響對React的理解,它為已知的React概念提供了更直接的API

Hooks解決的問題
1. 函數(shù)式組件不能使用state:函數(shù)式組件比類組件更簡潔好用,而Hooks讓它更加豐富強大;
2. 副作用問題:諸如數(shù)據(jù)獲取、訂閱、定時執(zhí)行任務、手動修改ReactDOM這些行為都可以稱為副作用;而Hooks的出現(xiàn)可以使用 useEffect 來處理這些副作用;
3. 有狀態(tài)的邏輯重用組件
4. 復雜的狀態(tài)管理:之前通常使用 redux、dva、mobx 這些第三方狀態(tài)管理器來管理復雜的狀態(tài),而Hooks可以使用 useReducer、useContext 配合實現(xiàn)復雜的狀態(tài)管理;
5. 開發(fā)效率和質量問題:函數(shù)式組件比類組件簡潔,效率高,性能也好。

useState

useState:組件狀態(tài)管理的鉤子

import { useState } from 'react'
const [state, setState] = useState(initState)
  • state:管理組件的狀態(tài);
  • setState:更新state的方法,方法名不可更改!
  • initState:初始的state,可以是任意的數(shù)據(jù)類型(只在初次渲染時有效,二次渲染時會被忽略),也可以是回調函數(shù),但函數(shù)必須有返回值。
  1. 函數(shù)式組件實現(xiàn)計數(shù)器
    import { useState } from 'react'
    export default function App() {
        const [count, setCount] = useState(0)  // 初始值0
        return (
            <div>
                <div>點擊了{ count }次</div>
                <button onClick={()=>setCount(count+1)}>點擊</button>
            </div>
        ) 
    }
    
  2. useState讓函數(shù)式組件具備了管理狀態(tài)的能力,不再是一個無狀態(tài)組件;與classsetState類似,當向useState更新狀態(tài)的方法setCount傳遞一個函數(shù)時,此函數(shù)會接收到上一次的狀態(tài):setCount(prevCount => prevCount + 1)
    但與classsetState不同的是,如果狀態(tài)值是一個對象,useState更新狀態(tài)的方法是不會合并更新對象的,可以結合展開運算符來達到合并更新對象的效果:
    setState(prevState => {
        return { ...prevState, ...updateValues };
    });
    
  3. useState當然可以多次調用,從而定義多個狀態(tài)值;但不管調用多少次,相互之間都是獨立的,不會相互污染。這就是為什么React否掉了Mixins,因為Mixins機制讓多個Mixins共享一個對象的數(shù)據(jù)空間,這樣就很難保證不同Mixins依賴的狀態(tài)不發(fā)生沖突;
    function App() {
        const [age, setAge] = useState(42);
        const [fruit, setFruit] = useState('banana');
        const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
    }
    
  4. React又是如何保證多個useState的相互獨立呢,定義時并沒有告訴React這些值的key,React如何保證這三個useState能準確找到它對應的state---> 根據(jù)useState出現(xiàn)的順序!
    第一次渲染
        useState(42);  //將age初始化為42
        useState('banana');  //將fruit初始化為banana
        useState([{ text: 'Learn Hooks' }]); //...
    第二次渲染
        useState(42);  //讀取狀態(tài)變量age的值(這時候傳的參數(shù)42直接被忽略)
        useState('banana');  //讀取狀態(tài)變量fruit的值(這時候傳的參數(shù)banana直接被忽略)
        useState([{ text: 'Learn Hooks' }]); //...
    
    加入條件控制語句
         let showFruit = true;
         function App() {
             const [age, setAge] = useState(42);
             if(showFruit) {
                 const [fruit, setFruit] = useState('banana');
                 showFruit = false;
             }
             const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
         }
    
    這樣一來,首次渲染的初始化過程是不變的,但第二次渲染就有所不同了
        第二次渲染
            useState(42);  //讀取狀態(tài)變量age的值(這時候傳的參數(shù)42直接被忽略)
            // useState('banana');
            useState([{ text: 'Learn Hooks' }]);  //讀取到的卻是狀態(tài)變量fruit的值,導致報錯
    
    鑒于此,React規(guī)定:
    • Hooks必須寫在函數(shù)的最外層,不能在循環(huán)語句、條件判斷、子函數(shù)中調用;
    • 只能在React的函數(shù)式組件、自定義Hooks中調用Hooks,不能在其他JavaScript函數(shù)中調用。

useEffect

useEffect(callback, array):副作用處理的鉤子;它也是componentDidMount()、componentDidUpdate()、componentWillUnmount()、這幾個生命周期方法的統(tǒng)一,一個頂三個!React 會等待瀏覽器完成畫面渲染之后才會延遲調用 useEffect,而生命周期鉤子是同步執(zhí)行的

  • callback 回調函數(shù),作用是處理副作用的邏輯,可以返回一個函數(shù),用作清理副作用;
    import { useEffect } from 'react'
    useEffect(() => {
        ......//副作用處理
        return () => {
            ......//清理副作用的清除函數(shù)
        }
    }, [])
    
    為防止內存泄漏,清除函數(shù)會在組件卸載前執(zhí)行;如果組件多次渲染,則在執(zhí)行下一個 effect 之前,上一個 effect 就已被清除
  • array 可選數(shù)組,用于控制useEffect的執(zhí)行; 省略時,每次渲染都會執(zhí)行; 空數(shù)組時,只會在組件掛載/卸載時執(zhí)行一次,類似componentDidMount、componentWillUnmount; 非空數(shù)組時,會在數(shù)組元素發(fā)生改變后執(zhí)行,且這個變化的比較是淺比較
    提供第二個參數(shù)時,相當于告訴React 該組件不依賴于state/props 的變化,只依賴第二個參數(shù)的變化。
    function App() {
        const [count, setCount] = useState(0)  // 初始值0
        useEffect(()=>{
            console.log('1')  // 初次渲染時執(zhí)行一次,之后每次 count 變化都執(zhí)行
            return () => {
                console.log('2')  // 初次渲染時不執(zhí)行,之后每次 count 變化都最先執(zhí)行
            }
        }, [count])
        return (<div>
             <div>點擊了{ count }次</div>
             <button onClick={() => setCount(preCount => preCount+1)}>點擊</button>
        </div>) 
    }
    // 初次渲染:1
    // 點擊更新count:2 1
    
    父組件也可以通過props控制子組件是否執(zhí)行useEffect
     useEffect(()=>{
         // 注冊事件
         const subscription = props.source.subscribe();
         return ()=>{
             // 解綁事件
             subscription.unsubscribe();
         }
     }, [props.source]);
    

如前文所述,Hooks可以反復多次使用,相互獨立。所以合理的做法是,給每一個副作用加一個單獨的useEffect鉤子。這樣一來,這些副作用不再全堆在class組件的生命周期鉤子里,代碼變得更加清晰;

useEffect中定義的副作用函數(shù)在執(zhí)行時不會阻礙瀏覽器更新視圖,即這些函數(shù)是異步執(zhí)行的,而class組件中的生命周期鉤子都是同步執(zhí)行的;異步設計對大多數(shù)副作用是合理的,但也有特例,比如有時候需要先根據(jù)DOM計算出某個元素的尺寸,然后再去渲染,此時則希望二次渲染是同步發(fā)生的,也就是在瀏覽器真的去繪制界面前發(fā)生;
為此,React 為此提供了一個額外的 useLayoutEffect Hook 來處理這類 effect。它和 useEffect 的結構相同,區(qū)別只是調用時機不同 -- useLayoutEffect 是同步的。

useContext

useContext():同一個父組件的后臺組件之間的全局數(shù)據(jù)共享;
useContext() 接收React.createContext()的返回值作為參數(shù),即context對象,并返回最近的context。當最近的context更新時,使用該contextHooks 將會重新渲染;

  • 創(chuàng)建一個Context文件 InfoContext.js
    // 設置了默認值 defaultValue
    const InfoContext = React.createContext({ name: 'Jerry', age: 18 })
    export default InfoContext
    
  • 父組件
    import InfoContext from './context/InfoContext'
    const Person = () => {
        const ctx = useContext(InfoContext)
        return(<InfoContext.Provider value={{ username: 'superman' }}>
            <div>
                <AgeCompt></AgeCompt>
            </div>
        </InfoContext.Provider>)
    }
    export default Person
    
  • 某一層的后代組件
    import { useContext } from 'react'
    import InfoContext from './context/InfoContext'
    const AgeCompt = () => {
        const { username } = useContext(InfoContext)
        return <p>{username}</p>
    }
    export default AgeCompt
    

useReducer

useReducer()useState的一個增強體,用于處理復雜的狀態(tài)管理,靈感來源于Reduxreducer
useState 內部就是基于 useReducer 實現(xiàn)的,只是對于簡單的狀態(tài)管理,useState()比較好用;

    const [state, setState] = useState(initState)

    const [state, dispatch] = useReducer(reducer, initState, initAction)
  • reducer -- 一個函數(shù),根據(jù)action狀態(tài)處理并更新state
  • initState --初始化state
  • initAction -- useReducer()初次執(zhí)行時被處理的action,會把第二個參數(shù)initState當作參數(shù)執(zhí)行
  • state -- 狀態(tài)值
  • dispatch -- 更新state的方法,接收action作為參數(shù),當它被調用時,reducer函數(shù)也會被調用,同時根據(jù)action去更新stateaction是一個描述操作的對象,如 dispatch({type: 'add'})
    import { useReducer } from 'react';
    const initState = { count: 0 };
    const initAction = initState => { count: initState.count + 2 };
    const reducer = (state, action) => {
        switch(action.type) {
            case 'ADD':
                return { count: state.count+1 }
            case 'DEL':
                return { count: state.count-1 }
            case 'RESET':
                return initState
            default:
                return state
        }
    }
    export default function UserCompt() {
        const [state, dispatch] = useReducer(reducer, initState)
        return (<div>
            <p>{state.count}</p>
            <div>
                <button onClick={() => dispatch({ type: 'ADD', /**可以加一些其他屬性*/ })}>增加</button>
                <button onClick={() => dispatch({ type: 'DEL' })}>減少</button>
                <button onClick={() => dispatch({ type: 'RESET' })}>重置</button>
            </div>
        </div>)
    }
    

useRef

useRef():創(chuàng)建ref,方便訪問操作DOM

    const RefCompt = () => {
        //創(chuàng)建ref
        const inputRef = useRef();
        const getValue = () => {
            //訪問ref
            const inpt = inputRef.current;  // input的DOM對象
            inpt.focus();  // 讓 input 框獲取焦點
            console.log(inpt.value);  // input輸入框的值
        };
        //掛載
        return (<div>
            <input ref={ inputRef } type="text" />
            <button onClick={ getValue }>獲取值</button>
        </div>);
    }

當然,useRef 不只是能指向 DOM

// 可變的 ref
const App = () => {
    const intervalRef = useRef<number>(0);

    setEffect(() => {
        intervalRef.current = setInterval(() => {}, 300);
        return () => clearInterval(intervalRef.current);
    });
    return <div></div>
}

注意:與 useState 不同的是,useRef 會實時更新數(shù)據(jù),但不會觸發(fā)組件的更新!

useMemo

useMemo(callback, array):性能優(yōu)化,利用了閉包的特性,通過記憶值來避免在每個渲染上都執(zhí)行高開銷的計算(計算緩存);適用于復雜的計算場景,如復雜的列表渲染,對象深拷貝...

  • callback - 用于處理邏輯的函數(shù)
  • array - 依賴項,控制useMemo()重新執(zhí)行的數(shù)組,array元素改變時才會重新執(zhí)行useMemo()
  • 返回值是一個記憶值,也是callback的返回值
         import React, { useMemo } from 'react';
         export default function UserCompt() {
             const obj1 = { name: 'Tom', age: 15 }
             const obj2 = { name: 'Jerry', age: 18, sex: '男' }
             //合并obj1、obj2
             const memoValue = useMemo(() => Object.assign(obj1, obj2), [obj1, obj2])
             return (<div>
                 <p>{ memoValue.name }</p>
                 <p>{ memoValue.age }</p>
             </div>)
         }
    

注意:不能在 useMemo() 中處理副作用邏輯,而是把副作用處理邏輯放在useEffect()

useCallback

useCallback(callback, array):也是用于性能優(yōu)化,與useMemo()不同的是,返回值是callback本身;

    // 合并obj1、obj2
    const backValue = useCallback(() => Object.assign(obj1, obj2), [obj1, obj2])

    <div>{ backValue().name } --- { backValue().age }</div>

當依賴項變化時,返回一個新的函數(shù)體callback。

自定義Hooks

  1. Hooks本質上就是封裝好的勾子函數(shù),在自定義Hooks時,最需要關心的就是性能、重復渲染這些問題;
  2. 自定義一個Effect Hooks,把可以復用的邏輯抽離出來,變成一個個可插拔的插銷;
  3. 當標題變化時,則修改標題、否則不執(zhí)行的Hooks
    import { useEffect } from 'react'
    
    //封裝Hooks,以 use 開頭
    const useChangeTitle = (title) => {
        useEffect(() => {
            document.title = title
        }, [title])
    }
    export default (props) => {
        useChangeTitle("自定義修改標題Hooks")
        return <div>測試</div>
    }
    
  4. 一個用來判斷某個 id 是否在線的Hooks
    import { useState, useEffect } from 'react';
    function useFriendStatus(friendID) {
        const [isOnline, setIsOnline] = useState(null);
        useEffect(()=>{
            //注冊監(jiān)聽事件
            ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
            return () => {
              //取消監(jiān)聽事件
              ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
            };
        });
        return isOnline;
    }
    
    在組件中使用此Hooks
    function FriendStatus(props) {
         //調用自定義Hooks
        const isOnline = useFriendStatus(props.friend.id);
        if (isOnline === null) {
           return 'Loading...';
        }
        return isOnline ? 'Online' : 'Offline';
    }
    

Hooks的使用規(guī)則

  1. 只在頂層調用Hooks
    • Hooks的調用盡量只在頂層作用域
    • 不要在循環(huán)、條件或嵌套函數(shù)中調用Hook,否則可能會無法確保每次組件渲染時都以相同的順序調用Hook
  2. 只在函數(shù)組件調用Hooks:目前只支持函數(shù)式組件,未來版本Hooks會擴展到class類組件;
  3. React Hooks的應用場景:函數(shù)式組件、自定義Hooks
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容