React Hook
的出現,是對
react hooks
react
中無狀态元件的一種更新,使得函數元件也能state 和 生命周期
React Hooks 要解決的問題是狀态共享,是繼 render-props(渲染屬性) 和 higher-order components(HOC;高階元件) 之後的第三種狀态邏輯複用方案,不會産生 JSX 嵌套地獄問題。
1. useState
的能力,就是讓我們在函數元件中使用
hooks
, 就是通過
state
來實作的
useState
UseState預設值
-
支援我們在調用的時候直接傳入一個值,來指定useState
的預設值,比如這樣state
,useState(0)
,useState({ a: 1 })
,useState([ 1, 2 ])
- 支援我們傳入一個函數,
中的函數**隻會執行一次*useState
setUseState時
- 擷取上一輪的值:使用
的第二個參數時,我們想要擷取上一輪該useState
的值的話,隻需要在state
傳回的第二個參數,也就是我們上面的例子中的useState
使用時,傳入一個參數,該函數的參數就是上一輪的setCount
的值state
- 更新值:setUseState必須傳入一個新的值與上次值不相等,否則将不會重新渲染(函數會繼續執行)
setCount([...Array])
多個 useState 的情況
React依靠useState執行順序來識别useState,不可以打亂其執行順序即不能寫在判斷語句中
2. useEffect
可以讓你在函數元件中執行副作用操作,這裡提到副作用,什麼是副作用呢,就是除了狀态相關的邏輯,比如網絡請求,監聽事件,查找
Effect Hook
dom
useEffect
的第二個參數,有三種情況
- 什麼都不傳,元件每次 render 之後 useEffect 都會調用,相當于 componentDidMount 和 componentDidUpdate
- 傳入一個空數組 [], 隻會調用一次,相當于 componentDidMount 和 componentWillUnmount
- 傳入一個數組,其中包括變量,隻有這些變量任意一個變動時,useEffect 才會執行
3. useContext
Context
提供了一種方式,能夠讓資料在元件樹中傳遞時不必一級一級的手動傳遞
中的
context
和
Provider
,在類元件和函數元件中都能使用,
Consumer
隻能在類元件中使用,因為它是類的靜态屬性
contextType
import React, {createContext, useState, useContext} from 'react';
const Context = createContext(0)
function Parent () {
const [ count, setCount ] = useState(0)
return (
<div>
點選次數: { count }
<button onClick={() => { setCount(count + 1)}}>點我</button>
<Context.Provider value={count}>
<Child></Child>
</Context.Provider>
</div>
)
}
function Child () {
const count = useContext(Context);
return (
<div>{ count }</div>
)
}
4. useMemo
根據依賴的值計算出結果(類似Vue計算屬性),當依賴的值未發生改變的時候,不觸發狀态改變
說白了React中 的
就是函數元件的
memo
,
PureComponent
同理也是
useMemo
function App () {
const [ count, setCount ] = useState(0)
const add = useMemo(() => count + 1 , [count])
return (
<div>
點選次數: { count }<br/>
次數加一: { add }<br/>
<button onClick={() => { setCount(count + 1)}}>點我</button>
</div>
)
}
也支援傳入第二個參數,用法和
useMemo
類似。需要注意的是,
useEffect
會在渲染的時候執行,而不是渲染之後執行,這一點和
useMemo
有差別,是以
useEffect
不建議有 副作用相關的邏輯
useMemo
- 不傳數組,每次更新都會重新計算
- 空數組,隻會計算一次
- 依賴對應的值,當對應的值發生變化時,才會重新計算(可以依賴另外一個
傳回的值)useMemo
const expensive = useMemo(() => {
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}, [count]);
5. useCallback
可以說是的文法糖,能用
useMemo
實作的,都可以使用
useCallback
, 在 react 中我們經常面臨一個子元件渲染優化的問題,
useMemo
的第二個參數和
useCallback
一樣,沒有差別
useMemo
useCallback(fn, deps) 相當于 useMemo(() => fn, deps)。
主要差別是
React.useMemo
将調用
fn
函數并傳回其結果,而
React.useCallback
将傳回
fn
函數而不調用它。
使用場景是:有一個父元件,其中包含子元件,子元件接收一個函數作為props;通常而言,如果父元件更新了,子元件也會執行更新;但是大多數場景下,更新是沒有必要的,我們可以借助useCallback來傳回函數,然後把這個函數作為props傳遞給子元件;這樣,子元件就能避免不必要的更新。
function Parent() {
const [count, setCount] = useState(1)
const [val, setVal] = useState('')
const callback = useCallback(() => {return count}, [count])
return (<div>
<h4>{count}</h4>
<Child callback={callback}/>
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={val} onChange={event => setVal(event.target.value)}/>
</div>
</div>)
}
function Child({ callback }) {
const [count, setCount] = useState(() => callback())
useEffect(() => {setCount(callback())}, [callback])
return <div>{count}</div>
}
6. useRef
的作用,總共有兩種用法
useRef
- 擷取子元件的執行個體(隻有類元件可用)
- 在函數元件中的一個全局變量,不會因為重複
重複申明, 類似于類元件的
render
this.xxx
對于第2點:
有些情況下,我們需要保證函數元件每次 render 之後,某些變量不會被重複申明,比如說 Dom 節點,定時器的 id 等等,在類元件中,我們完全可以通過給類添加一個自定義屬性來保留,比如說 this.xxx, 但是函數元件沒有 this,自然無法通過這種方法使用,有的朋友說,我可以使用
useState 來保留變量的值,但是 useState 會觸發元件 render,在這裡完全是不需要的,我們就需要使用 useRef 來實作了,具體看下面例子
function App () {
const [ count, setCount ] = useState(0)
const timer = useRef(null)
let timer2
useEffect(() => {
let id = setInterval(() => {setCount(count => count + 1)}, 500)
timer.current = id
timer2 = id
return () => {clearInterval(timer.current)}
}, [])
const onClickRef = () => {clearInterval(timer.current)}
const onClick = () => {clearInterval(timer2)}
return (
<div>
點選次數: { count }
<button onClick={onClick}>普通</button>
<button onClick={onClickRef}>useRef</button>
</div>
)
}
當我們們使用普通的按鈕去暫停定時器時發現定時器無法清除,因為 App 元件每次 render,都會重新申明一次 timer2, 定時器的 id 在第二次 render 時,就丢失了,是以無法清除定時器,針對這種情況,就需要使用到 useRef,來為我們保留定時器 id,類似于 this.xxx,這就是 useRef 的另外一種用法
7. useReducer
是什麼呢,它其實就是類似
useReducer
中的功能,相較于
redux
,它更适合一些邏輯較複雜且包含多個子值,或者下一個
useState
依賴于之前的
state
等等的特定場景,
state
總共有三個參數
useReducer
- 一個 reducer,就是一個函數類似 (state, action) => newState 的函數,傳入 上一個 state 和本次的 action
- 初始 state,也就是預設值,是比較簡單的方法
- 惰性初始化,這麼做可以将用于計算 state 的邏輯提取到 reducer 外部,這也為将來對重置 state 的 action 做處理提供了便利
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 App() {
const [state, dispatch] = useReducer(reducer, {count: 0});
return (
<>
點選次數: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
8. useEffect和useLayoutEffect的差別
useEffect是在渲染完成後執行,如果在渲染後執行DOM操作就可能出現元素的閃爍、瞬移,useLayoutEffect裡面的callback函數會在DOM更新完成後立即執行,但是會在浏覽器進行任何繪制之前運作完成,阻塞了浏覽器的繪制.
差別就在執行的時間不同:
DOM更新 ⇒ 渲染 ⇒ useEffect
DOM更新 ⇒ useLayoutEffect ⇒ 渲染
9. 自定義Hook
關于自定義hook使用:
當元件的整個邏輯可以抽離時候,适當的封裝起來,便于引用使用。打個比方,用React Hook 想做一個輸入框的UI元件,我們把邏輯抽離,對于UI元件的樣式就可以随便寫,有幾種樣式就能生成幾種不同的輸入框元件,隻需要引入自定義hook實作邏輯複用。