在?React?項(xiàng)目中全量使用?Hooks的方法
前言
此篇文章整理了在 React 項(xiàng)目開(kāi)發(fā)中常用的一些 Hooks
React Hooks
Hooks 只能用于函數(shù)組件當(dāng)中
useState
import { useState } from 'react'; const Component = () => { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}>click</button> ) }
此方法會(huì)返回兩個(gè)值:當(dāng)期狀態(tài)和更新?tīng)顟B(tài)的函數(shù)。效果同 this.state
與 this.setState
,區(qū)別是 useState
傳入的值并不一定要對(duì)象,并且在更新的時(shí)候不會(huì)把當(dāng)前的 state 與舊的 state 合并。
useReducer
useReducer
接收兩個(gè)參數(shù),第一個(gè)是 reducer 函數(shù),通過(guò)該函數(shù)可以更新 state,第二個(gè)參數(shù)為 state 的初始值,是 useReducer
返回的數(shù)組的第一個(gè)值,也是在 reducer 函數(shù)第一次被調(diào)用時(shí)傳入的一個(gè)參數(shù)。
基礎(chǔ)用法
import { useState } from 'react'; const Component = () => { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}>click</button> ) }
在基礎(chǔ)用法中,返回一個(gè) dispatch 通過(guò) dispatch 觸發(fā)不同的 action 來(lái)加減 state。這里既然能傳string
action
那么肯定也能傳遞更復(fù)雜的參數(shù)來(lái)面對(duì)更復(fù)雜的場(chǎng)景。
進(jìn)階用法
import { useReducer } from 'react'; const Component = () => { const [userInfo, dispatch] = useReducer( (state, { type, payload }) => { switch (type) { case 'setName': return { ...state, name: payload }; case 'setAge': return { ...state, age: payload }; } }, { name: 'Jace', age: 18 } ); return ( <button onClick={() => dispatch({ type: 'setName', payload: 'John' })}> click </button> ); };
useContext
在上述案例 useReducer
中,我們將函數(shù)的參數(shù)改為一個(gè)對(duì)象,分別有type
和 payload
兩個(gè)參數(shù),type
用來(lái)決定更新什么數(shù)據(jù),payload
則是更新的數(shù)據(jù)。寫過(guò) react-redux 的同學(xué)可能發(fā)這個(gè) reducer 與 react-redux 中的 reducer 很像,我們借助 react-redux 的思想可以實(shí)現(xiàn)一個(gè)對(duì)象部分更改的 reducer ,那么我們便可以使用 React Hooks 的 useContext
來(lái)實(shí)現(xiàn)一個(gè)狀態(tài)管理。
import { useMemo, createContext, useContext, useReducer } from 'react'; const store = createContext([]); const App = () => { const reducerValue = useReducer( (state, { type, payload }) => { switch (type) { case 'setName': return { ...state, name: payload }; case 'setAge': return { ...state, age: payload }; } }, { name: 'Jace', age: 18 } ); const [state, dispatch] = reducerValue; const storeValue = useMemo(() => reducerValue, reducerValue); return ( <store.Provider value={storeValue}> <Child /> </store.Provider> ); }; const Child = () => { const [state, dispatch] = useContext(store); // 在子組件中使用 console.log(state); return ( <button onClick={() => dispatch({ type: 'setName', payload: 'John' })}> click </button> ); }
useEffect
import { useState, useEffect } from 'react'; let timer = null; const Component = () => { const [count, setCount] = useState(0); // 類似于 class 組件的 componentDidMount 和 componentDidUpdate: useEffect(() => { document.title = `You clicked ${count} times`; timer = setInterval(() => { // events ... }, 1000) return () => { // 類似 componentWillUnmount // unmount events ... clearInterval(timer); // 組件卸載、useEffect 更新 移除計(jì)時(shí)器 }; }, [count]); // ... }
如果 useEffect
第二個(gè)參數(shù)數(shù)組內(nèi)的值發(fā)生了變化,那么useEffect
第一個(gè)參數(shù)的回調(diào)將會(huì)被再執(zhí)行一遍,這里要注意的useEffect 的返回值函數(shù)并不只是再組件卸載的時(shí)候執(zhí)行,而是在這個(gè) useEffect 被更新的時(shí)候也會(huì)調(diào)用,例如上述 count 發(fā)生變化后,useEffect 返回的方法也會(huì)被執(zhí)行,具體原因見(jiàn)Using the Effect Hook – React (reactjs.org)
useLayoutEffect
useLayoutEffect
與 useEffect
的API相同,區(qū)別:useEffect
在瀏覽器渲染后執(zhí)行,useLayoutEffect
在瀏覽器渲染之前執(zhí)行,由于JS是單線程,所以 useLayoutEffect 還會(huì)阻塞瀏覽器的渲染。區(qū)別就是這,那么應(yīng)用場(chǎng)景肯定是從區(qū)別中得到的,useLayoutEffect
在渲染前執(zhí)行,也就是說(shuō)我們?nèi)绻袪顟B(tài)變了需要依據(jù)該狀態(tài)來(lái)操作DOM
,為了避免狀態(tài)變化導(dǎo)致組件渲染,然后更新 DOM
后又渲染,給用戶肉眼能看到的閃爍,我們可以在這種情況下使用 useLayoutEffect
。
當(dāng)然這個(gè)不只是狀態(tài)的改變,在任何導(dǎo)致組件重新渲染,而且又要改變
DOM
的情況下都是useLayoutEffect
的使用場(chǎng)景。當(dāng)然這種場(chǎng)景不多,useLayoutEffect
也不能多用,且使用時(shí)同步操作時(shí)長(zhǎng)不能過(guò)長(zhǎng),不然會(huì)給用戶帶來(lái)明顯的卡頓。
useRef
細(xì)心的同學(xué)有可能發(fā)現(xiàn)我在上面寫 useEffect
中有一個(gè) timer
變量,我將其定義在了函數(shù)組件外面,這樣寫簡(jiǎn)單使用是沒(méi)問(wèn)題的,但是如果該組件在同一頁(yè)面有多個(gè)實(shí)例,那么組件外部的這個(gè)變量將會(huì)成共用的,會(huì)帶來(lái)一個(gè)沖突,所以我們需要一個(gè)能在函數(shù)組件聲明周期內(nèi)部的變量,可以使用 useState
中的 state 但是 state 發(fā)生變化組件也會(huì)隨之刷新,在有些情況是不需要刷新的,只是想單純的存一個(gè)值,例如計(jì)時(shí)器的 timer
以及子組件的 Ref 實(shí)例等等。
import React, { useRef, useState, useEffect } from 'react'; const Compnent = () => { const timer = useRef(null); const [count, setCount] = useState(0); useEffect(() => { clearInterval(timer.current); timer.current = setTimeout(() => { setCount(count + 1); }, 1000); }, [count]); return <div>UseRef count: {count}</div>; }
useRef
只接受一個(gè)參數(shù),就是 初始值,之后可以通過(guò)賦值 ref.current
來(lái)更改,我們可以將一些不影響組件聲明周期的參數(shù)放在 ref 中,還可以將 ref 直接傳遞給子組件 子元素。
const ref = useRef(); <div ref={ref}>Hello</div> // or <Child ref={ref} />
或許有同學(xué)這時(shí)候會(huì)想到,當(dāng)子組件為 Class 組件時(shí),ref 獲取的是 Class 組件的實(shí)例,上面包含 Class 的所有方法屬性等。但當(dāng)子組件為 Function 組件時(shí),ref 能拿到什么,總不可能是 function 內(nèi)定義的方法、變量。
useImperativeHandle
import React, { useRef, useState, useImperativeHandle } from 'react'; const App = () => { const ref = useRef(); return ( <Child ref={ref} /> ); }; const Child = React.forwardRef((props, ref) => { const inputRef = useRef(); const [value, setValue] = useState(1); useImperativeHandle(ref, () => ({ value, // 內(nèi)部變量 setValue, // 方法 input: inputRef.current // Ref })); return ( <input value={value} inputRef={inputRef} /> ); })
使用 useImperativeHandle
鉤子可以自定義將子組件中任何的變量,掛載到 ref 上。React.forwardRef
方法可以讓組件能接收到 ref ,然后再使用或者透?jìng)鞯礁聦印?/p>
useCallback
import React, { useCallback } from 'react'; const Component = () => { const setUserInfo = payload => {}; // request api const updateUserInfo = useCallback(payload => { setUserInfo(Object.assign({}, userInfo, payload)); }, [userInfo]); return ( <UserCard updateUserInfo={updateUserInfo}/> ) }
useCallback 會(huì)在二個(gè)參數(shù)的依賴項(xiàng)發(fā)生改變后才重新更新,如果將此函數(shù)傳遞到子組件時(shí),每次父組件渲染此函數(shù)更新,就會(huì)導(dǎo)致子組件也重新渲染,可以通過(guò)傳遞第二個(gè)參數(shù)以避免一些非必要性的渲染。
useMemo
import React, { useMemo } from 'react'; const Component = () => { const [count, setCount] = useState(0); const sum = useMemo(() => { // 求和邏輯 return sum; }, [count]); return <div>{sum}</div> }
useMemo 的用法跟 useCallback 一樣,區(qū)別就是一個(gè)返回的是緩存的方法,一個(gè)返回的是緩存的值。上述如果依賴值 count 不發(fā)生變化,計(jì)算 sum 的邏輯也就只會(huì)執(zhí)行一次,從而性能。
React Redux Hooks useSelector
import { shallowEqual, useSelector } from 'react-redux'; const Component = () => { const userInfo = useSelector(state => state.userInfo, shallowEqual); // ... }
useSelector 的第二個(gè)參數(shù)是一個(gè)比較函數(shù),useSelector 中默認(rèn)使用的是 ===
來(lái)判斷兩次計(jì)算的結(jié)果是否相同,如果我們返回的是一個(gè)對(duì)象,那么在 useSelector 中每次調(diào)用都會(huì)返回一個(gè)新對(duì)象,所以所以為了減少一些沒(méi)必要的 re-render
,我們可以使用一些比較函數(shù),如 react-redux 自帶的 shallowEqual
,或者是 Lodash 的 _.isEqual()
、Immutable 的比較功能。
useDispatch
import React, { useCallback } from 'react'; import { useDispatch } from 'react-redux'; const Compnent = () => { const dispatch = useDispatch(); const clearUserInfo = useCallback( () => dispatch({ type: 'clearUserInfo' }), [dispatch] ); return ( <button onClick={clearUserInfo}>click</buttn> ) }
使用 dispatch
來(lái)調(diào)度操作,加上useCallback
來(lái)減少不必要的渲染。
React Router Hooks
useHistory
import { useHistory } from 'react-router'; const Compnent = () => { const history = useHistory(); return ( <button onClick={() => history.push('/home')}>go home</buttn> ) }
useLocation
import React, { useEffect } from 'react'; import { useLocation } from 'react-router'; const Compnent = () => { const location = useLocation(); useEffect(() => { // ... }, [location]) }
URL一發(fā)生變化,將返回新的 location
,一般可以用來(lái)監(jiān)聽(tīng) location.search
useParams
import { useParams, useEffect } from 'react-router'; const Component = () => { const params = useParams(); const getUserInfo = id => { // request api // some event }; useEffect(() => { // parms 的 uid 發(fā)生變化就會(huì)重新請(qǐng)求用戶信息 getUserInfo(params.uid); }, [params.uid]); // ... }
useParams 返回 react-router 的參數(shù)鍵值對(duì)
useRouteMatch
import { useRouteMatch } from 'react-router'; const Component = () => { const match = useRouteMatch('/login'); // ... }
useRouteMatch 可以傳入一個(gè)參數(shù) path
,不傳參數(shù)則返回當(dāng)前路由的參數(shù)信息,如果傳了參數(shù)則用來(lái)判斷當(dāng)前路由是否能匹配上傳遞的 path
,適用于判斷一些全局性組件在不同路由下差異化的展示。
參考
結(jié)語(yǔ)
使用 Hooks
能為開(kāi)發(fā)提升不少效率,但并不代表就要拋棄 Class Component
,依舊還有很多場(chǎng)景我們還得用到它,比如需要封裝一個(gè)公共的可繼承的組件,當(dāng)然通過(guò)自定義 hooks 也能將一些共用的邏輯進(jìn)行封裝,以便再多個(gè)組件內(nèi)共用。
下期更新
在React 中自定義 Hooks 的應(yīng)用場(chǎng)景
,主要講一些 Hooks 的高階應(yīng)用
到此這篇關(guān)于在 React 項(xiàng)目中全量使用 Hooks的文章就介紹到這了,更多相關(guān)React使用 Hooks內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react-native-video實(shí)現(xiàn)視頻全屏播放的方法
這篇文章主要介紹了react-native-video實(shí)現(xiàn)視頻全屏播放的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03create-react-app構(gòu)建項(xiàng)目慢的解決方法
這篇文章主要介紹了create-react-app構(gòu)建項(xiàng)目慢的解決方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03使用react render props實(shí)現(xiàn)倒計(jì)時(shí)的示例代碼
這篇文章主要介紹了使用react render props實(shí)現(xiàn)倒計(jì)時(shí)的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12淺談React的React.FC與React.Component的使用
本文主要介紹了React的React.FC與React.Component的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09原生實(shí)現(xiàn)一個(gè)react-redux的代碼示例
這篇文章主要介紹了原生實(shí)現(xiàn)一個(gè)react-redux的代碼示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06React 使用recharts實(shí)現(xiàn)散點(diǎn)地圖的示例代碼
這篇文章主要介紹了React 使用recharts實(shí)現(xiàn)散點(diǎn)地圖的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12