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