摘要:比如在條件判斷中使用,在循環,嵌套函數中使用,都會造成執行順序不一致的問題。而比如定時器,事件監聽。第一個參數的返回值,會在組件卸載時執行,相當于,可以清理定時器,移除事件監聽,取消一些訂閱。
什么是 Hooks?
不通過編寫類組件的情況下,可以在組件內部使用狀態(state) 和其他 React 特性(生命周期,context)的技術Hooks 為什么會出現
在之前的 React 版本中,組件分為兩種:函數式組件(或無狀態組件(StatelessFunctionComponent))和類組件,而函數式組件是一個比較的純潔的 props => UI 的輸入、輸出關系,但是類組件由于有組件自己的內部狀態,所以其輸出就由 props 和 state 決定,類組件的輸入、輸出關系就不再那么純潔。同時也會帶來下列問題:
狀態邏輯難以復用。很多類組件都有一些類似的狀態邏輯,但是為了重用這些狀態邏輯,社區提出了 render props 或者 hoc 這些方案,但是這兩種模式對組件的侵入性太強。另外,會產生組件嵌套地獄的問題。
大多數開發者在編寫組件時,不管這個組件有木有內部狀態,會不會執行生命周期函數,都會將組件編寫成類組件,后續迭代可能增加了內部狀態,又增加了副作用處理,又在組件中調用了一些生命周期函數,文件代碼行數日益增多,最后導致組件中充斥著無法管理的混亂的狀態邏輯代碼和各種副作用,各種狀態邏輯散落在實例方法和生命周期方法中,維護性變差,拆分更是難上加難。
在類組件中,需要開發者額外去關注 this 問題,事件監聽器的添加和移除等等。
State Hookstate hook 提供了一種可以在 function component 中添加狀態的方式。通過 state hook,可以抽取狀態邏輯,使組件變得可測試,可重用。開發者可以在不改變組件層次結構的情況下,去重用狀態邏輯。更好的實現關注點分離。
一個簡單的使用 useState 栗子
import React, { useState } from "react"; const StateHook = () => { const [count, setCount] = useState(0); return (); };you clicked {count} times
幾點說明:
useState 推薦一種更加細粒度的控制狀態的方式,即一個狀態對應一個狀態設置函數,其接受的參數將作為這個狀態的初始值。其返回一個長度為2的元組,第一項為當前狀態,第二項為更新函數。
useState 的執行順序在每一次更新渲染時必須保持一致,否則多個 useState 調用將不會得到各自獨立的狀態,也會造成狀態對應混亂。比如在條件判斷中使用 hook,在循環,嵌套函數中使用 hook,都會造成 hook 執行順序不一致的問題。最后導致狀態的混亂。另外,所有的狀態聲明都應該放在函數頂部,首先聲明。
useState 和 setState 的區別
useState 將 setState 進行覆蓋式更新,而 setState 則將狀態進行合并式更新。
一個不正確的栗子
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; // 這里不可以多帶帶的設置某一個字段 新的狀態必須與初始的狀態類型保持一致 // 如果只設置了其中一個字段,編譯器會報錯,同時其余的字段也會丟失 setUser({ name, email }); }; const handleEmailChange = (event: ChangeEvent ) => { const { target: { value: email } } = event; // 這里不可以多帶帶的設置某一個字段 新的狀態必須與初始的狀態類型保持一致 setUser({ name, email }); }; return ( <> > ); }
正確的做法
import React, { useState, ChangeEvent } from "react"; const UserForm = () => { // 一個狀態對應一個狀態更新函數 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 ( <> > ); }
數據獲取,設置訂閱,手動的更改 DOM,都可以稱為副作用,可以將副作用分為兩種,一種是需要清理的,另外一種是不需要清理的。比如網絡請求,DOM 更改,日志這些副作用都不要清理。而比如定時器,事件監聽。
一個簡單使用 effect hook 去修改文檔標題的栗子。
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
在調用 useEffect 后,相當于告訴 React 在每一次組件更新完成渲染后,都調用傳入 useEffect 中的函數,包括初次渲染以及后續的每一次更新渲染。
幾點說明:
useEffect(effectCallback: () => void, deps: any[]) 接收兩個參數,第二個參數依賴項是可選的,表示這個 effect 要依賴哪些值。
有時候我們并不想每次渲染 effect 都執行,只有某些值發生變化才去執行 effect,這個時候我們可以指定這個 effect 的依賴列表,可以是一個也可以幾個,當其中列表中的某一個值發生變化,effect 才會執行。
第一個參數的返回值,會在組件卸載時執行,相當于 componentWillUnmount,可以清理定時器,移除事件監聽,取消一些訂閱。
當第二個參數為一個空數組時,相當于 componentDidMount 和 componentWillUnmount,表明這個 effect 沒有任何依賴,只在首次渲染時執行。
Custom Hook也可以使用 useEffect 和 useState 實現自定義 hook。
一個給 DOM 元素添加事件監聽器的栗子。
import { useRef, useEffect } from "react"; type EventType = keyof HTMLElementEventMap; type Handler = (event: Event) => void; const useEventListener = ( eventName: EventType, handler: Handler, element: EventTarget = document ) => { // 這里使用 `useRef` 來保存傳入的監聽器, // 在監聽器變更后,去更新 `useRef` 返回的對象的 `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]); }
一個使用 useReducer 來實現加、減計數器的栗子。這里雖然使用 useReducer 創建了類似 redux 的 功能,但是如果有多個組件都引用了這個 hook,那么這個 hook 提供的狀態是相互獨立、互不影響的,即 useReducer 只提供了狀態管理,但是并沒有提供數據持久化的功能。redux 卻提供了一種全局維護同一個數據源的機制。所以可以利用 useReducer 和 Context 來實現數據持久化的功能。
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); };
一個對封裝數據請求栗子。
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
隨來 hooks 帶來了新的組件編寫范式,但是下面兩條規則還是要開發者注意的。
在頂部使用 hook,不要使用 hook 在條件判斷,循環,嵌套函數。
只在 function component 中使用 hook,或者自定義 hook 中使用 hook, 不要在常規的 JavaScript 函數中使用 hook
新的問題hooks 的帶來,雖然解決之前存在的一些問題,但是也帶來了新的問題。
異常捕獲。之前的版本中,我們可以用 componentDidCatch 來捕獲組件作用域內的異常,做一些提示。但是在 hooks 中 ,我們只能使用 try {} catch(){} ` 去捕獲,使用姿勢也比較別扭。
一個組件若有狀態,則狀態一旦改變,所有的子組件需要重新渲染。所以一個有狀態的組件,應該是沒有子組件的。即 有狀態的組件不做渲染,有渲染的組件沒有狀態。
狀態變更的函數不支持回調。this.setState() 中支持第二個參數,允許我們在狀態變更后,傳入回調函數做一些其他事情。但是 useState 不支持。詳見。
參考鏈接
making-sense-of-react-hooks
rehooks
awesome-react-hooks
如何使用useEffect來獲取數據
hooks 是如何工作的
更多關于 hooks 的討論
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/104060.html
摘要:前言一直對這個新特性非常感興趣,終于今天有時間,花了大半天時間,把的官方教程過了一遍,收獲頗多,驚嘆這個新特性真好用,以后開發用這個怕是要起飛了 前言:一直對這個新特性非常感興趣,終于今天有時間,花了大半天時間,把 Hooks的官方教程過了一遍,收獲頗多,驚嘆這個新特性真 TM 好用,以后開發用這個怕是要起飛了
摘要:使用完成副作用操作,賦值給的函數會在組件渲染到屏幕之后。如此很容易產生,并且導致邏輯不一致。同時,這也是很多人將與狀態管理庫結合使用的原因之一。當我們通過的第二個數組類型參數,指明當前的依賴,就能避免不相關的執行開銷了。 前言 本文內容大部分參考了 overreacted.io 博客一文,同時結合 React Hook 官方 文章,整理并歸納一些筆記和輸出個人的一些理解 什么是 Hoo...
摘要:的來源鉤子,顧名思義,為了解決在函數組件中使用和生命周期,同時提高業務邏輯復用。函數組件等同于一個純的專門用作渲染的函數,我們知道,在函數組件中,我們無法使用和生命周期,這也是為了解決的問題。 Hooks的來源 Hooks => 鉤子,顧名思義,為了解決在函數組件(Function Component)中使用state和生命周期,同時提高業務邏輯復用。 Function Co...
摘要:基于與網易云音樂開發,技術棧主要是目前主要是著重小程序端的展示,主要也是借此項目強化下上述幾個技術棧的使用,通過這個項目也可以幫助你快速使用開發一個屬于你自己的小程序地址,感興趣的話可以關注下,功能會進行持續完善快速開始首先需要在目錄下 基于Taro與網易云音樂api開發,技術棧主要是:typescript+taro+taro-ui+redux,目前主要是著重小程序端的展示,主要也是借...
摘要:此優化有助于避免在每個渲染上進行昂貴的計算。同樣也是一個函數,接受兩個參數,第一個參數為函數,第二個參數為要比對的值,返回一個值。同理,第二個參數傳入的值沒有更新時,不會執行。以上代碼的地址為初體驗 什么是Hooks?Hooks是react即將推出的功能,它允許您在不編寫類的情況下使用狀態和其他React功能。我的理解就是可以用寫無狀態組件的方式去編寫擁有狀態的組件。遺憾的是,正式版1...
閱讀 1767·2023-04-26 01:41
閱讀 3073·2021-11-23 09:51
閱讀 2733·2021-10-09 09:43
閱讀 9019·2021-09-22 15:13
閱讀 2452·2021-09-07 09:59
閱讀 2624·2019-08-30 15:44
閱讀 1132·2019-08-30 12:45
閱讀 2616·2019-08-30 12:43