<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React Hooks 設(shè)計動機與工作模式

          共 21104字,需瀏覽 43分鐘

           ·

          2021-05-29 15:31

          React-Hooks 設(shè)計動機初探

          何謂類組件(Class Component)

          所謂類組件,就是基于 ES6 Class 這種寫法,通過繼承 React.Component 得來的 React 組件。以下是一個典型的類組件:

          class DemoClass extends React.Component {
           
            // 初始化類組件的 state
            state = {
              text""
            };
            // 編寫生命周期方法 didMount
            componentDidMount() {
              // 省略業(yè)務(wù)邏輯
            }
            // 編寫自定義的實例方法
            changeText = (newText) => {
              // 更新 state
              this.setState({
                text: newText
              });
            };
            // 編寫生命周期方法 render
            render() {
              return (
                <div className="demoClass">
                  <p>{this.state.text}</p>
                  <button onClick={this.changeText}>點我修改</button>
                </div>

              );
            }
          }

          何謂函數(shù)組件/無狀態(tài)組件(Function Component/Stateless Component)

          函數(shù)組件顧名思義,就是以函數(shù)的形態(tài)存在的 React 組件。早期并沒有 React-Hooks 的加持,函數(shù)組件內(nèi)部無法定義和維護 state,因此它還有一個別名叫“無狀態(tài)組件”。以下是一個典型的函數(shù)組件:

          function DemoFunction(props{
            const { text } = props
            return (
              <div className="demoFunction">
                <p>{`function 組件所接收到的來自外界的文本內(nèi)容是:[${text}]`}</p>
              </div>

            );
          }

          函數(shù)組件與類組件的對比:無關(guān)“優(yōu)劣”,只談“不同”

          我們先基于上面的兩個 Demo,從形態(tài)上對兩種組件做區(qū)分。它們之間肉眼可見的區(qū)別就包括但不限于:

          • 類組件需要繼承 class,函數(shù)組件不需要;
          • 類組件可以訪問生命周期方法,函數(shù)組件不能;
          • 類組件中可以獲取到實例化后的 this,并基于這個 this 做各種各樣的事情,而函數(shù)組件不可以;
          • 類組件中可以定義并維護 state(狀態(tài)),而函數(shù)組件不可以;

          單就我們列出的這幾點里面,頻繁出現(xiàn)了“類組件可以 xxx,函數(shù)組件不可以 xxx”,這是否就意味著類組件比函數(shù)組件更好呢?

          答案當然是否定的。你可以說,在 React-Hooks 出現(xiàn)之前的世界里,類組件的能力邊界明顯強于函數(shù)組件,但要進一步推導(dǎo)“類組件強于函數(shù)組件”,未免顯得有些牽強。同理,一些文章中一味鼓吹函數(shù)組件輕量優(yōu)雅上手迅速,不久的將來一定會把類組件干沒(類組件:我做錯了什么?)之類的,更是不可偏聽偏信。

          當我們討論這兩種組件形式時,不應(yīng)懷揣“孰優(yōu)孰劣”這樣的成見,而應(yīng)該更多地去關(guān)注兩者的不同,進而把不同的特性與不同的場景做連接,這樣才能求得一個全面的、辯證的認知。

          重新理解類組件:包裹在面向?qū)ο笏枷胂碌摹爸匮b戰(zhàn)艦”

          類組件是面向?qū)ο缶幊趟枷氲囊环N表征。面向?qū)ο笫且粋€老生常談的概念了,當我們應(yīng)用面向?qū)ο蟮臅r候,總是會有意或無意地做這樣兩件事情。

          • 封裝:將一類屬性和方法,“聚攏”到一個 Class 里去。
          • 繼承:新的 Class 可以通過繼承現(xiàn)有 Class,實現(xiàn)對某一類屬性和方法的復(fù)用。

          React 類組件也不例外。我們再次審視一下這個典型的類組件 Case:

          class DemoClass extends React.Component {
           
            // 初始化類組件的 state
            state = {
              text""
            };
            // 編寫生命周期方法 didMount
            componentDidMount() {
              // 省略業(yè)務(wù)邏輯
            }
            // 編寫自定義的實例方法
            changeText = (newText) => {
              // 更新 state
              this.setState({
                text: newText
              });
            };
            // 編寫生命周期方法 render
            render() {
              return (
                <div className="demoClass">
                  <p>{this.state.text}</p>
                  <button onClick={this.changeText}>點我修改</button>
                </div>

              );
            }
          }

          不難看出,React 類組件內(nèi)部預(yù)置了相當多的“現(xiàn)成的東西”等著你去調(diào)度/定制,state 和生命周期就是這些“現(xiàn)成東西”中的典型。要想得到這些東西,難度也不大,你只需要輕輕地繼承一個 React.Component 即可。

          這種感覺就好像是你不費吹灰之力,就擁有了一輛“重裝戰(zhàn)艦”,該有的槍炮導(dǎo)彈早已配備整齊,就等你操縱控制臺上的一堆開關(guān)了。

          毋庸置疑,類組件給到開發(fā)者的東西是足夠多的,但“多”就是“好”嗎?其實未必。

          把一個人塞進重裝戰(zhàn)艦里,他就一定能操縱這臺戰(zhàn)艦嗎?如果他沒有經(jīng)過嚴格的訓(xùn)練,不清楚每一個操作點的內(nèi)涵,那他極有可能會把炮彈打到友軍的營地里去。

          React 類組件,也有同樣的問題——它提供了多少東西,你就需要學(xué)多少東西。假如背不住生命周期,你的組件邏輯順序大概率會變成一團糟?!按蠖钡谋澈?,是不可忽視的學(xué)習(xí)成本。

          再想這樣一個場景:假如我現(xiàn)在只是需要打死一只蚊子,而不是打掉一個軍隊。這時候繼續(xù)開動重裝戰(zhàn)艦,是不是正應(yīng)了那句老話——“可以,但沒有必要”。這也是類組件的一個不便,它太重了,對于解決許多問題來說,編寫一個類組件實在是一個過于復(fù)雜的姿勢。復(fù)雜的姿勢必然帶來高昂的理解成本,這也是我們所不想看到的。

          更要命的是,由于開發(fā)者編寫的邏輯在封裝后是和組件粘在一起的,這就使得類**組件內(nèi)部的邏輯難以實現(xiàn)拆分和復(fù)用。**如果你想要打破這個僵局,則需要進一步學(xué)習(xí)更加復(fù)雜的設(shè)計模式(比如高階組件、Render Props 等),用更高的學(xué)習(xí)成本來交換一點點編碼的靈活度。

          這一切的一切,光是想想就讓人頭禿。所以說,類組件固然強大, 但它絕非萬能。

          深入理解函數(shù)組件:呼應(yīng) React 設(shè)計思想的“輕巧快艇”

          我們再來看這個函數(shù)組件的 case:

          function DemoFunction(props{
            const { text } = props
            return (
              <div className="demoFunction">
                <p>{`function 組件所接收到的來自外界的文本內(nèi)容是:[${text}]`}</p>
              </div>

            );
          }

          當然啦,要是你以為函數(shù)組件的簡單是因為它只能承擔(dān)渲染這一種任務(wù),那可就太小瞧它了。它同樣能夠承接相對復(fù)雜的交互邏輯,像這樣:

          function DemoFunction(props{
            const { text } = props 

            const showAlert = ()=> {
              alert(`我接收到的文本是${text}`)
            } 

            return (
              <div className="demoFunction">
                <p>{`function 組件所接收到的來自外界的文本內(nèi)容是:[${text}]`}</p>
                <button onClick={showAlert}>點擊彈窗</button>
              </div>

            );
          }

          相比于類組件,函數(shù)組件肉眼可見的特質(zhì)自然包括輕量、靈活、易于組織和維護、較低的學(xué)習(xí)成本等。

          類組件和函數(shù)組件之間,縱有千差萬別,但最不能夠被我們忽視掉的,是心智模式層面的差異,是面向?qū)ο蠛秃瘮?shù)式編程這兩套不同的設(shè)計思想之間的差異。

          說得更具體一點,函數(shù)組件更加契合 React 框架的設(shè)計理念。何出此言?不要忘了這個赫赫有名的 React 公式:

          不夸張地說,React 組件本身的定位就是函數(shù),一個吃進數(shù)據(jù)、吐出 UI 的函數(shù)。作為開發(fā)者,我們編寫的是聲明式的代碼,而 React 框架的主要工作,就是及時地把聲明式的代碼轉(zhuǎn)換為命令式的 DOM 操作,把數(shù)據(jù)層面的描述映射到用戶可見的 UI 變化中去。這就意味著從原則上來講,React 的數(shù)據(jù)應(yīng)該總是緊緊地和渲染綁定在一起的,而類組件做不到這一點。

          首先我們來看這樣一個類組件:

          class ProfilePage extends React.Component {
            showMessage = () => {
              alert('Followed ' + this.props.user);
            };
            handleClick = () => {
              setTimeout(this.showMessage, 3000);
            };
            render() {
              return <button onClick={this.handleClick}>Follow</button>;
            }
          }

          這個組件返回的是一個按鈕,交互內(nèi)容也很簡單:點擊按鈕后,過 3s,界面上會彈出“Followed xxx”的文案。類似于我們在微博上點擊“關(guān)注某人”之后彈出的“已關(guān)注”這樣的提醒。

          看起來好像沒啥毛病,但是如果你在這個在線 Demo中嘗試點擊基于類組件形式編寫的 ProfilePage 按鈕后 3s 內(nèi)把用戶切換為 Sophie,你就會看到如下圖所示的效果:

          明明我們是在 Dan 的主頁點擊的關(guān)注,結(jié)果卻提示了“Followed Sophie”!

          這個現(xiàn)象必然讓許多人感到困惑:user 的內(nèi)容是通過 props 下發(fā)的,props 作為不可變值,為什么會從 Dan 變成 Sophie 呢?

          因為雖然 props 本身是不可變的,但 this 卻是可變的,this 上的數(shù)據(jù)是可以被修改的,this.props 的調(diào)用每次都會獲取最新的 props,而這正是 React 確保數(shù)據(jù)實時性的一個重要手段。

          多數(shù)情況下,在 React 生命周期對執(zhí)行順序的調(diào)控下,this.props 和 this.state 的變化都能夠和預(yù)期中的渲染動作保持一致。但在這個案例中,我們通過 setTimeout 將預(yù)期中的渲染推遲了 3s,打破了 this.props 和渲染動作之間的這種時機上的關(guān)聯(lián),進而導(dǎo)致渲染時捕獲到的是一個錯誤的、修改后的 this.props。這就是問題的所在。

          但如果我們把 ProfilePage 改造為一個像這樣的函數(shù)組件:

          function ProfilePage(props{
            const showMessage = () => {
              alert('Followed ' + props.user);
            };
            const handleClick = () => {
              setTimeout(showMessage, 3000);
            };
            return (
              <button onClick={handleClick}>Follow</button>
            );
          }

          事情就會大不一樣。

          props 會在 ProfilePage 函數(shù)執(zhí)行的一瞬間就被捕獲,而 props 本身又是一個不可變值,因此我們可以充分確保從現(xiàn)在開始,在任何時機下讀取到的 props,都是最初捕獲到的那個 props。當父組件傳入新的 props 來嘗試重新渲染 ProfilePage 時,本質(zhì)上是基于新的 props 入?yún)l(fā)起了一次全新的函數(shù)調(diào)用,并不會影響上一次調(diào)用對上一個 props 的捕獲。這樣一來,我們便確保了渲染結(jié)果確實能夠符合預(yù)期。

          如果你認真閱讀了我前面說過的那些話,相信你現(xiàn)在一定也**不僅僅能夠充分理解 Dan 所想要表達的“函數(shù)組件會捕獲 render 內(nèi)部的狀態(tài)”**這個結(jié)論,而是能夠更進一步地意識到這樣一件事情:函數(shù)組件真正地把數(shù)據(jù)和渲染綁定到了一起。

          經(jīng)過歲月的洗禮,React 團隊顯然也認識到了,函數(shù)組件是一個更加匹配其設(shè)計理念、也更有利于邏輯拆分與重用的組件表達形式,接下來便開始“用腳投票”,用實際行動支持開發(fā)者編寫函數(shù)式組件。于是,React-Hooks 便應(yīng)運而生。

          Hooks 的本質(zhì):一套能夠使函數(shù)組件更強大、更靈活的“鉤子”

          React-Hooks 是什么?它是一套能夠使函數(shù)組件更強大、更靈活的“鉤子”。

          前面我們已經(jīng)說過,函數(shù)組件比起類組件“少”了很多東西,比如生命周期、對 state 的管理等。這就給函數(shù)組件的使用帶來了非常多的局限性,導(dǎo)致我們并不能使用函數(shù)這種形式,寫出一個真正的全功能的組件。

          React-Hooks 的出現(xiàn),就是為了幫助函數(shù)組件補齊這些(相對于類組件來說)缺失的能力。

          如果說函數(shù)組件是一臺輕巧的快艇,那么 React-Hooks 就是一個內(nèi)容豐富的零部件箱?!爸匮b戰(zhàn)艦”所預(yù)置的那些設(shè)備,這個箱子里基本全都有,同時它還不強制你全都要,而是允許你自由地選擇和使用你需要的那些能力,然后將這些能力以 Hook(鉤子)的形式“鉤”進你的組件里,從而定制出一個最適合你的“專屬戰(zhàn)艦”。

          從核心 API 看 Hooks 的基本形態(tài)

          useState():為函數(shù)組件引入狀態(tài)

          早期的函數(shù)組件相比于類組件,其一大劣勢是缺乏定義和維護 state 的能力,而 state(狀態(tài))作為 React 組件的靈魂,必然是不可省略的。因此 React-Hooks 在誕生之初,就優(yōu)先考慮了對 state 的支持。useState 正是這樣一個能夠為函數(shù)組件引入狀態(tài)的 API。

          函數(shù)組件,真的很輕

          在過去,你可能會為了使用 state,不得不去編寫一個類組件(這里我給出一個 Demo,編碼如下所示):

          import React, { Component } from "react";
          export default class TextButton extends Component {

            constructor() {
              super();
              this.state = {
                text"初始文本"
              };
            }

            changeText = () => {
              this.setState(() => {
                return {
                  text"修改后的文本"
                };
              });
            };
            render() {
              const { text } = this.state;
              return (
                <div className="textButton">
                  <p>{text}</p>
                  <button onClick={this.changeText}>點擊修改文本</button>
                </div>

              );
            }
          }

          有了 useState 后,我們就可以直接在函數(shù)組件里引入 state。以下是使用 useState 改造過后的 TextButton 組件:

          import React, { useState } from "react";
          export default function Button({
            const [text, setText] = useState("初始文本");
            function changeText({
              return setText("修改后的文本");
            }
            return (
              <div className="textButton">
                <p>{text}</p>
                <button onClick={changeText}>點擊修改文本</button>
              </div>

            );
          }

          useState 快速上手

          從用法上看,useState 返回的是一個數(shù)組,數(shù)組的第一個元素對應(yīng)的是我們想要的那個 state 變量,第二個元素對應(yīng)的是能夠修改這個變量的 API。我們可以通過數(shù)組解構(gòu)的語法,將這兩個元素取出來,并且按照我們自己的想法命名。一個典型的調(diào)用示例如下:

          const [state, setState] = useState(initialState);

          在這個示例中,我們給自己期望的那個狀態(tài)變量命名為 state,給修改 state 的 API 命名為 setState。useState 中傳入的 initialState 正是 state 的初始值。后續(xù)我們可以通過調(diào)用 setState,來修改 state 的值,像這樣:

          setState(newState)

          狀態(tài)更新后會觸發(fā)渲染層面的更新,這點和類組件是一致的。

          這里需要向初學(xué)者強調(diào)的一點是:狀態(tài)和修改狀態(tài)的 API 名都是可以自定義的。比如在上文的 Demo 中,就分別將其自定義為 text 和 setText:

          const [text, setText] = useState("初始文本");

          “set + 具體變量名”這種命名形式,可以幫助我們快速地將 API 和它對應(yīng)的狀態(tài)建立邏輯聯(lián)系。

          當我們在函數(shù)組件中調(diào)用 React.useState 的時候,實際上是給這個組件關(guān)聯(lián)了一個狀態(tài)——注意,是“一個狀態(tài)”而不是“一批狀態(tài)”。這一點是相對于類組件中的 state 來說的。在類組件中,我們定義的 state 通常是一個這樣的對象,如下所示:

          this.state {
            text"初始文本",
            length10000,
            author: ["xiuyan""cuicui""yisi"]
          }

          這個對象是“包容萬物”的:整個組件的狀態(tài)都在 state 對象內(nèi)部做收斂,當我們需要某個具體狀態(tài)的時候,會通過 this.state.xxx 這樣的訪問對象屬性的形式來讀取它。

          而在 useState 這個鉤子的使用背景下,state 就是單獨的一個狀態(tài),它可以是任何你需要的 JS 類型。像這樣:

          // 定義為數(shù)組
          const [author, setAuthor] = useState(["xiuyan""cuicui""yisi"]);
           
          // 定義為數(shù)值
          const [length, setLength] = useState(100);
          // 定義為字符串
          const [text, setText] = useState("初始文本")

          你還可以定義為布爾值、對象等,都是沒問題的。它就像類組件中 state 對象的某一個屬性一樣,對應(yīng)著一個單獨的狀態(tài),允許你存儲任意類型的值

          useEffect():允許函數(shù)組件執(zhí)行副作用操作

          函數(shù)組件相比于類組件來說,最顯著的差異就是 state 和生命周期的缺失。useState 為函數(shù)組件引入了 state,而 useEffect 則在一定程度上彌補了生命周期的缺席。

          useEffect 能夠為函數(shù)組件引入副作用。過去我們習(xí)慣放在 componentDidMount、componentDidUpdate  componentWillUnmount 三個生命周期里來做的事,現(xiàn)在可以放在 useEffect 里來做,比如操作 DOM、訂閱事件、調(diào)用外部 API 獲取數(shù)據(jù)等。

          useEffect 和生命周期函數(shù)之間的“替換”關(guān)系

          我們可以通過下面這個例子來理解 useEffect 和生命周期函數(shù)之間的替換關(guān)系。這里我先給到你一個用 useEffect 編寫的函數(shù)組件示例:

          // 注意 hook 在使用之前需要引入
          import React, { useState, useEffect } from 'react';
          // 定義函數(shù)組件
          function IncreasingTodoList({
            // 創(chuàng)建 count 狀態(tài)及其對應(yīng)的狀態(tài)修改函數(shù)
            const [count, setCount] = useState(0);
            // 此處的定位與 componentDidMount 和 componentDidUpdate 相似
            useEffect(() => {
              // 每次 count 增加時,都增加對應(yīng)的待辦項
              const todoList = document.getElementById("todoList");
              const newItem = document.createElement("li");
              newItem.innerHTML = `我是第${count}個待辦項`;
              todoList.append(newItem);
            });
            // 編寫 UI 邏輯
            return (
              <div>
                <p>當前共計 {count} 個todo Item</p>
                <ul id="todoList"></ul>
                <button onClick={() => setCount(count + 1)}>點我增加一個待辦項</button>
              </div>

            );
          }

          通過上面這段代碼構(gòu)造出來的界面在剛剛掛載完畢時,就是如下圖所示的樣子:

          IncreasingTodoList 是一個只允許增加 item 的 ToDoList(待辦事項列表)。按照 useEffect 的設(shè)定,每當我們點擊“點我增加一個待辦項”這個按鈕,驅(qū)動 count+1 的同時,DOM 結(jié)構(gòu)里也會被追加一個 li 元素。以下是連擊按鈕三次之后的效果圖:

          同樣的效果,按照注釋里的提示,我們也可以通過編寫 class 組件來實現(xiàn):

          import React from 'react';
          // 定義類組件
          class IncreasingTodoList extends React.Component {

            // 初始化 state
            state = { count0 }
            // 此處調(diào)用上個 demo 中 useEffect 中傳入的函數(shù)
            componentDidMount() {
              this.addTodoItem()
            }
            // 此處調(diào)用上個 demo 中 useEffect 中傳入的函數(shù)
            componentDidUpdate() {
              this.addTodoItem()
            }
            // 每次 count 增加時,都增加對應(yīng)的待辦項
            addTodoItem = () => {
              const { count } = this.state
              const todoList = document.getElementById("todoList")
              const newItem = document.createElement("li")
              newItem.innerHTML = `我是第${count}個待辦項`
              todoList.append(newItem)
            }

            // 定義渲染內(nèi)容
            render() {
              const { count } = this.state
              return (
                <div>
                  <p>當前共計 {count} 個todo Item</p>
                  <ul id="todoList"></ul>
                  <button
                    onClick={() =>

                      this.setState({
                        count: this.state.count + 1,
                      })
                    }
                  >
                    點我增加一個待辦項
                  </button>
                </div>

              )
            }
          }

          通過這樣一個對比,類組件生命周期和函數(shù)組件 useEffect 之間的轉(zhuǎn)換關(guān)系可以說是躍然紙上了。

          在這里,我提個醒:初學(xué) useEffect 時,我們難免習(xí)慣于借助對生命周期的理解來推導(dǎo)對 useEffect 的理解。但長期來看,若是執(zhí)著于這個學(xué)習(xí)路徑,無疑將阻礙你真正從心智模式的層面擁抱 React-Hooks。

          有時候,我們必須學(xué)會忘記舊的知識,才能夠更好地擁抱新的知識。對于每一個學(xué)習(xí) useEffect 的人來說,生命周期到 useEffect 之間的轉(zhuǎn)換關(guān)系都不是最重要的,最重要的是在腦海中構(gòu)建一個“組件有副作用 → 引入 useEffect”這樣的條件反射——當你真正拋卻類組件帶給你的刻板印象、擁抱函數(shù)式編程之后,想必你會更加認同“useEffect 是用于為函數(shù)組件引入副作用的鉤子”這個定義。

          useEffect 快速上手

          useEffect 可以接收兩個參數(shù),分別是回調(diào)函數(shù)與依賴數(shù)組,如下面代碼所示:

          useEffect(callBack, [])

          useEffect 用什么姿勢來調(diào)用,本質(zhì)上取決于你想用它來達成什么樣的效果。下面我們就以效果為線索,簡單介紹 useEffect 的調(diào)用規(guī)則。

          每一次渲染后都執(zhí)行的副作用:傳入回調(diào)函數(shù),不傳依賴數(shù)組。調(diào)用形式如下所示

          useEffect(callBack)

          僅在掛載階段執(zhí)行一次的副作用:傳入回調(diào)函數(shù),且這個函數(shù)的返回值不是一個函數(shù),同時傳入一個空數(shù)組。調(diào)用形式如下所示:

          useEffect(()=>{
            // 這里是業(yè)務(wù)邏輯 
          }, [])

          僅在掛載階段和卸載階段執(zhí)行的副作用:傳入回調(diào)函數(shù),且這個函數(shù)的返回值是一個函數(shù),同時傳入一個空數(shù)組。假如回調(diào)函數(shù)本身記為 A, 返回的函數(shù)記為 B,那么將在掛載階段執(zhí)行 A,卸載階段執(zhí)行 B。調(diào)用形式如下所示:

          useEffect(()=>{
            // 這里是 A 的業(yè)務(wù)邏輯

            // 返回一個函數(shù)記為 B
            return ()=>{
            }
          }, [])

          這里需要注意,這種調(diào)用方式之所以會在卸載階段去觸發(fā) B 函數(shù)的邏輯,是由 useEffect 的執(zhí)行規(guī)則決定的:useEffect 回調(diào)中返回的函數(shù)被稱為“清除函數(shù)”,當 React 識別到清除函數(shù)時,會在卸載時執(zhí)行清除函數(shù)內(nèi)部的邏輯。這個規(guī)律不會受第二個參數(shù)或者其他因素的影響,只要你在 useEffect 回調(diào)中返回了一個函數(shù),它就會被作為清除函數(shù)來處理。

          每一次渲染都觸發(fā),且卸載階段也會被觸發(fā)的副作用:傳入回調(diào)函數(shù),且這個函數(shù)的返回值是一個函數(shù),同時不傳第二個參數(shù)。如下所示:

          useEffect(()=>{
            // 這里是 A 的業(yè)務(wù)邏輯

            // 返回一個函數(shù)記為 B
            return ()=>{
            }
          })

          上面這段代碼就會使得 React 在每一次渲染都去觸發(fā) A 邏輯,并且在卸載階段去觸發(fā) B 邏輯。

          其實你只要記住,如果你有一段副作用邏輯需要在卸載階段執(zhí)行,那么把它寫進 useEffect 回調(diào)的返回函數(shù)(上面示例中的 B 函數(shù))里就行了。也可以認為,這個 B 函數(shù)的角色定位就類似于生命周期里 componentWillUnmount 方法里的邏輯

          根據(jù)一定的依賴條件來觸發(fā)的副作用:傳入回調(diào)函數(shù)(若返回值是一個函數(shù),仍然僅影響卸載階段對副作用的處理,此處不再贅述),同時傳入一個非空的數(shù)組,如下所示:

          useEffect(()=>{
            // 這是回調(diào)函數(shù)的業(yè)務(wù)邏輯 

            // 若 xxx 是一個函數(shù),則 xxx 會在組件卸載時被觸發(fā)
            return xxx
          }, [num1, num2, num3])

          這里我給出的一個示意數(shù)組是 [num1, num2, num3]。首先需要說明,數(shù)組中的變量一般都是來源于組件本身的數(shù)據(jù)(props 或者 state)。若數(shù)組不為空,那么 React 就會在新的一次渲染后去對比前后兩次的渲染,查看數(shù)組內(nèi)是否有變量發(fā)生了更新(只要有一個數(shù)組元素變了,就會被認為更新發(fā)生了),并在有更新的前提下去觸發(fā) useEffect 中定義的副作用邏輯。

          Hooks 是如何幫助我們升級工作模式的

          函數(shù)組件相比類組件來說,有著不少能夠利好 React 組件開發(fā)的特性,而 React-Hooks 的出現(xiàn)正是為了強化函數(shù)組件的能力?,F(xiàn)在,基于對 React-Hooks 編碼層面的具體認知,想必你對“動機”的理解也已經(jīng)上了一個臺階。這里我們就趁熱打鐵,針對“Why React-Hooks”這個問題,做一個加強版的總結(jié)。

          相信有不少嗅覺敏銳的同學(xué)已經(jīng)感覺到了——沒錯,這個環(huán)節(jié)就是手把手教你做“為什么需要 React-Hooks”這道面試題。以“Why xxx”開頭的這種面試題,往往都沒有標準答案,但會有一些關(guān)鍵的“點”,只要能答出關(guān)鍵的點,就足以證明你思考的方向是正確的,也就意味著這道題能給你加分。這里,我梳理了以下 4 條答題思路:

          • 告別難以理解的 Class;
          • 解決業(yè)務(wù)邏輯難以拆分的問題;
          • 使狀態(tài)邏輯復(fù)用變得簡單可行;
          • 函數(shù)組件從設(shè)計思想上來看,更加契合 React 的理念。

          關(guān)于思路 4,我在上個課時已經(jīng)講得透透的了,這里我主要是借著代碼的東風(fēng),把 1、2、3 攤開來給你看一下。

          1. 告別難以理解的 Class:把握 Class 的兩大“痛點”

          坊間總有傳言說 Class 是“難以理解”的,這個說法的背后是 this 和生命周期這兩個痛點。

          先來說說 this,在上個課時,你已經(jīng)初步感受了一把 this 有多么難以捉摸。但那畢竟是個相對特殊的場景,更為我們所熟悉的,可能還是 React 自定義組件方法中的 this??纯聪旅孢@段代碼:

          class Example extends Component {
            state = {
              name'test',
              age'99';
            };
            changeAge() {
              // 這里會報錯
              this.setState({
                age'100'
              });
            }
            render() {
              return <button onClick={this.changeAge}>{this.state.name}的年齡是{this.state.age}</button>
            }
          }

          你先不用關(guān)心組件具體的邏輯,就看 changeAge 這個方法:它是 button 按鈕的事件監(jiān)聽函數(shù)。當我點擊 button 按鈕時,希望它能夠幫我修改狀態(tài),但事實是,點擊發(fā)生后,程序會報錯。原因很簡單,changeAge 里并不能拿到組件實例的 this

          為了解決 this 不符合預(yù)期的問題,各路前端也是各顯神通,之前用 bind、現(xiàn)在推崇箭頭函數(shù)。但不管什么招數(shù),本質(zhì)上都是在用實踐層面的約束來解決設(shè)計層面的問題。好在現(xiàn)在有了 Hooks,一切都不一樣了,我們可以在函數(shù)組件里放飛自我(畢竟函數(shù)組件是不用關(guān)心 this 的)哈哈,解放啦

          至于生命周期,它帶來的麻煩主要有以下兩個方面:

          • 學(xué)習(xí)成本
          • 不合理的邏輯規(guī)劃方式

          對于第一點,大家都學(xué)過生命周期,都懂。下面著重說說這“不合理的邏輯規(guī)劃方式”是如何被 Hooks 解決掉的。

          2. Hooks 如何實現(xiàn)更好的邏輯拆分

          在過去,你是怎么組織自己的業(yè)務(wù)邏輯的呢?我想多數(shù)情況下應(yīng)該都是先想清楚業(yè)務(wù)的需要是什么樣的,然后將對應(yīng)的業(yè)務(wù)邏輯拆到不同的生命周期函數(shù)里去——沒錯,邏輯曾經(jīng)一度與生命周期耦合在一起。

          在這樣的前提下,生命周期函數(shù)常常做一些奇奇怪怪的事情:比如在 componentDidMount 里獲取數(shù)據(jù),在 componentDidUpdate 里根據(jù)數(shù)據(jù)的變化去更新 DOM 等。如果說你只用一個生命周期做一件事,那好像也還可以接受,但是往往在一個稍微成規(guī)模的 React 項目中,一個生命周期不止做一件事情。下面這段偽代碼就很好地詮釋了這一點:

          componentDidMount() {
            // 1. 這里發(fā)起異步調(diào)用
            // 2. 這里從 props 里獲取某個數(shù)據(jù),根據(jù)這個數(shù)據(jù)更新 DOM

            // 3. 這里設(shè)置一個訂閱

            // 4. 這里隨便干點別的什么 

            // ...
          }
          componentWillUnMount() {
            // 在這里卸載訂閱
          }
          componentDidUpdate() {
            // 1. 在這里根據(jù) DidMount 獲取到的異步數(shù)據(jù)更新 DOM

            // 2. 這里從 props 里獲取某個數(shù)據(jù),根據(jù)這個數(shù)據(jù)更新 DOM(和 DidMount 的第2步一樣)
          }

          像這樣的生命周期函數(shù),它的體積過于龐大,做的事情過于復(fù)雜,會給閱讀和維護者帶來很多麻煩。最重要的是,這些事情之間看上去毫無關(guān)聯(lián),邏輯就像是被“打散”進生命周期里了一樣。比如,設(shè)置訂閱和卸載訂閱的邏輯,雖然它們在邏輯上是有強關(guān)聯(lián)的,但是卻只能被分散到不同的生命周期函數(shù)里去處理,這無論如何也不能算作是一個非常合理的設(shè)計。

          而在 Hooks 的幫助下,我們完全可以把這些繁雜的操作按照邏輯上的關(guān)聯(lián)拆分進不同的函數(shù)組件里:我們可以有專門管理訂閱的函數(shù)組件、專門處理 DOM 的函數(shù)組件、專門獲取數(shù)據(jù)的函數(shù)組件等。Hooks 能夠幫助我們實現(xiàn)業(yè)務(wù)邏輯的聚合,避免復(fù)雜的組件和冗余的代碼。

          3. 狀態(tài)復(fù)用:Hooks 將復(fù)雜的問題變簡單

          過去我們復(fù)用狀態(tài)邏輯,靠的是 HOC(高階組件)和 Render Props 這些組件設(shè)計模式,這是因為 React 在原生層面并沒有為我們提供相關(guān)的途徑。但這些設(shè)計模式并非萬能,它們在實現(xiàn)邏輯復(fù)用的同時,也破壞著組件的結(jié)構(gòu),其中一個最常見的問題就是“嵌套地獄”現(xiàn)象。

          Hooks 可以視作是 React 為解決狀態(tài)邏輯復(fù)用這個問題所提供的一個原生途徑?,F(xiàn)在我們可以通過自定義 Hook,達到既不破壞組件結(jié)構(gòu)、又能夠?qū)崿F(xiàn)邏輯復(fù)用的效果。

          保持清醒:Hooks 并非萬能

          盡管我們已經(jīng)說了這么多 Hooks 的“好話”,盡管 React 團隊已經(jīng)用腳投票表明了對函數(shù)組件的積極態(tài)度,但我們還是要謹記這樣一個基本的認知常識:事事無絕對,凡事皆有兩面性。更何況 React 僅僅是推崇函數(shù)組件,并沒有“拉踩”類組件,甚至還官宣了“類組件和函數(shù)組件將繼續(xù)共存”這件事情。這些都在提醒我們,在認識到 Hooks 帶來的利好的同時,還需要認識到它的局限性。

          關(guān)于 Hooks 的局限性,目前社區(qū)鮮少有人討論。這里我想結(jié)合團隊開發(fā)過程當中遇到的一些瓶頸,和你分享實踐中的幾點感受:

          • Hooks 暫時還不能完全地為函數(shù)組件補齊類組件的能力:比如 getSnapshotBeforeUpdate、componentDidCatch 這些生命周期,目前都還是強依賴類組件的
          • “輕量”幾乎是函數(shù)組件的基因,這可能會使它不能夠很好地消化“復(fù)雜”:我們有時會在類組件中見到一些方法非常繁多的實例,如果用函數(shù)組件來解決相同的問題,業(yè)務(wù)邏輯的拆分和組織會是一個很大的挑戰(zhàn)。我個人的感覺是,從頭到尾都在“過于復(fù)雜”和“過度拆分”之間搖擺不定,哈哈。耦合和內(nèi)聚的邊界,有時候真的很難把握,函數(shù)組件給了我們一定程度的自由,卻也對開發(fā)者的水平提出了更高的要求。
          • Hooks 在使用層面有著嚴格的規(guī)則約束:對于如今的 React 開發(fā)者來說,如果不能牢記并踐行 Hooks 的使用原則,如果對 Hooks 的關(guān)鍵原理沒有扎實的把握,很容易把自己的 React 項目搞成大型車禍現(xiàn)場。

          最后

          如果覺得這篇文章還不錯
          點擊下面卡片關(guān)注我
          來個【分享、點贊、在看】三連支持一下吧

             “分享、點贊、在看” 支持一波  

          瀏覽 81
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  福利视频一区二区三区 | 国产成人网视频 | 天天摸天天摸 | 成年人视频大全 | 久久午夜无码人妻精品蜜桃冫 |