摘要:更容易將組件的與狀態(tài)分離。也就是只提供狀態(tài)處理方法,不會(huì)持久化狀態(tài)。大體思路是利用共享一份數(shù)據(jù),作為的數(shù)據(jù)源。精讀帶來的約定函數(shù)必須以命名開頭,因?yàn)檫@樣才方便做檢查,防止用判斷包裹語句。前端精讀幫你篩選靠譜的內(nèi)容。
1 引言
React Hooks 是 React 16.7.0-alpha 版本推出的新特性,想嘗試的同學(xué)安裝此版本即可。
React Hooks 要解決的問題是狀態(tài)共享,是繼 render-props 和 higher-order components 之后的第三種狀態(tài)共享方案,不會(huì)產(chǎn)生 JSX 嵌套地獄問題。
狀態(tài)共享可能描述的不恰當(dāng),稱為狀態(tài)邏輯復(fù)用會(huì)更恰當(dāng),因?yàn)橹还蚕頂?shù)據(jù)處理邏輯,不會(huì)共享數(shù)據(jù)本身。
不久前精讀分享過的一篇 Epitath 源碼 - renderProps 新用法 就是解決 JSX 嵌套問題,有了 React Hooks 之后,這個(gè)問題就被官方正式解決了。
為了更快理解 React Hooks 是什么,先看筆者引用的下面一段 renderProps 代碼:
function App() { return ({({ on, toggle }) => ( ) })}
恰巧,React Hooks 解決的也是這個(gè)問題:
function App() { const [open, setOpen] = useState(false); return ( <>setOpen(false)} onCancel={() => setOpen(false)} /> > ); }
可以看到,React Hooks 就像一個(gè)內(nèi)置的打平 renderProps 庫,我們可以隨時(shí)創(chuàng)建一個(gè)值,與修改這個(gè)值的方法。看上去像 function 形式的 setState,其實(shí)這等價(jià)于依賴注入,與使用 setState 相比,這個(gè)組件是沒有狀態(tài)的。
2 概述React Hooks 帶來的好處不僅是 “更 FP,更新粒度更細(xì),代碼更清晰”,還有如下三個(gè)特性:
多個(gè)狀態(tài)不會(huì)產(chǎn)生嵌套,寫法還是平鋪的(renderProps 可以通過 compose 解決,可不但使用略為繁瑣,而且因?yàn)閺?qiáng)制封裝一個(gè)新對(duì)象而增加了實(shí)體數(shù)量)。
Hooks 可以引用其他 Hooks。
更容易將組件的 UI 與狀態(tài)分離。
第二點(diǎn)展開說一下:Hooks 可以引用其他 Hooks,我們可以這么做:
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 (
這個(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)的。
順帶一提,這個(gè)例子也可以用來理解 對(duì) React Hooks 的一些思考 一文的那句話:“有狀態(tài)的組件沒有渲染,有渲染的組件沒有狀態(tài)”:
useFriendStatusBoolean 與 useFriendStatusString 是有狀態(tài)的組件(使用 useState),沒有渲染(返回非 UI 的值),這樣就可以作為 Custom Hooks 被任何 UI 組件調(diào)用。
FriendListItem 與 FriendListStatus 是有渲染的組件(返回了 JSX),沒有狀態(tài)(沒有使用 useState),這就是一個(gè)純函數(shù) UI 組件,
利用 useState 創(chuàng)建 ReduxRedux 的精髓就是 Reducer,而利用 React Hooks 可以輕松創(chuàng)建一個(gè) Redux 機(jī)制:
// 這就是 Redux function useReducer(reducer, initialState) { const [state, setState] = useState(initialState); function dispatch(action) { const nextState = reducer(state, action); setState(nextState); } return [state, dispatch]; }
這個(gè)自定義 Hook 的 value 部分當(dāng)作 redux 的 state,setValue 部分當(dāng)作 redux 的 dispatch,合起來就是一個(gè) redux。而 react-redux 的 connect 部分做的事情與 Hook 調(diào)用一樣:
// 一個(gè) Action function useTodos() { const [todos, dispatch] = useReducer(todosReducer, []); function handleAddClick(text) { dispatch({ type: "add", text }); } return [todos, { handleAddClick }]; } // 綁定 Todos 的 UI function TodosUI() { const [todos, actions] = useTodos(); return ( <> {todos.map((todo, index) => ({todo.text}))} > ); }
useReducer 已經(jīng)作為一個(gè)內(nèi)置 Hooks 了,在這里可以查閱所有 內(nèi)置 Hooks。
不過這里需要注意的是,每次 useReducer 或者自己的 Custom Hooks 都不會(huì)持久化數(shù)據(jù),所以比如我們創(chuàng)建兩個(gè) App,App1 與 App2:
function App1() { const [todos, actions] = useTodos(); return todo count: {todos.length}; } function App2() { const [todos, actions] = useTodos(); return todo count: {todos.length}; } function All() { return ( <>> ); }
這兩個(gè)實(shí)例同時(shí)渲染時(shí),并不是共享一個(gè) todos 列表,而是分別存在兩個(gè)獨(dú)立 todos 列表。也就是 React Hooks 只提供狀態(tài)處理方法,不會(huì)持久化狀態(tài)。
如果要真正實(shí)現(xiàn)一個(gè) Redux 功能,也就是全局維持一個(gè)狀態(tài),任何組件 useReducer 都會(huì)訪問到同一份數(shù)據(jù),可以和 useContext 一起使用。
大體思路是利用 useContext 共享一份數(shù)據(jù),作為 Custom Hooks 的數(shù)據(jù)源。具體實(shí)現(xiàn)可以參考 redux-react-hook。
利用 useEffect 代替一些生命周期在 useState 位置附近,可以使用 useEffect 處理副作用:
useEffect(() => { const subscription = props.source.subscribe(); return () => { // Clean up the subscription subscription.unsubscribe(); }; });
useEffect 的代碼既會(huì)在初始化時(shí)候執(zhí)行,也會(huì)在后續(xù)每次 rerender 時(shí)執(zhí)行,而返回值在析構(gòu)時(shí)執(zhí)行。這個(gè)更多帶來的是便利,對(duì)比一下 React 版 G2 調(diào)用流程:
class Component extends React.PureComponent{ private chart: G2.Chart = null; private rootDomRef: React.ReactInstance = null; componentDidMount() { this.rootDom = ReactDOM.findDOMNode(this.rootDomRef) as HTMLDivElement; this.chart = new G2.Chart({ container: document.getElementById("chart"), forceFit: true, height: 300 }); this.freshChart(this.props); } componentWillReceiveProps(nextProps: Props) { this.freshChart(nextProps); } componentWillUnmount() { this.chart.destroy(); } freshChart(props: Props) { // do something this.chart.render(); } render() { return (this.rootDomRef = ref)} />; } }用 React Hooks 可以這么做:
function App() { const ref = React.useRef(null); let chart: G2.Chart = null; React.useEffect(() => { if (!chart) { chart = new G2.Chart({ container: ReactDOM.findDOMNode(ref.current) as HTMLDivElement, width: 500, height: 500 }); } // do something chart.render(); return () => chart.destroy(); }); return ; }可以看到將細(xì)碎的代碼片段結(jié)合成了一個(gè)完整的代碼塊,更維護(hù)。
現(xiàn)在介紹了 useState useContext useEffect useRef 等常用 hooks,更多可以查閱:內(nèi)置 Hooks,相信不久的未來,這些 API 又會(huì)成為一套新的前端規(guī)范。
3 精讀 Hooks 帶來的約定Hook 函數(shù)必須以 "use" 命名開頭,因?yàn)檫@樣才方便 eslint 做檢查,防止用 condition 判斷包裹 useHook 語句。
為什么不能用 condition 包裹 useHook 語句,詳情可以見 官方文檔,這里簡單介紹一下。
React Hooks 并不是通過 Proxy 或者 getters 實(shí)現(xiàn)的(具體可以看這篇文章 React hooks: not magic, just arrays),而是通過數(shù)組實(shí)現(xiàn)的,每次 useState 都會(huì)改變下標(biāo),如果 useState 被包裹在 condition 中,那每次執(zhí)行的下標(biāo)就可能對(duì)不上,導(dǎo)致 useState 導(dǎo)出的 setter 更新錯(cuò)數(shù)據(jù)。
雖然有 eslint-plugin-react-hooks 插件保駕護(hù)航,但這第一次將 “約定優(yōu)先” 理念引入了 React 框架中,帶來了前所未有的代碼命名和順序限制(函數(shù)命名遭到官方限制,JS 自由主義者也許會(huì)暴跳如雷),但帶來的便利也是前所未有的(沒有比 React Hooks 更好的狀態(tài)共享方案了,約定帶來提效,自由的代價(jià)就是回到 renderProps or HOC,各團(tuán)隊(duì)可以自行評(píng)估)。
筆者認(rèn)為,React Hooks 的誕生,也許來自于這個(gè)靈感:“不如通過增加一些約定,徹底解決狀態(tài)共享問題吧!”
React 約定大于配置腳手架 nextjs umi 以及筆者的 pri 都通過有 “約定路由” 的功能,大大降低了路由配置復(fù)雜度,那么 React Hooks 就像代碼級(jí)別的約定,大大降低了代碼復(fù)雜度。狀態(tài)與 UI 的界限會(huì)越來越清晰因?yàn)?React Hooks 的特性,如果一個(gè) Hook 不產(chǎn)生 UI,那么它可以永遠(yuǎn)被其他 Hook 封裝,雖然允許有副作用,但是被包裹在 useEffect 里,總體來說還是挺函數(shù)式的。而 Hooks 要集中在 UI 函數(shù)頂部寫,也很容易養(yǎng)成書寫無狀態(tài) UI 組件的好習(xí)慣,踐行 “狀態(tài)與 UI 分開” 這個(gè)理念會(huì)更容易。
不過這個(gè)理念稍微有點(diǎn)蹩腳的地方,那就是 “狀態(tài)” 到底是什么。
function App() { const [count, setCount] = useCount(); return {count}; }
我們知道 useCount 算是無狀態(tài)的,因?yàn)?React Hooks 本質(zhì)就是 renderProps 或者 HOC 的另一種寫法,換成 renderProps 就好理解了:
{(count, setCount) => ; function App(props) { return {props.count}; }} 可以看到 App 組件是無狀態(tài)的,輸出完全由輸入(Props)決定。
那么有狀態(tài)無 UI 的組件就是 useCount 了:
function useCount() { const [count, setCount] = useState(0); return [count, setCount]; }有狀態(tài)的地方應(yīng)該指 useState(0) 這句,不過這句和無狀態(tài) UI 組件 App 的 useCount() 很像,既然 React 把 useCount 成為自定義 Hook,那么 useState 就是官方 Hook,具有一樣的定義,因此可以認(rèn)為 useCount 是無狀態(tài)的,useState 也是一層 renderProps,最終的狀態(tài)其實(shí)是 useState 這個(gè) React 內(nèi)置的組件。
我們看 renderProps 嵌套的表達(dá):
{(count, setCount) => ( {" "} {/**雖然是透傳,但給 count 做了去重,不可謂沒有作用 */} {(count, setCount) => )}} 能確定的是,App 一定有 UI,而上面兩層父級(jí)組件一定沒有 UI。為了最佳實(shí)踐,我們盡量避免 App 自己維護(hù)狀態(tài),而其父級(jí)的 RenderProps 組件可以維護(hù)狀態(tài)(也可以不維護(hù)狀態(tài),做個(gè)二傳手)。因此可以考慮在 “有狀態(tài)的組件沒有渲染,有渲染的組件沒有狀態(tài)” 這句話后面加一句:沒渲染的組件也可以沒狀態(tài)。
4 總結(jié)把 React Hooks 當(dāng)作更便捷的 RenderProps 去用吧,雖然寫法看上去是內(nèi)部維護(hù)了一個(gè)狀態(tài),但其實(shí)等價(jià)于注入、Connect、HOC、或者 renderProps,那么如此一來,使用 renderProps 的門檻會(huì)大大降低,因?yàn)?Hooks 用起來實(shí)在是太方便了,我們可以抽象大量 Custom Hooks,讓代碼更加 FP,同時(shí)也不會(huì)增加嵌套層級(jí)。
5 更多討論討論地址是:精讀《React Hooks》 · Issue #111 · dt-fe/weekly如果你想?yún)⑴c討論,請(qǐng)點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。前端精讀 - 幫你篩選靠譜的內(nèi)容。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/99188.html
摘要:拿到的都是而不是原始值,且這個(gè)值會(huì)動(dòng)態(tài)變化。精讀對(duì)于的與,筆者做一些對(duì)比。因此采取了作為優(yōu)化方案只有當(dāng)?shù)诙€(gè)依賴參數(shù)變化時(shí)才返回新引用。不需要使用等進(jìn)行性能優(yōu)化,所有性能優(yōu)化都是自動(dòng)的。前端精讀幫你篩選靠譜的內(nèi)容。 1. 引言 Vue 3.0 的發(fā)布引起了軒然大波,讓我們解讀下它的 function api RFC 詳細(xì)了解一下 Vue 團(tuán)隊(duì)是怎么想的吧! 首先官方回答了幾個(gè)最受關(guān)注的...
摘要:未來可能成為官方之一。討論地址是精讀組件如果你想?yún)⑴c討論,請(qǐng)點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。前端精讀幫你篩選靠譜的內(nèi)容。 1. 引言 為什么要了解 Function 寫法的組件呢?因?yàn)樗谧兊迷絹碓街匾?那么 React 中 Function Component 與 Class Component 有何不同? how-are-function-components-di...
摘要:可以看到,這樣不僅沒有占用組件自己的,也不需要手寫回調(diào)函數(shù)進(jìn)行處理,這些處理都?jí)嚎s成了一行。效果通過拿到周期才執(zhí)行的回調(diào)函數(shù)。實(shí)現(xiàn)等價(jià)于的回調(diào)僅執(zhí)行一次時(shí),因此直接把回調(diào)函數(shù)拋出來即可。 1 引言 上周的 精讀《React Hooks》 已經(jīng)實(shí)現(xiàn)了對(duì) React Hooks 的基本認(rèn)知,也許你也看了 React Hooks 基本實(shí)現(xiàn)剖析(就是數(shù)組),但理解實(shí)現(xiàn)原理就可以用好了嗎?學(xué)的是...
摘要:在讀了一些文章后,大致是找到自己總是掉坑的原因了沒理解中的特性。通過這個(gè)示例,相信會(huì)比較容易地理解特性,并如何使用來暫時(shí)繞過它。在知道并理解這個(gè)特性后,有助于進(jìn)一步熟悉了的運(yùn)行機(jī)制,減少掉坑的次數(shù)。 由于剛使用 React hooks 不久,對(duì)它的脾氣還拿捏不準(zhǔn),掉了很多次坑;這里的 坑 的意思并不是說 React hooks 的設(shè)計(jì)有問題,而是我在使用的時(shí)候,因?yàn)檫€沒有跟上它的理念導(dǎo)...
摘要:會(huì)自動(dòng)觸發(fā)函數(shù)內(nèi)回調(diào)函數(shù)的執(zhí)行。因此利用并將依賴置為使代碼在所有渲染周期內(nèi),只在初始化執(zhí)行一次。同時(shí)代碼里還對(duì)等公共方法進(jìn)行了包裝,讓這些回調(diào)函數(shù)中自帶效果。前端精讀幫你篩選靠譜的內(nèi)容。 1. 引言 react-easy-state 是個(gè)比較有趣的庫,利用 Proxy 創(chuàng)建了一個(gè)非常易用的全局?jǐn)?shù)據(jù)流管理方式。 import React from react; import { stor...
閱讀 3715·2021-11-17 09:33
閱讀 2725·2021-09-22 15:12
閱讀 3344·2021-08-12 13:24
閱讀 2439·2019-08-30 11:14
閱讀 1733·2019-08-29 14:09
閱讀 1326·2019-08-26 14:01
閱讀 3061·2019-08-26 13:49
閱讀 1775·2019-08-26 12:16