摘要:另外也不利于組件的,及。所以在使用時,盡量將相關(guān)聯(lián)的,會共同變化的值放入一個。有同學(xué)可能會想,每次后都會執(zhí)行,這樣會不會對性能造成影響。另外必須以開頭來命名,這樣工具才能正確檢測其是否符合規(guī)范。
由于工作的原因我已經(jīng)很長時間沒接觸過React了。前段時間圈子里都在討論React Hooks,出于好奇也學(xué)習(xí)了一番,特此整理以加深理解。緣由
在web應(yīng)用無所不能的9012年,組成應(yīng)用的Components也越來越復(fù)雜,冗長而難以復(fù)用的代碼給開發(fā)者們造成了很多麻煩。比如:
難以復(fù)用stateful的代碼,render props及HOC雖然解決了問題,但對組件的包裹改變了組件樹的層級,存在冗余;
在ComponentDidMount、ComponentDidUpdate、ComponentWillUnmount等生命周期中做獲取數(shù)據(jù),訂閱/取消事件,操作ref等相互之間無關(guān)聯(lián)的操作,而把訂閱/取消這種相關(guān)聯(lián)的操作分開,降低了代碼的可讀性;
與其他語言中的class概念差異較大,需要對事件處理函數(shù)做bind操作,令人困擾。另外class也不利于組件的AOT compile,minify及hot loading。
在這種背景下,React在16.8.0引入了React Hooks。
特性主要介紹state hook,effect hook及custom hook
State Hook最基本的應(yīng)用如下:
import React, { useState } from "react" function counter() { const [count, setCount] = useState(0) return () }You have clicked {count} times
調(diào)用useState,傳入初始值,通過數(shù)組的結(jié)構(gòu)賦值得到獨立的local state count,及setCount。count可以理解為class component中的state,可見這里的state不局限于對象,可以為number,string,當(dāng)然也可以是一個對象。而setCount可以理解為class component中的setState,不同的是setState會merge新老state,而hook中的set函數(shù)會直接替換,這就意味著如果state是對象時,每次set應(yīng)該傳入所有屬性,而不能像class component那樣僅傳入變化的值。所以在使用useState時,盡量將相關(guān)聯(lián)的,會共同變化的值放入一個object。
再看看有多個“l(fā)ocal state”的情況:
import React, { useState } from "react" function person() { const [name, setName] = useState("simon") const [age, setAge] = useState(24) return () }name: {name}
age: {age}
我們知道當(dāng)函數(shù)執(zhí)行完畢,函數(shù)作用域內(nèi)的變量都會銷毀,hooks中的state在component首次render后被React保留下來了。那么在下一次render時,React如何將這些保留的state與component中的local state對應(yīng)起來呢。這里給出一個簡單版本的實現(xiàn):
const stateArr = [] const setterArr = [] let cursor = 0 let isFirstRender = true function createStateSetter(cursor) { return state => { stateArr[cursor] = state } } function useState(initState) { if (isFirstRender) { stateArr.push(initState) setterArr.push(createStateSetter(cursor)) isFirstRender = false } const state = stateArr[cursor] const setter = setterArr[cursor] cursor++ return [state, setter] }
可以看出React需要保證多個hooks在component每次render的時候的執(zhí)行順序都保持一致,否則就會出現(xiàn)錯誤。這也是React hooks rule中必須在top level使用hooks的由來——條件,遍歷等語句都有可能會改變hooks執(zhí)行的順序。
Effect Hookimport React, { useState, useEffect } from "react" function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null) function handleStatusChange(status) { setIsOnline(status.isOnline) } // 基本寫法 useEffect(() => { document.title = "Dom is ready" }) // 需要取消操作的寫法 useEffect(() => { ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange) return function cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange) } }) if (isOnline === null) { return "Loading..." } return isOnline ? "Online" : "Offline" }
可以看到上面的代碼在傳入useEffect的函數(shù)(effect)中做了一些"side effect",在class component中我們通常會在componentDidMount,componentDidUpdate中去做這些事情。另外在class component中,需要在componentDidMount中訂閱,在componentWillUnmount中取消訂閱,這樣將一件事拆成兩件事做,不僅可讀性低,還容易產(chǎn)生bug:
class FriendStatus extends React.Component { constructor(props) { super(props); this.state = { isOnline: null }; this.handleStatusChange = this.handleStatusChange.bind(this); } componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } handleStatusChange(status) { this.setState({ isOnline: status.isOnline }); } render() { if (this.state.isOnline === null) { return "Loading..."; } return this.state.isOnline ? "Online" : "Offline"; } }
如上代碼,如果props中的friend.id發(fā)生變化,則會導(dǎo)致訂閱和取消的id不一致,如需解決需要在componentDidUpdate中先取消訂閱舊的再訂閱新的,代碼非常冗余。而useEffect hook在這一點上是渾然天成的。另外effect函數(shù)在每次render時都是新創(chuàng)建的,這其實是有意而為之,因為這樣才能取得最新的state值。
有同學(xué)可能會想,每次render后都會執(zhí)行effect,這樣會不會對性能造成影響。其實effect是在頁面渲染完成之后執(zhí)行的,不會阻塞,而在effect中執(zhí)行的操作往往不要求同步完成,除了少數(shù)如要獲取寬度或高度,這種情況需要使用其他的hook(useLayoutEffect),此處不做詳解。即使這樣,React也提供了控制的方法,及useEffect的第二個參數(shù)————一個數(shù)組,如果數(shù)組中的值不發(fā)生變化的話就跳過effect的執(zhí)行:
useEffect(() => { ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange) return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); } }, [props.friend.id])Custom Hook
A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.
Custom Hook的使命是解決stateful logic復(fù)用的問題,如上面例子中的FriendStatus,在一個聊天應(yīng)用中可能多個組件都需要知道好友的在線狀態(tài),將FriendStatus抽象成這樣的hook:
function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) { setIsOnline(status.isOnline); } useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; } function FriendStatus(props) { const isOnline = useFriendStatus(props.friend.id) if (isOnline === null) { return "Loading..." } return isOnline ? "Online" : "Offline" } function FriendListItem(props) { const isOnline = useFriendStatus(props.friend.id) return (
FriendStatus和FriendListItem中的isOnline是獨立的,因custom hook復(fù)用的是stateful logic,而不是state本身。另外custom hook必須以use開頭來命名,這樣linter工具才能正確檢測其是否符合規(guī)范。
除了以上三種hook,React還提供了useContext, useReducer, useCallback, useMemo, useRef, useImperativeHandle, useLayoutEffect, useDebugValue內(nèi)置hook,它們的用途可以參考官方文檔,這里我想多帶帶講講useRef。
顧名思義,這個hook應(yīng)該跟ref相關(guān)的:
function TextInputWithFocusButton() { const inputEl = useRef(null) const onButtonClick = () => { inputEl.current.focus() } return ( <> > ) }
來看看官方文檔上的說明:
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
這句話告訴我們在組件的整個生命周期里,inputEl.current都是存在的,這擴展了useRef本身的用途,可以使用useRef維護(hù)類似于class component中實例屬性的變量:
function Timer() { const intervalRef = useRef() useEffect(() => { const id = setInterval(() => { // ... }) intervalRef.current = id return () => { clearInterval(intervalRef.current) } }) // ... }
這在class component中是理所當(dāng)然的,但不要忘記Timer僅僅是一個函數(shù),函數(shù)執(zhí)行完畢后函數(shù)作用域內(nèi)的變量將會銷毀,所以這里需要使用useRef來保持這個timerId。類似的useRef還可以用來獲取preState:
function Counter() { const [count, setCount] = useState(0) const prevCountRef = useRef() useEffect(() => { prevCountRef.current = count // 由于useEffect中的函數(shù)是在render完成之后異步執(zhí)行的,所以在每次render時prevCountRef.current的值為上一次的count值 }) const prevCount = prevCountRef.current return參考文章&拓展閱讀Now: {count}, before: {prevCount}
}
React Hooks官方文檔
React hooks: not magic, just arrays
Why Isn’t X a Hook?
Making Sense of React Hooks
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/102216.html
本文不會過多講解基礎(chǔ)知識,更多說的是在使用useRef如何能擺脫 這個 閉包陷阱 ? react hooks 的閉包陷阱 基本每個開發(fā)員都有遇見,這是很令人抓狂的?! ?以下react示范demo,均為react 16.8.3 版本) 列一個具體的場景: functionApp(){ const[count,setCount]=useState(1); useEffect(()=...
摘要:注冊方法之后,當(dāng)執(zhí)行了當(dāng)前的,那么掛載正在當(dāng)前上的方法就會被執(zhí)行。比如在開始編譯之前,就能觸發(fā)鉤子,就用到了當(dāng)前的。上面都是前置知識,下面通過解讀一個源碼來鞏固下。先看一段簡單的源碼。,是眾多的的一個,官網(wǎng)的解釋是編譯創(chuàng)建之后,執(zhí)行插件。 通過解讀webpack-manifest-plugin,了解下plugin機制 先簡單說一下這個插件的功能,生成一份資源清單的json文件,如下 s...
摘要:什么是每一個都有一個對應(yīng)的,記錄這個節(jié)點的各種狀態(tài),是一鏈表的結(jié)構(gòu)的串聯(lián)起來。 1. 什么是fiber 每一個ReactElement都有一個對應(yīng)的fiber, 記錄這個節(jié)點的各種狀態(tài), fiber是一鏈表的結(jié)構(gòu)的串聯(lián)起來。showImg(https://segmentfault.com/img/bVbqVZR?w=540&h=708); 2. Fiber的組成 export type...
摘要:更容易將組件的與狀態(tài)分離。也就是只提供狀態(tài)處理方法,不會持久化狀態(tài)。大體思路是利用共享一份數(shù)據(jù),作為的數(shù)據(jù)源。精讀帶來的約定函數(shù)必須以命名開頭,因為這樣才方便做檢查,防止用判斷包裹語句。前端精讀幫你篩選靠譜的內(nèi)容。 1 引言 React Hooks 是 React 16.7.0-alpha 版本推出的新特性,想嘗試的同學(xué)安裝此版本即可。 React Hooks 要解決的問題是狀態(tài)共享,...
摘要:課程制作和案例制作都經(jīng)過精心編排。對于開發(fā)者意義重大,希望對有需要的開發(fā)者有所幫助。是從提案轉(zhuǎn)為正式加入的新特性。并不需要用繼承,而是推薦用嵌套。大型項目中模塊化與功能解耦困難。從而更加易于復(fù)用和獨立測試。但使用會減少這種幾率。 showImg(https://segmentfault.com/img/bVbpNRZ?w=1920&h=1080); 講師簡介 曾任職中軟軍隊事業(yè)部,參與...
閱讀 2152·2023-04-26 00:23
閱讀 826·2021-09-08 09:45
閱讀 2446·2019-08-28 18:20
閱讀 2553·2019-08-26 13:51
閱讀 1606·2019-08-26 10:32
閱讀 1405·2019-08-26 10:24
閱讀 2042·2019-08-26 10:23
閱讀 2208·2019-08-23 18:10