本文首發於政採雲前端團隊博客:看完這篇,你也能把 React Hooks 玩出花前端
https://www.zoo.team/article/react-hooks
先講概念
再總結
Hooks 初識
官方提供的鉤子
useRef 、useCallback 、useMemo 、react
useReducer 、useLayoutEffect 、web
useImperativeHandle 、useDebugValuejson
不一樣鉤子用法
useState
export
default
function HookDemo() {
const [count, changeCount] = useState(
0);
return (
<div>
{count}
<button onClick={() => { changeCount(Math.ceil(Math.random() * 1000)); }}>
改變count
</button>
</div>
);
}
useEffect
-
函數式組件中不存在傳統類組件生命週期的概念,若是咱們須要在一些特定的生命週期或者值變化後作一些操做的話,必須藉助 useEffect
的一些特性去實現。 -
useState
產生的 changeState 方法並無提供相似於setState
的第二個參數同樣的功能,所以若是須要在 State 改變後執行一些方法,必須經過useEffect
實現。
const [count, changeCount] = useState(
0);
// 將在count變化時打印最新的count數據
useEffect(
() => {
message.info(
`count發生變更,最新值爲${count}`);
}, [count])
useEffect
這個鉤子適用狀況中的第二種狀況,那麼如何使用該鉤子才能實現相似於類組件中生命週期的功能呢?既然第一個參數是反作用執行的回調,那麼實現咱們所要功能的重點就應該在第二個參數上了。
componentDidMount
&& componentWillUnmout
:這兩個生命週期只在頁面掛載/卸載後執行一次。前面講過,全部的反作用在組件掛載完成後會執行一次 ,若是反作用存在返回函數,那麼返回的函數將在卸載時運行。藉助這樣的特性,咱們要作的就是讓目標反作用在初始化執行一次後不再會被調用,因而只要讓與該反作用相關聯的狀態爲空,無論其餘狀態如何變更,該反作用都不會再次執行,即實現了
componentDidMount
與
componentWillUnmout
。
import React, { useState, useEffect }
from
'react';
import { message }
from
'antd';
function Child({ visible }) {
useEffect(
() => {
message.info(
'我只在頁面掛載時打印');
return
() => {
message.info(
'我只在頁面卸載時打印');
};
}, []);
return visible ?
'true' :
'false';
}
export
default
function HookDemo() {
const [visible, changeVisible] = useState(
true);
return (
<div>
{
visible && <Child visible={visible} />
}
<button onClick={() => { changeVisible(!visible); }}>
改變visible
</button>
</div>
);
}
componentDidUpdate
:該生命週期在每次頁面更新後都會被調用。那麼按照以前的邏輯,就應該把全部的狀態所有放在第二個狀態中,可是這樣的話新增/刪除一個狀態都須要改變第二參數。其實,
若是第二個參數爲空,那麼在每個 State 變化時都會執行該反作用,那麼若是要實現
componentDidUpdate
就很是簡單了。
useEffect(
() => {
// ...反作用邏輯
})
// 注意上面說的關聯狀態爲空不是說不傳遞第二個參數,而是第二個參數應該爲一個空數組
在類組件中,若是在 componentDidMount
中屢次調用setState
設置一個值( 固然不推薦這樣作),並在成功的回調中打印該值,那麼最後的結果極可能會打印不少個相同的最後一次設置的值。是由於類的setState
是一個類異步的結果,他們會將全部變更的內容進行收集而後在合適的時間去統一賦值。而在 useEffect
中,全部的變量的值都會保留在該反作用執行的時刻,相似於 for 循環中的 let 或者 閉包,全部的變量都維持在反作用執行時的狀態,也有人稱這個爲 Capture Value。
useCallback
useEffect
中存在的相同邏輯的封裝,減小代碼冗餘,配合
useEffect
使用。
const [count1, changeCount1] = useState(
0);
const [count2, changeCount2] = useState(
10);
const calculateCount = useCallback(
() => {
if (count1 && count2) {
return count1 * count2;
}
return count1 + count2;
}, [count1, count2])
useEffect(
() => {
const result = calculateCount(count, count2);
message.info(
`執行反作用,最新值爲${result}`);
}, [calculateCount])
useCallback
的使用生成了一個回調,
useCallback
的使用方法和
useEffect
一致,第一個參數爲生成的回調方法,第二個參數爲該方法關聯的狀態,
任一狀態發生變更都會從新生成新的回調。
useEffect
不一樣的地方在於使用
useCallback
生成計算的回調後,在使用該回調的反作用中,
第二個參數應該是生成的回調。其實這個問題是很好理解的,咱們使用
useCallback
生成了一個與 count1 / count2 相關聯的回調方法,那麼當關聯的狀態發生變化時會從新生成新的回調,反作用監聽到了回調的變化就會去從新執行反作用,
此時 useCallback
和 useEffect
是按順序執行的, 這樣就實現了反作用邏輯的抽離。
useRef
useRef
接受一個參數,爲 ref 的初始值。相似於類組件中的
createRef
方法 ,該鉤子會返回一個對象,對象中的 current 字段爲咱們
指向的實例 /
保存的變量,能夠實現得到目標節點實例或保存狀態的功能。
useRef
保存的變量不會隨着每次數據的變化從新生成,而是保持在咱們最後一次賦值時的狀態,依靠這種特性,再配合 useCabllback
和 useEffect
咱們能夠實現 preProps/preState
的功能。
const [count, changeCount] = useState(
0);
const [count1, changeCount1] = useState(
0);
// 建立初始值爲空對象的prestate
const preState = useRef({});
// 依賴preState進行判斷時能夠先判斷,最後保存最新的state數據
useEffect(
() => {
const { ... } = preState.current;
if (
// 條件判斷) {
// 邏輯
}
// 保存最新的state
preState.current = {
count,
count1,
}
});
useState
建立的狀態賦值給
useRef
用做初始化時,
手動更改 Ref 的值並不會引發關聯狀態的變更。從該現象來看,
useRef 彷佛只是在內存空間中開闢了一個堆空間將初始化的值存儲起來,該值與初始化的值存儲在不一樣的內存空間,修改 Ref 的值不會引發視圖的變化。
export
default
function HookDemo() {
const [count] = useState({
count:
1 });
const countRef = useRef(count);
return (
<div>
{count.count}
<button onClick={() => { countRef.current.count = 10; }}>
改變ref
</button>
</div>
);
}
useMemo
useMemo
即便用記憶的內容。該鉤子主要用於作性能的優化。
useEffect
會所有執行。一樣的,
經過計算出來的值或者引入的組件也會從新計算/掛載一遍,即便與其關聯的狀態沒有發生任何變化。
shouldComponetUpdate
以及
React.memo
useMemo
這個鉤子。
useMemo
來處理計算結果的緩存或引入組件的防止重複掛載優化。其接受兩個參數,第一個參數爲一個 Getter 方法,
返回值爲要緩存的數據或組件,第二個參數爲該返回值相關聯的狀態,當其中任何一個狀態發生變化時就會從新調用 Getter 方法生成新的返回值。
import React, { useState, useMemo }
from
'react';
import { message }
from
'antd';
export
default
function HookDemo() {
const [count1, changeCount1] = useState(
0);
const [count2, changeCount2] = useState(
10);
const calculateCount = useMemo(
() => {
message.info(
'從新生成計算結果');
return count1 *
10;
}, [count1]);
return (
<div>
{calculateCount}
<button onClick={() => { changeCount1(count1 + 1); }}>改變count1</button>
<button onClick={() => { changeCount2(count2 + 1); }}>改變count2</button>
</div>
);
}
useMemo
時可能咱們會以爲該鉤子只是用來作計算結果的緩存,返回值只能是一個數字或字符串。其實
useMemo
並不關心咱們的返回值類型是什麼,它只是在關聯狀態發生變更時從新調用咱們傳遞的 Getter 方法 生成新的返回值,也就是說
useMemo
生成的是 Getter 方法與依賴數組的關聯關係。所以,若是咱們將函數的返回值替換爲一個組件,那麼就能夠
實現對組件掛載/從新掛載的性能優化。
import React, { useState, useMemo }
from
'react';
import { message }
from
'antd';
function Child({ count }) {
return
<p>當前傳遞的count爲:{count}</p>;
}
export
default
function HookDemo() {
const [count1, changeCount1] = useState(
0);
const [count2, changeCount2] = useState(
10);
const child = useMemo(
() => {
message.info(
'從新生成Child組件');
return
<Child count={count1} />;
}, [count1]);
return (
<div>
{child}
<button onClick={() => { changeCount1(count1 + 1); }}>改變count1</button>
<button onClick={() => { changeCount2(count2 + 1); }}>改變count2</button>
</div>
);
}
其餘鉤子
useLayoutEffect
useImperativeHandle
useDebugValue
,
useContext
,是 createContext 功能在函數式組件中的實現。經過該功能能夠實現不少強大的功能,能夠是說官方的 Redux,不少人對此應該有很多的瞭解。該鉤子內容太多,後續單獨使用一個章節進行描述。
編寫本身的鉤子
最基本的鉤子
import React, { useState }
from
'react';
// 編寫咱們本身的hook,名字以use開頭
function useCounter(initialValue) {
// 接受初始化的值生成state
const [count, changeCount] = useState(initialValue);
// 聲明減小的方法
const decrease =
() => {
changeCount(count -
1);
}
// 聲明增長的方法
const increase =
() => {
changeCount(count +
1);
}
// 聲明重置計數器方法
const resetCounter =
() => {
changeCount(
0);
}
// 將count數字與方法返回回去
return [count, { decrease, increase, resetCounter }]
}
export
default
function myHooksView() {
// 在函數組件中使用咱們本身編寫的hook生成一個計數器,並拿到全部操做方法的對象
const [count, controlCount] = useCounter(
10);
return (
<div>
當前數量:{count}
<button onClick={controlCount.decrease}>減小</button>
<button onClick={controlCount.increase}>增長</button>
<button onClick={controlCount.resetCounter}>重置</button>
</div>
)
}
useCounter
這個鉤子中建立了一個關聯了
initialValue
的狀態,並建立減小/增長/重置的方法,最終將其經過
return
返回出去。這樣在其餘組件須要用到該功能的地方,經過調用該方法拿到其返回值,便可實現對
useCounter
組件封裝邏輯的複用。
返回 DOM 的鉤子
import React, { useState }
from
'react';
import { Modal }
from
'antd';
function useModal() {
const [visible, changeVisible] = useState(
false);
const toggleModalVisible =
() => {
changeVisible(!visible);
};
return [(
<Modal
visible={visible}
onOk={toggleModalVisible}
onCancel={toggleModalVisible}
>
彈窗內容
</Modal>
), toggleModalVisible];
}
export
default
function HookDemo() {
const [modal, toggleModal] = useModal();
return (
<div>
{modal}
<button onClick={toggleModal}>打開彈窗</button>
</div>
);
}
鉤子/最終總結
鉤子總結
useState
與
useRef
外,其餘鉤子都存在第二個參數,第一個方法的執行與第二個參數相互關聯。因而咱們能夠得出一個結論,在使用了 Hook 的函數式組件中,咱們在使用
反作用/引用子組件時都須要時刻注意對代碼進行性能上的優化
。
最終總結
Hooks 組件的目標並非取代 class component 組件,而是增長函數式組件的使用率,明確通用工具函數與業務工具函數的邊界, 鼓勵開發者將業務通用的邏輯封裝成 React Hooks 而不是工具函數。
招賢納士
ZooTeam@cai-inc.com
本文分享自微信公衆號 - 政採雲前端團隊(Zoo-Team)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。數組