摘要:比如在條件判斷中使用,在循環(huán),嵌套函數(shù)中使用,都會(huì)造成執(zhí)行順序不一致的問(wèn)題。而比如定時(shí)器,事件監(jiān)聽(tīng)。第一個(gè)參數(shù)的返回值,會(huì)在組件卸載時(shí)執(zhí)行,相當(dāng)于,可以清理定時(shí)器,移除事件監(jiān)聽(tīng),取消一些訂閱。
什么是 Hooks?
不通過(guò)編寫類組件的情況下,可以在組件內(nèi)部使用狀態(tài)(state) 和其他 React 特性(生命周期,context)的技術(shù)Hooks 為什么會(huì)出現(xiàn)
在之前的 React 版本中,組件分為兩種:函數(shù)式組件(或無(wú)狀態(tài)組件(StatelessFunctionComponent))和類組件,而函數(shù)式組件是一個(gè)比較的純潔的 props => UI 的輸入、輸出關(guān)系,但是類組件由于有組件自己的內(nèi)部狀態(tài),所以其輸出就由 props 和 state 決定,類組件的輸入、輸出關(guān)系就不再那么純潔。同時(shí)也會(huì)帶來(lái)下列問(wèn)題:
狀態(tài)邏輯難以復(fù)用。很多類組件都有一些類似的狀態(tài)邏輯,但是為了重用這些狀態(tài)邏輯,社區(qū)提出了 render props 或者 hoc 這些方案,但是這兩種模式對(duì)組件的侵入性太強(qiáng)。另外,會(huì)產(chǎn)生組件嵌套地獄的問(wèn)題。
大多數(shù)開(kāi)發(fā)者在編寫組件時(shí),不管這個(gè)組件有木有內(nèi)部狀態(tài),會(huì)不會(huì)執(zhí)行生命周期函數(shù),都會(huì)將組件編寫成類組件,后續(xù)迭代可能增加了內(nèi)部狀態(tài),又增加了副作用處理,又在組件中調(diào)用了一些生命周期函數(shù),文件代碼行數(shù)日益增多,最后導(dǎo)致組件中充斥著無(wú)法管理的混亂的狀態(tài)邏輯代碼和各種副作用,各種狀態(tài)邏輯散落在實(shí)例方法和生命周期方法中,維護(hù)性變差,拆分更是難上加難。
在類組件中,需要開(kāi)發(fā)者額外去關(guān)注 this 問(wèn)題,事件監(jiān)聽(tīng)器的添加和移除等等。
State Hookstate hook 提供了一種可以在 function component 中添加狀態(tài)的方式。通過(guò) state hook,可以抽取狀態(tài)邏輯,使組件變得可測(cè)試,可重用。開(kāi)發(fā)者可以在不改變組件層次結(jié)構(gòu)的情況下,去重用狀態(tài)邏輯。更好的實(shí)現(xiàn)關(guān)注點(diǎn)分離。
一個(gè)簡(jiǎn)單的使用 useState 栗子
import React, { useState } from "react"; const StateHook = () => { const [count, setCount] = useState(0); return (); };you clicked {count} times
幾點(diǎn)說(shuō)明:
useState 推薦一種更加細(xì)粒度的控制狀態(tài)的方式,即一個(gè)狀態(tài)對(duì)應(yīng)一個(gè)狀態(tài)設(shè)置函數(shù),其接受的參數(shù)將作為這個(gè)狀態(tài)的初始值。其返回一個(gè)長(zhǎng)度為2的元組,第一項(xiàng)為當(dāng)前狀態(tài),第二項(xiàng)為更新函數(shù)。
useState 的執(zhí)行順序在每一次更新渲染時(shí)必須保持一致,否則多個(gè) useState 調(diào)用將不會(huì)得到各自獨(dú)立的狀態(tài),也會(huì)造成狀態(tài)對(duì)應(yīng)混亂。比如在條件判斷中使用 hook,在循環(huán),嵌套函數(shù)中使用 hook,都會(huì)造成 hook 執(zhí)行順序不一致的問(wèn)題。最后導(dǎo)致?tīng)顟B(tài)的混亂。另外,所有的狀態(tài)聲明都應(yīng)該放在函數(shù)頂部,首先聲明。
useState 和 setState 的區(qū)別
useState 將 setState 進(jìn)行覆蓋式更新,而 setState 則將狀態(tài)進(jìn)行合并式更新。
一個(gè)不正確的栗子
import React, { useState, ChangeEvent } from "react"; const UserForm = () => { const [state, setUser] = useState({ name: "", email: "" }); const { name, email } = state; const handleNameChange = (event: ChangeEvent) => { const { target: { value: name } } = event; // 這里不可以多帶帶的設(shè)置某一個(gè)字段 新的狀態(tài)必須與初始的狀態(tài)類型保持一致 // 如果只設(shè)置了其中一個(gè)字段,編譯器會(huì)報(bào)錯(cuò),同時(shí)其余的字段也會(huì)丟失 setUser({ name, email }); }; const handleEmailChange = (event: ChangeEvent ) => { const { target: { value: email } } = event; // 這里不可以多帶帶的設(shè)置某一個(gè)字段 新的狀態(tài)必須與初始的狀態(tài)類型保持一致 setUser({ name, email }); }; return ( <> > ); }
正確的做法
import React, { useState, ChangeEvent } from "react"; const UserForm = () => { // 一個(gè)狀態(tài)對(duì)應(yīng)一個(gè)狀態(tài)更新函數(shù) const [name, setName] = useState(""); const [email, setEmail] = useState(""); const handleNameChange = (event: ChangeEventEffect Hook) => { const { target: { value: name } } = event; // hear could do some validation setName(name); }; const handleEmailChange = (event: ChangeEvent ) => { const { target: { value: email } } = event; // hear could do some validation setEmail(email); }; return ( <> > ); }
數(shù)據(jù)獲取,設(shè)置訂閱,手動(dòng)的更改 DOM,都可以稱為副作用,可以將副作用分為兩種,一種是需要清理的,另外一種是不需要清理的。比如網(wǎng)絡(luò)請(qǐng)求,DOM 更改,日志這些副作用都不要清理。而比如定時(shí)器,事件監(jiān)聽(tīng)。
一個(gè)簡(jiǎn)單使用 effect hook 去修改文檔標(biāo)題的栗子。
import React, { useState, useEffect } from "react"; const effectHook = () => { const [count, setCount] = useState(0); useEffect(() => { document.title = `you clicked ${count} times`; }, [count]); return (); };you clicked {count} times
在調(diào)用 useEffect 后,相當(dāng)于告訴 React 在每一次組件更新完成渲染后,都調(diào)用傳入 useEffect 中的函數(shù),包括初次渲染以及后續(xù)的每一次更新渲染。
幾點(diǎn)說(shuō)明:
useEffect(effectCallback: () => void, deps: any[]) 接收兩個(gè)參數(shù),第二個(gè)參數(shù)依賴項(xiàng)是可選的,表示這個(gè) effect 要依賴哪些值。
有時(shí)候我們并不想每次渲染 effect 都執(zhí)行,只有某些值發(fā)生變化才去執(zhí)行 effect,這個(gè)時(shí)候我們可以指定這個(gè) effect 的依賴列表,可以是一個(gè)也可以幾個(gè),當(dāng)其中列表中的某一個(gè)值發(fā)生變化,effect 才會(huì)執(zhí)行。
第一個(gè)參數(shù)的返回值,會(huì)在組件卸載時(shí)執(zhí)行,相當(dāng)于 componentWillUnmount,可以清理定時(shí)器,移除事件監(jiān)聽(tīng),取消一些訂閱。
當(dāng)?shù)诙€(gè)參數(shù)為一個(gè)空數(shù)組時(shí),相當(dāng)于 componentDidMount 和 componentWillUnmount,表明這個(gè) effect 沒(méi)有任何依賴,只在首次渲染時(shí)執(zhí)行。
Custom Hook也可以使用 useEffect 和 useState 實(shí)現(xiàn)自定義 hook。
一個(gè)給 DOM 元素添加事件監(jiān)聽(tīng)器的栗子。
import { useRef, useEffect } from "react"; type EventType = keyof HTMLElementEventMap; type Handler = (event: Event) => void; const useEventListener = ( eventName: EventType, handler: Handler, element: EventTarget = document ) => { // 這里使用 `useRef` 來(lái)保存?zhèn)魅氲谋O(jiān)聽(tīng)器, // 在監(jiān)聽(tīng)器變更后,去更新 `useRef` 返回的對(duì)象的 `current` 屬性 const saveHandler = useRef(); useEffect(() => { saveHandler.current = handler; }, [handler]); useEffect(() => { const supported = element && element.addEventListener; if (!supported) { return; } const listener: Handler = (event: Event) => (saveHandler.current as Handler)(event); element.addEventListener(eventName, listener); return () => { element.removeEventListener(eventName, listener); }; }, [eventName, element]); }
一個(gè)使用 useReducer 來(lái)實(shí)現(xiàn)加、減計(jì)數(shù)器的栗子。這里雖然使用 useReducer 創(chuàng)建了類似 redux 的 功能,但是如果有多個(gè)組件都引用了這個(gè) hook,那么這個(gè) hook 提供的狀態(tài)是相互獨(dú)立、互不影響的,即 useReducer 只提供了狀態(tài)管理,但是并沒(méi)有提供數(shù)據(jù)持久化的功能。redux 卻提供了一種全局維護(hù)同一個(gè)數(shù)據(jù)源的機(jī)制。所以可以利用 useReducer 和 Context 來(lái)實(shí)現(xiàn)數(shù)據(jù)持久化的功能。
import React, { useReducer } from "react"; const INCREMENT = "increment"; const DECREMENT = "decrement"; const initHandle = (initCount) => { return { count: initCount }; }; const reducer = (state, action) => { switch (action.type) { case INCREMENT: return { count: state.count + 1 }; case DECREMENT: return { count: state.count - 1 }; case "reset": return { count: action.payload }; default: return state; } }; const Counter = ({ initialCount }) => { const [state, dispatch] = useReducer(reducer, initialCount, initHandle); const { count } = state; return (Counter: {count} j); };
一個(gè)對(duì)封裝數(shù)據(jù)請(qǐng)求栗子。
import { useState, useEffect } from "react"; import axios, { AxiosRequestConfig } from "axios"; interface RequestError { error: null | boolean; message: string; } const requestError: RequestError = { error: null, message: "", }; /** * @param url request url * @param initValue if initValue changed, the request will send again * @param options request config data * * @returns a object contains response"s data, request loading and request error */ const useFetchData = (url: string, initValue: any, options: AxiosRequestConfig = {}) => { const [data, saveData] = useState(); const [loading, updateLoading] = useState(false); const [error, updateError] = useState(requestError); let ignore = false; const fetchData = async () => { updateLoading(true); const response = await axios(url, options); if (!ignore) saveData(response.data); updateLoading(false); }; useEffect(() => { try { fetchData(); } catch (error) { updateError({ error: true, message: error.message }); } return () => { ignore = true; }; }, [initValue]); return { data, loading, error }; }; export { useFetchData };Rules of Hook
隨來(lái) hooks 帶來(lái)了新的組件編寫范式,但是下面兩條規(guī)則還是要開(kāi)發(fā)者注意的。
在頂部使用 hook,不要使用 hook 在條件判斷,循環(huán),嵌套函數(shù)。
只在 function component 中使用 hook,或者自定義 hook 中使用 hook, 不要在常規(guī)的 JavaScript 函數(shù)中使用 hook
新的問(wèn)題hooks 的帶來(lái),雖然解決之前存在的一些問(wèn)題,但是也帶來(lái)了新的問(wèn)題。
異常捕獲。之前的版本中,我們可以用 componentDidCatch 來(lái)捕獲組件作用域內(nèi)的異常,做一些提示。但是在 hooks 中 ,我們只能使用 try {} catch(){} ` 去捕獲,使用姿勢(shì)也比較別扭。
一個(gè)組件若有狀態(tài),則狀態(tài)一旦改變,所有的子組件需要重新渲染。所以一個(gè)有狀態(tài)的組件,應(yīng)該是沒(méi)有子組件的。即 有狀態(tài)的組件不做渲染,有渲染的組件沒(méi)有狀態(tài)。
狀態(tài)變更的函數(shù)不支持回調(diào)。this.setState() 中支持第二個(gè)參數(shù),允許我們?cè)跔顟B(tài)變更后,傳入回調(diào)函數(shù)做一些其他事情。但是 useState 不支持。詳見(jiàn)。
參考鏈接
making-sense-of-react-hooks
rehooks
awesome-react-hooks
如何使用useEffect來(lái)獲取數(shù)據(jù)
hooks 是如何工作的
更多關(guān)于 hooks 的討論
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/104060.html
摘要:前言一直對(duì)這個(gè)新特性非常感興趣,終于今天有時(shí)間,花了大半天時(shí)間,把的官方教程過(guò)了一遍,收獲頗多,驚嘆這個(gè)新特性真好用,以后開(kāi)發(fā)用這個(gè)怕是要起飛了 前言:一直對(duì)這個(gè)新特性非常感興趣,終于今天有時(shí)間,花了大半天時(shí)間,把 Hooks的官方教程過(guò)了一遍,收獲頗多,驚嘆這個(gè)新特性真 TM 好用,以后開(kāi)發(fā)用這個(gè)怕是要起飛了
摘要:使用完成副作用操作,賦值給的函數(shù)會(huì)在組件渲染到屏幕之后。如此很容易產(chǎn)生,并且導(dǎo)致邏輯不一致。同時(shí),這也是很多人將與狀態(tài)管理庫(kù)結(jié)合使用的原因之一。當(dāng)我們通過(guò)的第二個(gè)數(shù)組類型參數(shù),指明當(dāng)前的依賴,就能避免不相關(guān)的執(zhí)行開(kāi)銷了。 前言 本文內(nèi)容大部分參考了 overreacted.io 博客一文,同時(shí)結(jié)合 React Hook 官方 文章,整理并歸納一些筆記和輸出個(gè)人的一些理解 什么是 Hoo...
摘要:的來(lái)源鉤子,顧名思義,為了解決在函數(shù)組件中使用和生命周期,同時(shí)提高業(yè)務(wù)邏輯復(fù)用。函數(shù)組件等同于一個(gè)純的專門用作渲染的函數(shù),我們知道,在函數(shù)組件中,我們無(wú)法使用和生命周期,這也是為了解決的問(wèn)題。 Hooks的來(lái)源 Hooks => 鉤子,顧名思義,為了解決在函數(shù)組件(Function Component)中使用state和生命周期,同時(shí)提高業(yè)務(wù)邏輯復(fù)用。 Function Co...
摘要:基于與網(wǎng)易云音樂(lè)開(kāi)發(fā),技術(shù)棧主要是目前主要是著重小程序端的展示,主要也是借此項(xiàng)目強(qiáng)化下上述幾個(gè)技術(shù)棧的使用,通過(guò)這個(gè)項(xiàng)目也可以幫助你快速使用開(kāi)發(fā)一個(gè)屬于你自己的小程序地址,感興趣的話可以關(guān)注下,功能會(huì)進(jìn)行持續(xù)完善快速開(kāi)始首先需要在目錄下 基于Taro與網(wǎng)易云音樂(lè)api開(kāi)發(fā),技術(shù)棧主要是:typescript+taro+taro-ui+redux,目前主要是著重小程序端的展示,主要也是借...
摘要:此優(yōu)化有助于避免在每個(gè)渲染上進(jìn)行昂貴的計(jì)算。同樣也是一個(gè)函數(shù),接受兩個(gè)參數(shù),第一個(gè)參數(shù)為函數(shù),第二個(gè)參數(shù)為要比對(duì)的值,返回一個(gè)值。同理,第二個(gè)參數(shù)傳入的值沒(méi)有更新時(shí),不會(huì)執(zhí)行。以上代碼的地址為初體驗(yàn) 什么是Hooks?Hooks是react即將推出的功能,它允許您在不編寫類的情況下使用狀態(tài)和其他React功能。我的理解就是可以用寫無(wú)狀態(tài)組件的方式去編寫擁有狀態(tài)的組件。遺憾的是,正式版1...
閱讀 1792·2023-04-26 01:41
閱讀 3090·2021-11-23 09:51
閱讀 2751·2021-10-09 09:43
閱讀 9074·2021-09-22 15:13
閱讀 2469·2021-09-07 09:59
閱讀 2638·2019-08-30 15:44
閱讀 1145·2019-08-30 12:45
閱讀 2630·2019-08-30 12:43