React-hooks面試考察知識點匯總小結(推薦)
什麼是hooks?解決瞭什麼問題?
Hooks 是react16.8新增特性,它可以使用一些state的新特性,簡化邏輯復用,副作用統一數據。
Hooks就是把某個目標結果鉤到某個可能會變化的數據源或者事件源上,那麼當被鉤到的數據或者事件發生變化時,產生這個目標結果的代碼會重新執行,產生更新後的結果。
Hook 簡介
Hook出世之前React存在的問題
-
在組件之間復用狀態邏輯很難
React 沒有提供將可復用性行為“附加”到組件的途徑(例如,把組件連接到 store)。有一些解決此類問題的方案,比如 render props 和 高階組件。但是這類方案需要重新組織你的組件結構,這可能會很麻煩,使你的代碼難以理解。
-
復雜組件變得難以理解
組件常常在
componentDidMount
和componentDidUpdate
中獲取數據。但是,同一個componentDidMount
中可能也包含很多其它的邏輯,如設置事件監聽,而之後需在componentWillUnmount
中清除。相互關聯且需要對照修改的代碼被進行瞭拆分,而完全不相關的代碼卻在同一個方法中組合在一起。如此很容易產生 bug,並且導致邏輯不一致。 -
難以理解的 class
class 是學習 React 的一大屏障。你必須去理解 JavaScript 中
this
的工作方式,這與其他語言存在巨大差異。還不能忘記綁定事件處理器。沒有穩定的語法提案,這些代碼非常冗餘。大傢可以很好地理解 props,state 和自頂向下的數據流,但對 class 卻一籌莫展。
Hook帶來的解決方案
- 你可以使用 Hook 從組件中提取狀態邏輯,使得這些邏輯可以單獨測試並復用。Hook 使你在無需修改組件結構的情況下復用狀態邏輯。
- Hook 將組件中相互關聯的部分拆分成更小的函數(比如設置訂閱或請求數據),而並非強制按照生命周期劃分。你還可以使用 reducer 來管理組件的內部狀態,使其更加可預測。
- Hook 使你在非 class 的情況下可以使用更多的 React 特性。 從概念上講,React 組件一直更像是函數。而 Hook 則擁抱瞭函數,同時也沒有犧牲 React 的精神原則。Hook 提供瞭問題的解決方案,無需學習復雜的函數式或響應式編程技術。
Hook API
useState
useState
是react自帶的一個hook函數,它的作用就是用來聲明狀態變量。useState
這個函數接收的參數是我們的狀態初始值(initial state),它返回瞭一個數組,這個數組的第[0]
項是當前當前的狀態值,第[1]
項是可以改變狀態值的方法函數。
初始化
//返回一個 state,以及更新 state 的函數 setState(接收一個新的 state 值並將組件的一次重新渲染加入隊列) const [state, setState] = useState(initialState);
函數式更新
//如果新的 state 需要通過使用先前的 state 計算得出,那麼可以將函數傳遞給 setState。該函數將接收先前的 state,並返回一個更新後的值。 function Counter({initialCount}) { const [count, setCount] = useState(initialCount); return ( <> Count: {count} <button onClick={() => setCount(initialCount)}>Reset</button> <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button> <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button> </> ); }
惰性初始 state
//如果初始 state 需要通過復雜計算獲得,則可以傳入一個函數,在函數中計算並返回初始的 state,此函數隻在初始渲染時被調用 const [state, setState] = useState(() => { const initialState = someExpensiveComputation(props); return initialState; });
跳過 state 更新
調用 State Hook 的更新函數並傳入當前的 state 時,React 將跳過子組件的渲染及 effect 的執行。(React 使用 Object.is
比較算法 來比較 state。)
useEffect
我們寫的有狀態組件,通常會產生很多的副作用(side effect),比如發起ajax請求獲取數據,添加一些監聽的註冊和取消註冊,手動修改dom等等。我們之前都把這些副作用的函數寫在生命周期函數鉤子裡,比如componentDidMount
,componentDidUpdate
和componentWillUnmount
。而現在的useEffect就相當與這些聲明周期函數鉤子的集合體。它以一抵三。
簡單例子
import { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // 類似於componentDidMount 和 componentDidUpdate: useEffect(() => { // 更新文檔的標題 document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
清除 effect
通常,組件卸載時需要清除 effect 創建的諸如訂閱或計時器 ID 等資源。要實現這一點,useEffect
函數需返回一個清除函數。以下就是一個創建訂閱的例子:
useEffect(() => { const subscription = props.source.subscribe(); return () => { // 清除訂閱 subscription.unsubscribe(); }; });
為防止內存泄漏,清除函數會在組件卸載前執行。另外,如果組件多次渲染(通常如此),則在執行下一個 effect 之前,上一個 effect 就已被清除。
effect 的執行時機
與 componentDidMount
、componentDidUpdate
不同的是,在瀏覽器完成佈局與繪制之後,傳給 useEffect
的函數會延遲調用。這使得它適用於許多常見的副作用場景,比如設置訂閱和事件處理等情況,因此不應在函數中執行阻塞瀏覽器更新屏幕的操作。
effect 的條件執行
默認情況下,effect 會在每輪組件渲染完成後執行。這樣的話,一旦 effect 的依賴發生變化,它就會被重新創建。在某些情況下,我們不需要在每次組件更新時都創建新的訂閱,而是僅需要在 source
prop 改變時重新創建。要實現這一點,可以給 useEffect
傳遞第二個參數,它是 effect 所依賴的值數組。
//此時,隻有當 props.source 改變後才會重新創建訂閱。(要實現componentDidMount功能隻需要設置第二個參數為[]即可) useEffect( () => { const subscription = props.source.subscribe(); return () => { subscription.unsubscribe(); }; }, [props.source], );
useContext
可以深層組件傳值,父組件傳給子孫組件。接收一個 context 對象(React.createContext
的返回值)並返回該 context 的當前值。當前的 context 值由上層組件中距離當前組件最近的 <MyContext.Provider>
的 value
prop 決定。
當組件上層最近的 <MyContext.Provider>
更新時,該 Hook 會觸發重渲染,並使用最新傳遞給 MyContext
provider 的 context value
值。即使祖先使用 React.memo
或 shouldComponentUpdate
,也會在組件本身使用 useContext
時重新渲染
const themes = { light: { foreground: "#000000", background: "#eeeeee" }, dark: { foreground: "#ffffff", background: "#222222" } }; const ThemeContext = React.createContext(themes.light); function App() { return ( <ThemeContext.Provider value={themes.dark}> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } function ThemedButton() { const theme = useContext(ThemeContext); return ( <button style={{ background: theme.background, color: theme.foreground }}> I am styled by theme context! </button> ); }
useReducer
useState
的替代方案,可以用於復雜狀態處理。它接收一個形如 (state, action) => newState
的 reducer,並返回當前的 state 以及與其配套的 dispatch
方法。(如果你熟悉 Redux 的話,就已經知道它如何工作瞭。)參考 前端react面試題詳細解答
指定初始 state
有兩種不同初始化 useReducer
state 的方式,你可以根據使用場景選擇其中的一種。將初始 state 作為第二個參數傳入 useReducer
是最簡單的方法:
//nst [state, dispatch] = useReducer(reducer, initialArg, init); const [state, dispatch] = useReducer( reducer, {count: initialCount} );
某些場景下,useReducer
會比 useState
更適用,例如 state 邏輯較復雜且包含多個子值,或者下一個 state 依賴於之前的 state 等。並且,使用 useReducer
還能給那些會觸發深更新的組件做性能優化,因為你可以向子組件傳遞 dispatch
而不是回調函數 。
const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); }
惰性初始化
你可以選擇惰性地創建初始 state。為此,需要將 init
函數作為 useReducer
的第三個參數傳入,這樣初始 state 將被設置為 init(initialArg)
。
這麼做可以將用於計算 state 的邏輯提取到 reducer 外部,這也為將來對重置 state 的 action 做處理提供瞭便利:
function init(initialCount) { return {count: initialCount}; } function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; case 'reset': return init(action.payload); default: throw new Error(); } } function Counter({initialCount}) { const [state, dispatch] = useReducer(reducer, initialCount, init); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'reset', payload: initialCount})}> Reset </button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); }
跳過 dispatch
如果 Reducer Hook 的返回值與當前 state 相同,React 將跳過子組件的渲染及副作用的執行。(React 使用 Object.is
比較算法 來比較 state。)
useMemo
把“創建”函數和依賴項數組作為參數傳入 useMemo
,它僅會在某個依賴項改變時才重新計算 memoized 值。這種優化有助於避免在每次渲染時都進行高開銷的計算。如果沒有提供依賴項數組,useMemo
在每次渲染時都會計算新的值。memo
是淺比較,意思是,對象隻比較內存地址,隻要你內存地址沒變,管你對象裡面的值千變萬化都不會觸發render。
你可以把 useMemo
作為性能優化的手段,但不要把它當成語義上的保證。將來,React 可能會選擇“遺忘”以前的一些 memoized 值,並在下次渲染時重新計算它們,比如為離屏組件釋放內存。先編寫在沒有 useMemo
的情況下也可以執行的代碼 —— 之後再在你的代碼中添加 useMemo
,以達到優化性能的目的。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useCallback
把內聯回調函數及依賴項數組作為參數傳入 useCallback
,它將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時才會更新。當你把回調函數傳遞給經過優化的並使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate
)的子組件時,它將非常有用。
useMemo
與 useCallback
類似,都是有著緩存的作用,useMemo 是緩存值的,useCallback 是緩存函數的。
useCallback(fn, deps)
相當於 useMemo(() => fn, deps)
。
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
useRef
useRef
返回一個可變的 ref 對象,其 .current
屬性被初始化為傳入的參數(initialValue
)。返回的 ref 對象在組件的整個生命周期內保持不變。
useEffect
裡面的state的值,是固定的,這個是有辦法解決的,就是用useRef
,可以理解成useRef
的一個作用:就是相當於全局作用域,一處被修改,其他地方全更新。
本質上,useRef
就像是可以在其 .current
屬性中保存一個可變值的“盒子”。你應該熟悉 ref 這一種訪問 DOM 的主要方式。如果你將 ref 對象以 <div ref={myRef} />
形式傳入組件,則無論該節點如何改變,React 都會將 ref 對象的 .current
屬性設置為相應的 DOM 節點。然而,useRef()
比 ref
屬性更有用。它可以很方便地保存任何可變值,其類似於在 class 中使用實例字段的方式。
請記住,當 ref 對象內容發生變化時,useRef
並不會通知你。變更 .current
屬性不會引發組件重新渲染。如果想要在 React 綁定或解綁 DOM 節點的 ref 時運行某些代碼,則需要使用回調 ref 來實現。
const Hook =()=>{ const [count, setCount] = useState(0) const btnRef = useRef(null) useEffect(() => { console.log('use effect...') const onClick = ()=>{ setCount(count+1) } btnRef.current.addEventListener('click',onClick, false) return ()=> btnRef.current.removeEventListener('click',onClick, false) },[count]) return( <div> <div> {count} </div> <button ref={btnRef}>click me </button> </div> ) }
useImperativeHandle
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle
可以讓你在使用 ref
時自定義暴露給父組件的實例值。在大多數情況下,應當避免使用 ref 這樣的命令式代碼。useImperativeHandle
應當與 forwardRef
一起使用:
function FancyInput(props, ref) { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} ... />; } FancyInput = forwardRef(FancyInput);
在本例中,渲染 <FancyInput ref={inputRef} />
的父組件可以調用 inputRef.current.focus()
。
自定義 Hook
自定義 Hook 是一個函數,其名稱以 “use
” 開頭,函數內部可以調用其他的 Hook。
例如,下面的 useFriendStatus
是我們第一個自定義的 Hook:
import { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }
自定義一個當resize 的時候 監聽window的width和height的hook
import {useEffect, useState} from "react"; export const useWindowSize = () => { const [width, setWidth] = useState() const [height, setHeight] = useState() useEffect(() => { const {clientWidth, clientHeight} = document.documentElement setWidth(clientWidth) setHeight(clientHeight) }, []) useEffect(() => { const handleWindowSize = () =>{ const {clientWidth, clientHeight} = document.documentElement setWidth(clientWidth) setHeight(clientHeight) }; window.addEventListener('resize', handleWindowSize, false) return () => { window.removeEventListener('resize',handleWindowSize, false) } }) return [width, height] }
使用:
const [width, height] = useWindowSize() const isOnline = useFriendStatus(id);
到此這篇關於React-hooks面試考察知識點匯總的文章就介紹到這瞭,更多相關React-hooks面試內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 30分鐘帶你全面瞭解React Hooks
- React中10種Hook的使用介紹
- 詳解如何構建自己的react hooks
- react hooks實現原理解析
- react使用useState修改對象或者數組的值無法改變視圖的問題