React Hooks
Hook 是 16.8新增特性刑顺。
hooks 優(yōu)勢(shì)
- 能優(yōu)化類組件的三大問題
- 能在無需修改組件結(jié)構(gòu)的情況下復(fù)用狀態(tài)(自定義Hooks)
- 能將組件中想關(guān)聯(lián)的部分成更小的函數(shù),(訂閱數(shù)據(jù)或請(qǐng)求數(shù)據(jù))
- 副作用的關(guān)注分離: 副作用致那些沒有發(fā)生在數(shù)據(jù)向視圖轉(zhuǎn)換過程中的邏輯,如ajax請(qǐng)求冠绢、訪問原生dom元素、本地?cái)?shù)據(jù)持久化緩存、綁定、解綁時(shí)間抖坪、添加訂閱、設(shè)置定時(shí)器闷叉、記錄日志等擦俐。以往這些副作用在類組件聲明周期函數(shù)值。useEffect在全部渲染完成之后才會(huì)執(zhí)行握侧。useLayoutEffect會(huì)在瀏覽器layout之后捌肴,painting之前執(zhí)行。
useState
useState
通過在函數(shù)組件里調(diào)用它來給組件添加一些內(nèi)部 state藕咏。React 會(huì)在重復(fù)渲染時(shí)保留這個(gè) state。useState 會(huì)返回一對(duì)值:當(dāng)前狀態(tài)和一個(gè)讓你更新它的函數(shù)秽五,你可以在事件處理函數(shù)中或其他一些地方調(diào)用這個(gè)函數(shù)
useState
唯一的參數(shù)是初始state孽查。
function useState<s>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<s>>];
const [count, setCount] = useState(0)
- 惰性初始 state
initialState
參數(shù)只會(huì)在組件的初始渲染中起作用,后續(xù)渲染時(shí)會(huì)被忽略坦喘。如果初始 state 需要通過復(fù)雜計(jì)算獲得盲再,則可以傳入一個(gè)函數(shù)西设,在函數(shù)中計(jì)算并返回初始的 state,此函數(shù)只在初始渲染時(shí)被調(diào)用:
const [count, setCount] = useState(()=>{
// 這里只在初始化的時(shí)候會(huì)被執(zhí)行
return 0
})
- 函數(shù)式更新
如果新的 state 需要通過使用先前的 state 計(jì)算得出答朋,那么可以將函數(shù)傳遞給 setState贷揽。該函數(shù)將接收先前的 state,并返回一個(gè)更新后的值梦碗。下面的計(jì)數(shù)器組件示例展示了 setState 的兩種用法
useEffect
Effect Hook 可以讓你能夠在 Function 組件中執(zhí)行副作用(side effects)禽绪。 Function Component沒有Class Component生命周期的概念,只有一個(gè)狀態(tài)洪规。
import { useState, useEffect } from "react";
// 底層 Hooks, 返回布爾值:是否在線
function useFriendStatusBoolean(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
// 上層 Hooks印屁,根據(jù)在線狀態(tài)返回字符串:Loading... or Online or Offline
function useFriendStatusString(props) {
const isOnline = useFriendStatusBoolean(props.friend.id);
if (isOnline === null) {
return "Loading...";
}
return isOnline ? "Online" : "Offline";
}
// 使用了底層 Hooks 的 UI
function FriendListItem(props) {
const isOnline = useFriendStatusBoolean(props.friend.id);
return (
<li>{props.friend.name}</li>
);
}
// 使用了上層 Hooks 的 UI
function FriendListStatus(props) {
const status = useFriendStatusString(props);
return <li>{status}</li>;
}
這個(gè)例子中,有兩個(gè) Hooks:useFriendStatusBoolean 與 useFriendStatusString, useFriendStatusString 是利用 useFriendStatusBoolean 生成的新 Hook斩例,這兩個(gè) Hook 可以給不同的 UI:FriendListItem雄人、FriendListStatus 使用,而因?yàn)閮蓚€(gè) Hooks 數(shù)據(jù)是聯(lián)動(dòng)的念赶,因此兩個(gè) UI 的狀態(tài)也是聯(lián)動(dòng)的础钠。
利用 useEffect 代替一些生命周期
useEffect 會(huì)在每次渲染后都執(zhí)行嗎? 是的叉谜,默認(rèn)情況下旗吁,它在第一次渲染之后和每次更新之后都會(huì)執(zhí)行。
- 用useEffect模擬componentDidMount生命周期
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
},[]); // [] 空的依賴 只會(huì)執(zhí)行一次
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
- 用useEffect模擬componentDidUpdate
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
}); // 每次渲染都會(huì)更新
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
- 用useEffect模擬ComponentWillUnmount
useEffect(
() => {
const subscription = props.id.subscribe();
return () => {
subscription.unsubscribe();
}; // 這是 effect 可選的清除機(jī)制正罢。每個(gè) effect 都可以返回一個(gè)清除函數(shù)阵漏。如此可以將添加和移除訂閱的邏輯放在一起。它們都屬于 effect 的一部分翻具。
},
[props.id],
);
程序一開始執(zhí)行履怯,就執(zhí)行了useEffect
中的函數(shù),之后的每次render都會(huì)執(zhí)行useEffect
裆泳,但是因?yàn)橛?code>props.id的存在叹洲,所以只有在props.id
改變的時(shí)候,useEffect
中的函數(shù)才會(huì)執(zhí)行。
React 何時(shí)清除 effect工禾? React 會(huì)在組件卸載的時(shí)候執(zhí)行清除操作运提。正如之前學(xué)到的,effect 在每次渲染的時(shí)候都會(huì)執(zhí)行闻葵。這就是為什么 React 會(huì)在執(zhí)行當(dāng)前 effect 之前對(duì)上一個(gè) effect 進(jìn)行清除民泵。
每一次渲染都有它自己的 Props and State
先討論一下渲染(rendering)
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
我們的組件第一次渲染的時(shí)候,從useState()拿到count的初始值0槽畔。當(dāng)我們調(diào)用setCount(1)栈妆,React會(huì)再次渲染組件,這一次count是1
// During first render
function Counter() {
const count = 0; // Returned by useState()
// ...
<p>You clicked {count} times</p>
// ...
}
// After a click, our function is called again
function Counter() {
const count = 1; // Returned by useState()
// ...
<p>You clicked {count} times</p>
// ...
}
// After another click, our function is called again
function Counter() {
const count = 2; // Returned by useState()
// ...
<p>You clicked {count} times</p>
// ...
}
當(dāng)我們更新狀態(tài)的時(shí)候,React會(huì)重新渲染組件鳞尔。每一次渲染都能拿到獨(dú)立的count 狀態(tài)嬉橙,這個(gè)狀態(tài)值是函數(shù)中的一個(gè)常量。
當(dāng)setCount的時(shí)候寥假,React會(huì)帶著一個(gè)不同的count值再次調(diào)用組件市框。然后,React會(huì)更新DOM以保持和渲染輸出一致糕韧。
這里關(guān)鍵的點(diǎn)在于任意一次渲染中的count常量都不會(huì)隨著時(shí)間改變枫振。渲染輸出會(huì)變是因?yàn)槲覀兊慕M件被一次次調(diào)用,而每一次調(diào)用引起的渲染中兔沃,它包含的count值獨(dú)立于其他渲染蒋得。
每一次渲染都有它自己的事件處理函數(shù)
在任意一次渲染中,props和state是始終保持不變的乒疏。如果props和state在不同的渲染中是相互獨(dú)立的额衙,那么使用到它們的任何值也是獨(dú)立的(包括事件處理函數(shù))。它們都“屬于”一次特定的渲染怕吴。即便是事件處理中的異步函數(shù)調(diào)用“看到”的也是這次渲染中的count值窍侧。
每次渲染都有它自己的Effects
function Counter() {
const [count, setCount] = useState(0);
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是如何讀取到最新的count 狀態(tài)值的呢等恐?
我們已經(jīng)知道count是某個(gè)特定渲染中的常量邻悬。事件處理函數(shù)“看到”的是屬于它那次特定渲染中的count狀態(tài)值。對(duì)于effects也同樣如此:
并不是count的值在“不變”的effect中發(fā)生了改變艰猬,而是effect 函數(shù)本身在每一次渲染中都不相同议经。
每一個(gè)effect版本“看到”的count值都來自于它屬于的那次渲染:
// During first render
function Counter() {
// ...
useEffect(
// Effect function from first render
() => {
document.title = `You clicked ${0} times`;
}
);
// ...
}
// After a click, our function is called again
function Counter() {
// ...
useEffect(
// Effect function from second render
() => {
document.title = `You clicked ${1} times`;
}
);
// ...
}
// After another click, our function is called again
function Counter() {
// ...
useEffect(
// Effect function from third render
() => {
document.title = `You clicked ${2} times`;
}
);
// ..
}
React會(huì)記住你提供的effect函數(shù)斧账,并且會(huì)在每次更改作用于DOM并讓瀏覽器繪制屏幕后去調(diào)用它。
所以雖然我們說的是一個(gè) effect(這里指更新document的title)煞肾,但其實(shí)每次渲染都是一個(gè)不同的函數(shù) — 并且每個(gè)effect函數(shù)“看到”的props和state都來自于它屬于的那次特定渲染咧织。
那Effect中的清理又是怎樣的呢?
- React 先渲染新數(shù)據(jù)的UI界面
- 瀏覽器繪制籍救∠熬睿看到新的數(shù)據(jù)的UI。
- React 清除舊數(shù)據(jù)的Effect蝙昙。
- React 運(yùn)行新數(shù)據(jù)的Effect闪萄。
告訴 React 如何對(duì)比 Effects
React不能區(qū)分effects的不同。 在沒有調(diào)用之前是不能猜測(cè)到函數(shù)做了什么奇颠。這是為什么想要避免effects不必要的重復(fù)調(diào)用败去,所以要提供給uesEffect
一個(gè)依賴數(shù)組參數(shù)。
useEffect(() => {
document.title = 'Hello, ' + name;
}, [name]);
如果當(dāng)前渲染中的這些依賴項(xiàng)和上一次運(yùn)行這個(gè)effect的時(shí)候紙一樣烈拒,就會(huì)自動(dòng)跳過這次effect運(yùn)行为迈。
即使依賴數(shù)組中只有一個(gè)值在兩次渲染中不一樣三椿,我們也不能跳過effect的運(yùn)行。要同步所有葫辐!
對(duì)依賴必須誠(chéng)實(shí)
function SearchResults() {
async function fetchData() {
// ...
}
useEffect(() => {
fetchData();
}, []); // Is this okay? Not always -- and there's a better way to write it.
// ...
}
如果設(shè)置了依賴項(xiàng),effect中用到的所有組件內(nèi)的值都要包含在依賴中伴郁。包括props
耿战、state
,函數(shù) 等組件內(nèi)任何東西。
useCallback
用來緩存函數(shù)
const memoizedCallback = useCallback(()=>{
doSomething(a,b)
},[a,b])
返回一個(gè) memoized 函數(shù)焊傅。 當(dāng)依賴改變的時(shí)候剂陡,會(huì)自動(dòng)執(zhí)行
把內(nèi)聯(lián)回調(diào)函數(shù)及依賴項(xiàng)數(shù)組作為參數(shù)傳入 useCallback,它將返回該回調(diào)函數(shù)的 memoized 版本狐胎,該回調(diào)函數(shù)僅在某個(gè)依賴項(xiàng)改變時(shí)才會(huì)更新鸭栖。當(dāng)你把回調(diào)函數(shù)傳遞給經(jīng)過優(yōu)化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時(shí),它將非常有用握巢。
useCallback(fn, deps) 相當(dāng)于 useMemo(() => fn, deps)晕鹊。
useMemo
useMemo 做計(jì)算結(jié)果緩存
const memoizedValue = useMemo(()=> computeExoensiceValue(a, b),[a,b])
useMemo 的第一個(gè)參數(shù)就是一個(gè)函數(shù),這個(gè)函數(shù)返回的值會(huì)被緩存起來暴浦,同時(shí)這個(gè)值會(huì)作為 useMemo 的返回值溅话,第二個(gè)參數(shù)是一個(gè)數(shù)組依賴,如果數(shù)組里面的值有變化歌焦,那么就會(huì)重新去執(zhí)行第一個(gè)參數(shù)里面的函數(shù)飞几,并將函數(shù)返回的值緩存起來并作為 useMemo 的返回值 。
把“創(chuàng)建”函數(shù)和依賴項(xiàng)數(shù)組作為參數(shù)傳入useMemo, 僅會(huì)在依賴改變菜回重新計(jì)算独撇。這種優(yōu)化有助于避免在每次渲染時(shí)都進(jìn)行高開銷的計(jì)算屑墨。
useMemo 的使用場(chǎng)景主要是用來緩存計(jì)算量比較大的函數(shù)結(jié)果,可以避免不必要的重復(fù)計(jì)算纷铣。如果沒有提供依賴選項(xiàng),useMemo
在每次渲染都計(jì)算新的值卵史。
useMemo 也允許你跳過一次子節(jié)點(diǎn)的昂貴的重新渲染:
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b= />, [b]);
return (
<>
{child1}
{child2}
</>
)
}
注意這種方式在循環(huán)中是無效的关炼,因?yàn)?Hook 調(diào)用 不能 被放在循環(huán)中程腹。但你可以為列表項(xiàng)抽取一個(gè)單獨(dú)的組件,并在其中調(diào)用 useMemo儒拂。
useRef
useRef 返回一個(gè)可變的 ref 對(duì)象寸潦,其 .current 屬性被初始化為傳入的參數(shù)(initialValue)。返回的 ref 對(duì)象在組件的整個(gè)生命周期內(nèi)保持不變社痛。
useImperativeHandle
useImperativeHandle 可以讓你在使用 ref 時(shí)自定義暴露給父組件的實(shí)例值见转。在大多數(shù)情況下,應(yīng)當(dāng)避免使用 ref 這樣的命令式代碼蒜哀。useImperativeHandle 應(yīng)當(dāng)與 forwardRef 一起使用:
歡迎訪問主頁(yè)斩箫,有更多文章內(nèi)容
轉(zhuǎn)載請(qǐng)注明原出處
原文鏈接地址:深入學(xué)習(xí) React Hooks