摘要:在線傳遞給的是而不是,返回值即是想要透傳的數(shù)據(jù)了。所以函數(shù)組件在每次渲染的時候如果有傳遞函數(shù)的話都會重渲染子組件。
在學(xué)會使用React Hooks之前,可以先看一下相關(guān)原理學(xué)習(xí)React Hooks
前言在 React 的世界中,有容器組件和 UI 組件之分,在 React Hooks 出現(xiàn)之前,UI 組件我們可以使用函數(shù),無狀態(tài)組件來展示 UI,而對于容器組件,函數(shù)組件就顯得無能為力,我們依賴于類組件來獲取數(shù)據(jù),處理數(shù)據(jù),并向下傳遞參數(shù)給 UI 組件進(jìn)行渲染。在我看來,使用 React Hooks 相比于從前的類組件有以下幾點(diǎn)好處:
代碼可讀性更強(qiáng),原本同一塊功能的代碼邏輯被拆分在了不同的生命周期函數(shù)中,容易使開發(fā)者不利于維護(hù)和迭代,通過 React Hooks 可以將功能代碼聚合,方便閱讀維護(hù)
組件樹層級變淺,在原本的代碼中,我們經(jīng)常使用 HOC/render props 等方式來復(fù)用組件的狀態(tài),增強(qiáng)功能等,無疑增加了組件樹層數(shù)及渲染,而在 React Hooks 中,這些功能都可以通過強(qiáng)大的自定義的 Hooks 來實(shí)現(xiàn)
React 在 v16.8 的版本中推出了 React Hooks 新特性,雖然社區(qū)還沒有最佳實(shí)踐如何基于 React Hooks 來打造復(fù)雜應(yīng)用(至少我還沒有),憑借著閱讀社區(qū)中大量的關(guān)于這方面的文章,下面我將通過十個案例來幫助你認(rèn)識理解并可以熟練運(yùn)用 React Hooks 大部分特性。
useState 保存組件狀態(tài)在類組件中,我們使用?this.state?來保存組件狀態(tài),并對其修改觸發(fā)組件重新渲染。比如下面這個簡單的計數(shù)器組件,很好詮釋了類組件如何運(yùn)行:在線 Demo
import React from "react"; class App extends React.Component { constructor(props) { super(props); this.state = { count: 0, name: "alife" }; } render() { const { count } = this.state; return (Count: {count}); } }
一個簡單的計數(shù)器組件就完成了,而在函數(shù)組件中,由于沒有 this 這個黑魔法,React 通過 useState 來幫我們保存組件的狀態(tài)。在線 Demo
import React, { useState } from "react"; function App() { const [obj, setObject] = useState({ count: 0, name: "alife" }); return (Count: {obj.count}); }
通過傳入 useState 參數(shù)后返回一個帶有默認(rèn)狀態(tài)和改變狀態(tài)函數(shù)的數(shù)組。通過傳入新狀態(tài)給函數(shù)來改變原本的狀態(tài)值。值得注意的是 useState 不幫助你處理狀態(tài),相較于 setState 非覆蓋式更新狀態(tài),useState 覆蓋式更新狀態(tài),需要開發(fā)者自己處理邏輯。(代碼如上)
似乎有個 useState 后,函數(shù)組件也可以擁有自己的狀態(tài)了,但僅僅是這樣完全不夠。
useEffect 處理副作用函數(shù)組件能保存狀態(tài),但是對于異步請求,副作用的操作還是無能為力,所以 React 提供了 useEffect 來幫助開發(fā)者處理函數(shù)組件的副作用,在介紹新 API 之前,我們先來看看類組件是怎么做的:在線 Demo
import React, { Component } from "react"; class App extends Component { state = { count: 1 }; componentDidMount() { const { count } = this.state; document.title = "componentDidMount" + count; this.timer = setInterval(() => { this.setState(({ count }) => ({ count: count + 1 })); }, 1000); } componentDidUpdate() { const { count } = this.state; document.title = "componentDidMount" + count; } componentWillUnmount() { document.title = "componentWillUnmount"; clearInterval(this.timer); } render() { const { count } = this.state; return (Count:{count}); } }
在例子中,組件每隔一秒更新組件狀態(tài),并且每次觸發(fā)更新都會觸發(fā) document.title 的更新(副作用),而在組件卸載時修改 document.title(類似于清除)
從例子中可以看到,一些重復(fù)的功能開發(fā)者需要在 componentDidMount 和 componentDidUpdate 重復(fù)編寫,而如果使用 useEffect 則完全不一樣。在線 Demo
import React, { useState, useEffect } from "react"; let timer = null; function App() { const [count, setCount] = useState(0); useEffect(() => { document.title = "componentDidMount" + count; },[count]); useEffect(() => { timer = setInterval(() => { setCount(prevCount => prevCount + 1); }, 1000); // 一定注意下這個順序: // 告訴react在下次重新渲染組件之后,同時是下次執(zhí)行上面setInterval之前調(diào)用 return () => { document.title = "componentWillUnmount"; clearInterval(timer); }; }, []); return (Count: {count}); }
我們使用 useEffect 重寫了上面的例子,useEffect 第一個參數(shù)接收一個函數(shù),可以用來做一些副作用比如異步請求,修改外部參數(shù)等行為,而第二個參數(shù)稱之為dependencies,是一個數(shù)組,如果數(shù)組中的值變化才會觸發(fā) 執(zhí)行useEffect 第一個參數(shù)中的函數(shù)。返回值(如果有)則在組件銷毀或者調(diào)用函數(shù)前調(diào)用。
1.比如第一個 useEffect 中,理解起來就是一旦 count 值發(fā)生改變,則修改 documen.title 值;
2.而第二個 useEffect 中傳遞了一個空數(shù)組[],這種情況下只有在組件初始化或銷毀的時候才會觸發(fā),用來代替 componentDidMount 和 componentWillUnmount,慎用;
還有另外一個情況,就是不傳遞第二個參數(shù),也就是useEffect只接收了第一個函數(shù)參數(shù),代表不監(jiān)聽任何參數(shù)變化。每次渲染DOM之后,都會執(zhí)行useEffect中的函數(shù)。
基于這個強(qiáng)大 Hooks,我們可以模擬封裝出其他生命周期函數(shù),比如 componentDidUpdate 代碼十分簡單
function useUpdate(fn) { // useRef 創(chuàng)建一個引用 const mounting = useRef(true); useEffect(() => { if (mounting.current) { mounting.current = false; } else { fn(); } }); }
現(xiàn)在我們有了 useState 管理狀態(tài),useEffect 處理副作用,異步邏輯,學(xué)會這兩招足以應(yīng)對大部分類組件的使用場景。
useContext 減少組件層級上面介紹了 useState、useEffect 這兩個最基本的 API,接下來介紹的 useContext 是 React 幫你封裝好的,用來處理多層級傳遞數(shù)據(jù)的方式,在以前組件樹種,跨層級祖先組件想要給孫子組件傳遞數(shù)據(jù)的時候,除了一層層 props 往下透傳之外,我們還可以使用 React Context API 來幫我們做這件事,舉個簡單的例子:在線 Demo
const { Provider, Consumer } = React.createContext(null); function Bar() { return{color => ; } function Foo() { return{color}}; } function App() { return ( ); }
通過 React createContext 的語法,在 APP 組件中可以跨過 Foo 組件給 Bar 傳遞數(shù)據(jù)。而在 React Hooks 中,我們可以使用 useContext 進(jìn)行改造。在線 Demo
const colorContext = React.createContext("gray"); function Bar() { const color = useContext(colorContext); return{color}; } function Foo() { return; } function App() { return ( ); }
傳遞給 useContext 的是 context 而不是 consumer,返回值即是想要透傳的數(shù)據(jù)了。用法很簡單,使用 useContext 可以解決 Consumer 多狀態(tài)嵌套的問題。參考例子
function HeaderBar() { return ({user => ); }{notifications => Welcome back, {user.name}! You have {notifications.length} notifications. } }
而使用 useContext 則變得十分簡潔,可讀性更強(qiáng)且不會增加組件樹深度。
function HeaderBar() { const user = useContext(CurrentUser); const notifications = useContext(Notifications); return (useReducerWelcome back, {user.name}! You have {notifications.length} notifications. ); }
useReducer 這個 Hooks 在使用上幾乎跟 Redux/React-Redux 一模一樣,唯一缺少的就是無法使用 redux 提供的中間件。我們將上述的計時器組件改寫為 useReducer,在線 Demo
import React, { useReducer } from "react"; const initialState = { count: 0 }; function reducer(state, action) { switch (action.type) { case "increment": return { count: state.count + action.payload }; case "decrement": return { count: state.count - action.payload }; default: throw new Error(); } } function App() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} > ); }
用法跟 Redux 基本上是一致的,用法也很簡單,算是提供一個 mini 的 Redux 版本。
useCallback 記憶函數(shù)在類組件中,我們經(jīng)常犯下面這樣的錯誤:
class App { render() { return; } }{ console.log("do something"); }} />
這樣寫有什么壞處呢?一旦 App 組件的 props 或者狀態(tài)改變了就會觸發(fā)重渲染,即使跟 SomeComponent 組件不相關(guān),由于每次 render 都會產(chǎn)生新的 style 和 doSomething(因為重新render前后, style 和 doSomething分別指向了不同的引用),所以會導(dǎo)致 SomeComponent 重新渲染,倘若 SomeComponent 是一個大型的組件樹,這樣的 Virtual Dom 的比較顯然是很浪費(fèi)的,解決的辦法也很簡單,將參數(shù)抽離成變量。
const fontSizeStyle = { fontSize: 14 }; class App { doSomething = () => { console.log("do something"); } render() { return; } }
在類組件中,我們還可以通過 this 這個對象來存儲函數(shù),而在函數(shù)組件中沒辦法進(jìn)行掛載了。所以函數(shù)組件在每次渲染的時候如果有傳遞函數(shù)的話都會重渲染子組件。
function App() { const handleClick = () => { console.log("Click happened"); } returnClick Me ; }
這里多說一句,一版把函數(shù)式組件理解為class組件render函數(shù)的語法糖,所以每次重新渲染的時候,函數(shù)式組件內(nèi)部所有的代碼都會重新執(zhí)行一遍。所以上述代碼中每次render,handleClick都會是一個新的引用,所以也就是說傳遞給SomeComponent組件的props.onClick一直在變(因為每次都是一個新的引用),所以才會說這種情況下,函數(shù)組件在每次渲染的時候如果有傳遞函數(shù)的話都會重渲染子組件。
而有了 useCallback 就不一樣了,你可以通過 useCallback 獲得一個記憶后的函數(shù)。
function App() { const memoizedHandleClick = useCallback(() => { console.log("Click happened") }, []); // 空數(shù)組代表無論什么情況下該函數(shù)都不會發(fā)生改變 returnClick Me ; }
老規(guī)矩,第二個參數(shù)傳入一個數(shù)組,數(shù)組中的每一項一旦值或者引用發(fā)生改變,useCallback 就會重新返回一個新的記憶函數(shù)提供給后面進(jìn)行渲染。
這樣只要子組件繼承了 PureComponent 或者使用 React.memo 就可以有效避免不必要的 VDOM 渲染。
useMemo 記憶組件useCallback 的功能完全可以由 useMemo 所取代,如果你想通過使用 useMemo 返回一個記憶函數(shù)也是完全可以的。
useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs).
所以前面使用 useCallback 的例子可以使用 useMemo 進(jìn)行改寫:
function App() { const memoizedHandleClick = useMemo(() => () => { console.log("Click happened") }, []); // 空數(shù)組代表無論什么情況下該函數(shù)都不會發(fā)生改變 returnClick Me ; }
唯一的區(qū)別是:**useCallback 不會執(zhí)行第一個參數(shù)函數(shù),而是將它返回給你,而 useMemo 會執(zhí)行第一個函數(shù)并且將函數(shù)執(zhí)行結(jié)果返回給你。**所以在前面的例子中,可以返回 handleClick 來達(dá)到存儲函數(shù)的目的。
所以 useCallback 常用記憶事件函數(shù),生成記憶后的事件函數(shù)并傳遞給子組件使用。而 useMemo 更適合經(jīng)過函數(shù)計算得到一個確定的值,比如記憶組件。
function Parent({ a, b }) { // Only re-rendered if `a` changes: const child1 = useMemo(() =>, [a]); // Only re-rendered if `b` changes: const child2 = useMemo(() => , [b]); return ( <> {child1} {child2} > ) }
當(dāng) a/b 改變時,child1/child2 才會重新渲染。從例子可以看出來,只有在第二個參數(shù)數(shù)組的值發(fā)生變化時,才會觸發(fā)子組件的更新。
useRef 保存引用值useRef 跟 createRef 類似,都可以用來生成對 DOM 對象的引用,看個簡單的例子:在線 Demo
import React, { useState, useRef } from "react"; function App() { let [name, setName] = useState("Nate"); let nameRef = useRef(); const submitButton = () => { setName(nameRef.current.value); }; return (); }{name}
useRef 返回的值傳遞給組件或者 DOM 的 ref 屬性,就可以通過 ref.current 值訪問組件或真實(shí)的 DOM 節(jié)點(diǎn),重點(diǎn)是組件也是可以訪問到的,從而可以對 DOM 進(jìn)行一些操作,比如監(jiān)聽事件等等。
當(dāng)然 useRef 遠(yuǎn)比你想象中的功能更加強(qiáng)大,useRef 的功能有點(diǎn)像類屬性,或者說您想要在組件中記錄一些值,并且這些值在稍后可以更改。
利用 useRef 就可以繞過 Capture Value 的特性。可以認(rèn)為 ref 在所有 Render 過程中保持著唯一引用,因此所有對 ref 的賦值或取值,拿到的都只有一個最終狀態(tài),而不會在每個 Render 間存在隔離。參考例子:精讀《Function VS Class 組件》
React Hooks 中存在 Capture Value 的特性:在線 Demo
function App() { const [count, setCount] = useState(0); useEffect(() => { setTimeout(() => { alert("count: " + count); }, 3000); }, [count]); return (); }You clicked {count} times
先點(diǎn)擊增加button,后點(diǎn)擊減少button,3秒后先alert 1,后alert 0,而不是alert兩次0。這就是所謂的 capture value 的特性。而在類組件中 3 秒后輸出的就是修改后的值,因為這時候** message 是掛載在 this 變量上,它保留的是一個引用值**,對 this 屬性的訪問都會獲取到最新的值,類組件舉例,在線Demo。講到這里你應(yīng)該就明白了,useRef 創(chuàng)建一個引用,就可以有效規(guī)避 React Hooks 中 Capture Value 特性。useRef避免 Capture Value 在線Demo
function App() { const count = useRef(0); const showCount = () => { alert("count: " + count.current); }; const handleClick = number => { count.current = count.current + number; setTimeout(showCount, 3000); }; return (); }You clicked {count.current} times
只要將賦值與取值的對象變成 useRef,而不是 useState,就可以躲過 capture value 特性,在 3 秒后得到最新的值。
useImperativeHandle 透傳 Ref通過 useImperativeHandle 用于讓父組件獲取子組件內(nèi)的索引?在線 Demo
import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react"; function ChildInputComponent(props, ref) { const inputRef = useRef(null); useImperativeHandle(ref, () => inputRef.current); return ; } const ChildInput = forwardRef(ChildInputComponent); function App() { const inputRef = useRef(null); useEffect(() => { inputRef.current.focus(); }, []); return (); }
通過這種方式,App 組件可以獲得子組件的 input 的 DOM 節(jié)點(diǎn)。
useLayoutEffect 同步執(zhí)行副作用大部分情況下,使用 useEffect 就可以幫我們處理組件的副作用,但是如果想要同步調(diào)用一些副作用,比如對 DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用會在 DOM 更新之后同步執(zhí)行。在線 Demo
function App() { const [width, setWidth] = useState(0); useLayoutEffect(() => { const title = document.querySelector("#title"); const titleWidth = title.getBoundingClientRect().width; console.log("useLayoutEffect"); if (width !== titleWidth) { setWidth(titleWidth); } }); useEffect(() => { console.log("useEffect"); }); return (); }hello
{width}
在上面的例子中,useLayoutEffect 會在 render,DOM 更新之后同步觸發(fā)函數(shù),會優(yōu)于 useEffect 異步觸發(fā)函數(shù)。
useEffect和useLayoutEffect有什么區(qū)別?簡單來說就是調(diào)用時機(jī)不同,useLayoutEffect和原來componentDidMount&componentDidUpdate一致,在react完成DOM更新后馬上同步調(diào)用的代碼,會阻塞頁面渲染。而useEffect是會在整個頁面渲染完才會調(diào)用的代碼。
官方建議優(yōu)先使用useEffect
However,?we recommend starting with useEffect first?and only trying useLayoutEffect if that causes a problem.
在實(shí)際使用時如果想避免頁面抖動(在useEffect里修改DOM很有可能出現(xiàn))的話,可以把需要操作DOM的代碼放在useLayoutEffect里。關(guān)于使用useEffect導(dǎo)致頁面抖動,參考git倉庫git倉庫示例
不過useLayoutEffect在服務(wù)端渲染時會出現(xiàn)一個warning,要消除的話得用useEffect代替或者推遲渲染時機(jī)。見說明和討論。
React Hooks 不足盡管我們通過上面的例子看到 React Hooks 的強(qiáng)大之處,似乎類組件完全都可以使用 React Hooks 重寫。但是當(dāng)下 v16.8 的版本中,還無法實(shí)現(xiàn) getSnapshotBeforeUpdate 和 componentDidCatch 這兩個在類組件中的生命周期函數(shù)。官方也計劃在不久的將來在 React Hooks 進(jìn)行實(shí)現(xiàn)。
原文地址
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/6994.html
摘要:課程制作和案例制作都經(jīng)過精心編排。對于開發(fā)者意義重大,希望對有需要的開發(fā)者有所幫助。是從提案轉(zhuǎn)為正式加入的新特性。并不需要用繼承,而是推薦用嵌套。大型項目中模塊化與功能解耦困難。從而更加易于復(fù)用和獨(dú)立測試。但使用會減少這種幾率。 showImg(https://segmentfault.com/img/bVbpNRZ?w=1920&h=1080); 講師簡介 曾任職中軟軍隊事業(yè)部,參與...
摘要:經(jīng)典案例此例子中是最新的語法其中是他的值是用來設(shè)置值的函數(shù)是初始值該初始值可以接受任何參數(shù)但是記得當(dāng)他接受為一個函數(shù)時就變成了延遲初始化該函數(shù)返回值即為這兩種初始化方式是相等的但是在函數(shù)為初始值時會被執(zhí)行一次這里只會在初始化的時候執(zhí)行中的 useState 經(jīng)典案例: import { useState } from react; function Example() { con...
摘要:最近官方在大會上宣布內(nèi)測將引入。所以我們有必要了解,以及由此引發(fā)的疑問。在進(jìn)一步了解之前,我們需要先快速的了解一些基本的的用法。如何解決狀態(tài)有關(guān)的邏輯的重用和共享問題。 showImg(https://segmentfault.com/img/remote/1460000016886798?w=1500&h=750); 大家好,我是谷阿莫,今天要將的是一個...,哈哈哈,看到這個題我就...
摘要:本文是學(xué)習(xí)了年新鮮出爐的提案之后,針對異步請求數(shù)據(jù)寫的一個案例。注意,本文假設(shè)了你已經(jīng)初步了解的含義了,如果不了解還請移步官方文檔。但不要忘記和上下文對象可以看做是寫法的以及三個鉤子函數(shù)的組合。 本文是學(xué)習(xí)了2018年新鮮出爐的React Hooks提案之后,針對異步請求數(shù)據(jù)寫的一個案例。注意,本文假設(shè)了:1.你已經(jīng)初步了解hooks的含義了,如果不了解還請移步官方文檔。(其實(shí)有過翻譯...
閱讀 2283·2021-10-09 09:41
閱讀 1746·2019-08-30 15:53
閱讀 989·2019-08-30 15:52
閱讀 3444·2019-08-30 11:26
閱讀 768·2019-08-29 16:09
閱讀 3422·2019-08-29 13:25
閱讀 2260·2019-08-26 16:45
閱讀 1932·2019-08-26 11:51