Hooks
是 React v16.8
的新特性紧阔,可以在不使用類組件的情況下粒竖,使用 state
以及其他的React特性;
-
Hooks
是完全可選的绒北,無需重寫任何已有代碼就可以在一些組件中嘗試Hook
- 于
React v16.8
發(fā)布适贸,100%
向后兼容灸芳,Hooks
不包含任何破壞性改動. - React也沒有計劃移除
class
類組件涝桅,而且Hooks
不會影響對React的理解,它為已知的React概念提供了更直接的API
Hooks
解決的問題
1. 函數(shù)式組件不能使用state
:函數(shù)式組件比類組件更簡潔好用耗绿,而Hooks
讓它更加豐富強大苹支;
2. 副作用問題:諸如數(shù)據(jù)獲取、訂閱误阻、定時執(zhí)行任務(wù)、手動修改ReactDOM
這些行為都可以稱為副作用晴埂;而Hooks
的出現(xiàn)可以使用 useEffect
來處理這些副作用究反;
3. 有狀態(tài)的邏輯重用組件
4. 復(fù)雜的狀態(tài)管理:之前通常使用 redux、dva儒洛、mobx
這些第三方狀態(tài)管理器來管理復(fù)雜的狀態(tài)精耐,而Hooks
可以使用 useReducer、useContext
配合實現(xiàn)復(fù)雜的狀態(tài)管理琅锻;
5. 開發(fā)效率和質(zhì)量問題:函數(shù)式組件比類組件簡潔卦停,效率高,性能也好恼蓬。
useState
useState
:組件狀態(tài)管理的鉤子
import { useState } from 'react'
const [state, setState] = useState(initState)
-
state
:管理組件的狀態(tài)惊完; -
setState
:更新state
的方法,方法名不可更改处硬! -
initState
:初始的state
小槐,可以是任意的數(shù)據(jù)類型(只在初次渲染時有效,二次渲染時會被忽略)荷辕,也可以是回調(diào)函數(shù)凿跳,但函數(shù)必須有返回值。
- 函數(shù)式組件實現(xiàn)計數(shù)器
import { useState } from 'react' export default function App() { const [count, setCount] = useState(0) // 初始值0 return ( <div> <div>點擊了{ count }次</div> <button onClick={()=>setCount(count+1)}>點擊</button> </div> ) }
-
useState
讓函數(shù)式組件具備了管理狀態(tài)的能力疮方,不再是一個無狀態(tài)組件控嗜;與class
的setState
類似,當向useState
更新狀態(tài)的方法setCount
傳遞一個函數(shù)時骡显,此函數(shù)會接收到上一次的狀態(tài):setCount(prevCount => prevCount + 1)
但與class
的setState
不同的是疆栏,如果狀態(tài)值是一個對象,useState
更新狀態(tài)的方法是不會合并更新對象的蟆盐,可以結(jié)合展開運算符來達到合并更新對象的效果:setState(prevState => { return { ...prevState, ...updateValues }; });
-
useState
當然可以多次調(diào)用承边,從而定義多個狀態(tài)值;但不管調(diào)用多少次石挂,相互之間都是獨立的博助,不會相互污染。這就是為什么React否掉了Mixins
痹愚,因為Mixins
機制讓多個Mixins
共享一個對象的數(shù)據(jù)空間富岳,這樣就很難保證不同Mixins
依賴的狀態(tài)不發(fā)生沖突蛔糯;function App() { const [age, setAge] = useState(42); const [fruit, setFruit] = useState('banana'); const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]); }
- React又是如何保證多個
useState
的相互獨立呢,定義時并沒有告訴React這些值的key
窖式,React如何保證這三個useState
能準確找到它對應(yīng)的state
呢--->
根據(jù)useState
出現(xiàn)的順序蚁飒!
加入條件控制語句第一次渲染 useState(42); //將age初始化為42 useState('banana'); //將fruit初始化為banana useState([{ text: 'Learn Hooks' }]); //... 第二次渲染 useState(42); //讀取狀態(tài)變量age的值(這時候傳的參數(shù)42直接被忽略) useState('banana'); //讀取狀態(tài)變量fruit的值(這時候傳的參數(shù)banana直接被忽略) useState([{ text: 'Learn Hooks' }]); //...
這樣一來,首次渲染的初始化過程是不變的萝喘,但第二次渲染就有所不同了let showFruit = true; function App() { const [age, setAge] = useState(42); if(showFruit) { const [fruit, setFruit] = useState('banana'); showFruit = false; } const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]); }
鑒于此,React規(guī)定:第二次渲染 useState(42); //讀取狀態(tài)變量age的值(這時候傳的參數(shù)42直接被忽略) // useState('banana'); useState([{ text: 'Learn Hooks' }]); //讀取到的卻是狀態(tài)變量fruit的值淮逻,導(dǎo)致報錯
-
Hooks
必須寫在函數(shù)的最外層阁簸,不能在循環(huán)語句爬早、條件判斷、子函數(shù)中調(diào)用启妹; - 只能在
React
的函數(shù)式組件筛严、自定義Hooks
中調(diào)用Hooks
,不能在其他JavaScrip
t函數(shù)中調(diào)用饶米。
-
useEffect
useEffect(callback, array)
:副作用處理的鉤子桨啃;它也是componentDidMount()、componentDidUpdate()檬输、componentWillUnmount()照瘾、
這幾個生命周期方法的統(tǒng)一,一個頂三個褪猛!React 會等待瀏覽器完成畫面渲染之后才會延遲調(diào)用 useEffect网杆,而生命周期鉤子是同步執(zhí)行的
-
callback
回調(diào)函數(shù),作用是處理副作用的邏輯伊滋,可以返回一個函數(shù)碳却,用作清理副作用;
為防止內(nèi)存泄漏笑旺,清除函數(shù)會在組件卸載前執(zhí)行昼浦;如果組件多次渲染,則在執(zhí)行下一個 effect 之前筒主,上一個 effect 就已被清除关噪;import { useEffect } from 'react' useEffect(() => { ......//副作用處理 return () => { ......//清理副作用的清除函數(shù) } }, [])
-
array
可選數(shù)組,用于控制useEffect
的執(zhí)行乌妙; 省略時使兔,每次渲染都會執(zhí)行; 空數(shù)組時藤韵,只會在組件掛載/卸載時執(zhí)行一次虐沥,類似componentDidMount、componentWillUnmount
; 非空數(shù)組時欲险,會在數(shù)組元素發(fā)生改變后執(zhí)行镐依,且這個變化的比較是淺比較。
提供第二個參數(shù)時天试,相當于告訴React 該組件不依賴于state/props
的變化槐壳,只依賴第二個參數(shù)的變化。
父組件也可以通過function App() { const [count, setCount] = useState(0) // 初始值0 useEffect(()=>{ console.log('1') // 初次渲染時執(zhí)行一次喜每,之后每次 count 變化都執(zhí)行 return () => { console.log('2') // 初次渲染時不執(zhí)行务唐,之后每次 count 變化都最先執(zhí)行 } }, [count]) return (<div> <div>點擊了{ count }次</div> <button onClick={() => setCount(preCount => preCount+1)}>點擊</button> </div>) } // 初次渲染:1 // 點擊更新count:2 1
props
控制子組件是否執(zhí)行useEffect
useEffect(()=>{ // 注冊事件 const subscription = props.source.subscribe(); return ()=>{ // 解綁事件 subscription.unsubscribe(); } }, [props.source]);
如前文所述,Hooks
可以反復(fù)多次使用带兜,相互獨立绍哎。所以合理的做法是,給每一個副作用加一個單獨的useEffect
鉤子鞋真。這樣一來,這些副作用不再全堆在class
組件的生命周期鉤子里沃于,代碼變得更加清晰涩咖;
useEffect
中定義的副作用函數(shù)在執(zhí)行時不會阻礙瀏覽器更新視圖,即這些函數(shù)是異步執(zhí)行的繁莹,而class
組件中的生命周期鉤子都是同步執(zhí)行的檩互;異步設(shè)計對大多數(shù)副作用是合理的,但也有特例咨演,比如有時候需要先根據(jù)DOM計算出某個元素的尺寸闸昨,然后再去渲染,此時則希望二次渲染是同步發(fā)生的薄风,也就是在瀏覽器真的去繪制界面前發(fā)生饵较;
為此,React 為此提供了一個額外的useLayoutEffect Hook
來處理這類effect
遭赂。它和useEffect
的結(jié)構(gòu)相同循诉,區(qū)別只是調(diào)用時機不同-- useLayoutEffect
是同步的。
useContext
useContext()
:同一個父組件的后臺組件之間的全局數(shù)據(jù)共享撇他;
useContext()
接收React.createContext()
的返回值作為參數(shù)茄猫,即context
對象,并返回最近的context
困肩。當最近的context
更新時划纽,使用該context
的 Hooks
將會重新渲染;
- 創(chuàng)建一個
Context
文件InfoContext.js
// 設(shè)置了默認值 defaultValue const InfoContext = React.createContext({ name: 'Jerry', age: 18 }) export default InfoContext
- 父組件
import InfoContext from './context/InfoContext' const Person = () => { const ctx = useContext(InfoContext) return(<InfoContext.Provider value={{ username: 'superman' }}> <div> <AgeCompt></AgeCompt> </div> </InfoContext.Provider>) } export default Person
- 某一層的后代組件
import { useContext } from 'react' import InfoContext from './context/InfoContext' const AgeCompt = () => { const { username } = useContext(InfoContext) return <p>{username}</p> } export default AgeCompt
useReducer
useReducer()
:useState
的一個增強體锌畸,用于處理復(fù)雜的狀態(tài)管理勇劣,靈感來源于Redux
的reducer
useState
內(nèi)部就是基于 useReducer
實現(xiàn)的,只是對于簡單的狀態(tài)管理蹋绽,useState()
比較好用芭毙;
const [state, setState] = useState(initState)
const [state, dispatch] = useReducer(reducer, initState, initAction)
-
reducer --
一個函數(shù)筋蓖,根據(jù)action
狀態(tài)處理并更新state
-
initState --
初始化state
-
initAction -- useReducer()
初次執(zhí)行時被處理的action
,會把第二個參數(shù)initState
當作參數(shù)執(zhí)行 -
state --
狀態(tài)值 -
dispatch --
更新state
的方法退敦,接收action
作為參數(shù)粘咖,當它被調(diào)用時,reducer
函數(shù)也會被調(diào)用侈百,同時根據(jù)action
去更新state
瓮下,action
是一個描述操作的對象,如dispatch({type: 'add'})
import { useReducer } from 'react'; const initState = { count: 0 }; const initAction = initState => { count: initState.count + 2 }; const reducer = (state, action) => { switch(action.type) { case 'ADD': return { count: state.count+1 } case 'DEL': return { count: state.count-1 } case 'RESET': return initState default: return state } } export default function UserCompt() { const [state, dispatch] = useReducer(reducer, initState) return (<div> <p>{state.count}</p> <div> <button onClick={() => dispatch({ type: 'ADD', /**可以加一些其他屬性*/ })}>增加</button> <button onClick={() => dispatch({ type: 'DEL' })}>減少</button> <button onClick={() => dispatch({ type: 'RESET' })}>重置</button> </div> </div>) }
useRef
useRef()
:創(chuàng)建ref
钝域,方便訪問操作DOM
const RefCompt = () => {
//創(chuàng)建ref
const inputRef = useRef();
const getValue = () => {
//訪問ref
const inpt = inputRef.current; // input的DOM對象
inpt.focus(); // 讓 input 框獲取焦點
console.log(inpt.value); // input輸入框的值
};
//掛載
return (<div>
<input ref={ inputRef } type="text" />
<button onClick={ getValue }>獲取值</button>
</div>);
}
當然讽坏,useRef
不只是能指向 DOM
// 可變的 ref
const App = () => {
const intervalRef = useRef<number>(0);
setEffect(() => {
intervalRef.current = setInterval(() => {}, 300);
return () => clearInterval(intervalRef.current);
});
return <div></div>
}
注意:與 useState
不同的是,useRef
會實時更新數(shù)據(jù)例证,但不會觸發(fā)組件的更新路呜!
useMemo
useMemo(callback, array)
:性能優(yōu)化,利用了閉包的特性织咧,通過記憶值來避免在每個渲染上都執(zhí)行高開銷的計算(計算緩存)胀葱;適用于復(fù)雜的計算場景,如復(fù)雜的列表渲染笙蒙,對象深拷貝...
-
callback -
用于處理邏輯的函數(shù) -
array -
依賴項抵屿,控制useMemo()
重新執(zhí)行的數(shù)組,array
元素改變時才會重新執(zhí)行useMemo()
- 返回值是一個記憶值捅位,也是
callback
的返回值import React, { useMemo } from 'react'; export default function UserCompt() { const obj1 = { name: 'Tom', age: 15 } const obj2 = { name: 'Jerry', age: 18, sex: '男' } //合并obj1轧葛、obj2 const memoValue = useMemo(() => Object.assign(obj1, obj2), [obj1, obj2]) return (<div> <p>{ memoValue.name }</p> <p>{ memoValue.age }</p> </div>) }
注意:不能在 useMemo()
中處理副作用邏輯艇搀,而是把副作用處理邏輯放在useEffect()
useCallback
useCallback(callback, array)
:也是用于性能優(yōu)化尿扯,與useMemo()
不同的是中符,返回值是callback
本身;
// 合并obj1淀散、obj2
const backValue = useCallback(() => Object.assign(obj1, obj2), [obj1, obj2])
<div>{ backValue().name } --- { backValue().age }</div>
當依賴項變化時右莱,返回一個新的函數(shù)體callback
档插。
自定義Hooks
-
Hooks
本質(zhì)上就是封裝好的勾子函數(shù),在自定義Hooks
時郭膛,最需要關(guān)心的就是性能、重復(fù)渲染這些問題; - 自定義一個
Effect Hooks
耘柱,把可以復(fù)用的邏輯抽離出來如捅,變成一個個可插拔的插銷调煎; - 當標題變化時,則修改標題士袄、否則不執(zhí)行的
Hooks
import { useEffect } from 'react' //封裝Hooks悲关,以 use 開頭 const useChangeTitle = (title) => { useEffect(() => { document.title = title }, [title]) } export default (props) => { useChangeTitle("自定義修改標題Hooks") return <div>測試</div> }
- 一個用來判斷某個
id
是否在線的Hooks
在組件中使用此import { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); useEffect(()=>{ //注冊監(jiān)聽事件 ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { //取消監(jiān)聽事件 ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }
Hooks
function FriendStatus(props) { //調(diào)用自定義Hooks const isOnline = useFriendStatus(props.friend.id); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
Hooks的使用規(guī)則
- 只在頂層調(diào)用
Hooks
-
Hooks
的調(diào)用盡量只在頂層作用域 - 不要在循環(huán)寓辱、條件或嵌套函數(shù)中調(diào)用
Hook
,否則可能會無法確保每次組件渲染時都以相同的順序調(diào)用Hook
-
- 只在函數(shù)組件調(diào)用
Hooks
:目前只支持函數(shù)式組件赤拒,未來版本Hooks會擴展到class類組件; -
React Hooks
的應(yīng)用場景:函數(shù)式組件挎挖、自定義Hooks