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í)行任務(wù)、手動修改ReactDOM這些行為都可以稱為副作用晴埂;而Hooks的出現(xiàn)可以使用 useEffect 來處理這些副作用究反;
3. 有狀態(tài)的邏輯重用組件
4. 復(fù)雜的狀態(tài)管理:之前通常使用 redux、dva儒洛、mobx 這些第三方狀態(tài)管理器來管理復(fù)雜的狀態(tài)精耐,而Hooks可以使用 useReducer、useContext 配合實現(xiàn)復(fù)雜的狀態(tài)管理琅锻;
5. 開發(fā)效率和質(zhì)量問題:函數(shù)式組件比類組件簡潔卦停,效率高,性能也好恼蓬。

useState

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

import { useState } from 'react'
const [state, setState] = useState(initState)
  • state:管理組件的狀態(tài)惊完;
  • setState:更新state的方法,方法名不可更改处硬!
  • initState:初始的state小槐,可以是任意的數(shù)據(jù)類型(只在初次渲染時有效,二次渲染時會被忽略)荷辕,也可以是回調(diào)函數(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)的方法是不會合并更新對象的蟆盐,可以結(jié)合展開運算符來達到合并更新對象的效果:
    setState(prevState => {
        return { ...prevState, ...updateValues };
    });
    
  3. useState當然可以多次調(diào)用承边,從而定義多個狀態(tài)值;但不管調(diào)用多少次石挂,相互之間都是獨立的博助,不會相互污染。這就是為什么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能準確找到它對應(yīng)的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的值淮逻,導(dǎo)致報錯
    
    鑒于此,React規(guī)定:
    • Hooks必須寫在函數(shù)的最外層阁簸,不能在循環(huán)語句爬早、條件判斷、子函數(shù)中調(diào)用启妹;
    • 只能在React的函數(shù)式組件筛严、自定義Hooks中調(diào)用Hooks,不能在其他JavaScript函數(shù)中調(diào)用饶米。

useEffect

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

  • callback 回調(diào)函數(shù),作用是處理副作用的邏輯伊滋,可以返回一個函數(shù)碳却,用作清理副作用;
    import { useEffect } from 'react'
    useEffect(() => {
        ......//副作用處理
        return () => {
            ......//清理副作用的清除函數(shù)
        }
    }, [])
    
    為防止內(nèi)存泄漏笑旺,清除函數(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可以反復(fù)多次使用带兜,相互獨立绍哎。所以合理的做法是,給每一個副作用加一個單獨的useEffect鉤子鞋真。這樣一來,這些副作用不再全堆在class組件的生命周期鉤子里沃于,代碼變得更加清晰涩咖;

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

useContext

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

  • 創(chuàng)建一個Context文件 InfoContext.js
    // 設(shè)置了默認值 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的一個增強體锌畸,用于處理復(fù)雜的狀態(tài)管理勇劣,靈感來源于Reduxreducer
useState 內(nèi)部就是基于 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ù)粘咖,當它被調(diào)用時,reducer函數(shù)也會被調(diào)用侈百,同時根據(jù)action去更新state瓮下,action是一個描述操作的對象,如 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í)行高開銷的計算(計算緩存)胀葱;適用于復(fù)雜的計算場景,如復(fù)雜的列表渲染笙蒙,對象深拷貝...

  • 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本質(zhì)上就是封裝好的勾子函數(shù),在自定義Hooks時郭膛,最需要關(guān)心的就是性能、重復(fù)渲染這些問題;
  2. 自定義一個Effect Hooks耘柱,把可以復(fù)用的邏輯抽離出來如捅,變成一個個可插拔的插銷调煎;
  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) {
         //調(diào)用自定義Hooks
        const isOnline = useFriendStatus(props.friend.id);
        if (isOnline === null) {
           return 'Loading...';
        }
        return isOnline ? 'Online' : 'Offline';
    }
    

Hooks的使用規(guī)則

  1. 只在頂層調(diào)用Hooks
    • Hooks的調(diào)用盡量只在頂層作用域
    • 不要在循環(huán)寓辱、條件或嵌套函數(shù)中調(diào)用Hook,否則可能會無法確保每次組件渲染時都以相同的順序調(diào)用Hook
  2. 只在函數(shù)組件調(diào)用Hooks:目前只支持函數(shù)式組件赤拒,未來版本Hooks會擴展到class類組件;
  3. React Hooks的應(yīng)用場景:函數(shù)式組件挎挖、自定義Hooks
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市肋乍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌敷存,老刑警劉巖墓造,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異锚烦,居然都是意外死亡觅闽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門涮俄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛉拙,“玉大人,你說我怎么就攤上這事彻亲≡谐” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵苞尝,是天一觀的道長畸肆。 經(jīng)常有香客問我,道長宙址,這世上最難降的妖魔是什么轴脐? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上大咱,老公的妹妹穿的比我還像新娘恬涧。我一直安慰自己,他們只是感情好碴巾,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布溯捆。 她就那樣靜靜地躺著,像睡著了一般餐抢。 火紅的嫁衣襯著肌膚如雪现使。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天旷痕,我揣著相機與錄音碳锈,去河邊找鬼。 笑死欺抗,一個胖子當著我的面吹牛售碳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绞呈,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼贸人,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了佃声?” 一聲冷哼從身側(cè)響起艺智,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤圾亏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后志鹃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡曹铃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了陕见。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡直撤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蜕着,到底是詐尸還是另有隱情红柱,我是刑警寧澤蓖乘,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站嘉抒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏些侍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一岗宣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧耗式,春花似錦胁住、人聲如沸刊咳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至跷坝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間探孝,已是汗流浹背誉裆。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留足丢,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓斩跌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親耀鸦。 傳聞我的和親對象是個殘疾皇子啸澡,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容