關於為什麼使用React新特性Hook的一些實踐與淺見

来源:https://www.cnblogs.com/vvjiang/archive/2019/12/27/12101796.html
-Advertisement-
Play Games

前言 關於Hook的定義官方文檔是這麼說的: Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。 簡單來說,就是在使用函數式組件時能用上state,還有一些生命周期函數等其他的特性。 如果想瞭解Hook怎麼用, " ...


前言

關於Hook的定義官方文檔是這麼說的:

Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。

簡單來說,就是在使用函數式組件時能用上state,還有一些生命周期函數等其他的特性。

如果想瞭解Hook怎麼用,官方文檔和阮一峰的React Hooks 入門教程都講得很清楚了,我建議直接看官方文檔和阮大神的文章即可。

本篇博客只講為什麼要用React的Hook新特性,以及它解決了什麼問題。

為什麼使用Hook?

讓我們先看看別人怎麼說。

阮大神的文章中給了一個示例代碼:

import React, { Component } from "react";

export default class Button extends Component {
  constructor() {
    super();
    this.state = { buttonText: "Click me, please" };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState(() => {
      return { buttonText: "Thanks, been clicked!" };
    });
  }
  render() {
    const { buttonText } = this.state;
    return <button onClick={this.handleClick}>{buttonText}</button>;
  }
}

並且提出:

這個組件類僅僅是一個按鈕,但可以看到,它的代碼已經很"重"了。
真實的 React App 由多個類按照層級,一層層構成,複雜度成倍增長。
再加入 Redux,就變得更複雜。

實際上,上面這個代碼的“重”有部分來源於寫法問題,他可能並沒有“重”,讓我們看看下麵這種class寫法:

import React, { Component } from "react";

export default class Button extends Component {
  state = {
    buttonText: "Click me, please"
  }
  handleClick = () => {
    this.setState(() => {
      return { buttonText: "Thanks, been clicked!" };
    });
  }
  render() {
    const { buttonText } = this.state;
    return <button onClick={this.handleClick}>{buttonText}</button>;
  }
}

然後再對比下使用了Hook的函數式組件:

import React, { useState } from "react";

export default function Button() {
  const [buttonText, setButtonText] = useState("Click me,   please");

  function handleClick() {
    return setButtonText("Thanks, been clicked!");
  }

  return <button onClick={handleClick}>{buttonText}</button>;
}

即使是我們簡化過的class寫法,比起Hook的看起來好像也確實“重”了點。

Hook的語法確實簡練了一些,但是這個理由並不是那麼充分。

阮大神同時列舉了Redux 的作者 Dan Abramov 總結了組件類的幾個缺點:

  • 大型組件很難拆分和重構,也很難測試。
  • 業務邏輯分散在組件的各個方法之中,導致重覆邏輯或關聯邏輯。(這裡我認為阮大神寫的可能有點問題,應該是是各個生命周期方法更為準確)
  • 組件類引入了複雜的編程模式,比如 render props 和高階組件。

這三點都是事實,於是有了函數化的組件,但之前的函數化組件沒有state和生命周期,有了Hook那麼就可以解決這個痛點。

而且Hook並不只是這麼簡單,通過自定義Hook,我們可以將原有組件的邏輯提取出來實現復用。

用useEffect解決生命周期導致的重覆邏輯或關聯邏輯

上面舉的幾個缺點,第一點和第三點你可能很容易理解,第二點就不容易理解了,所以我們需要深入到具體的代碼中去理解這句話。

我們看看下麵這段代碼:

import React, { Component } from "react";

export default class Match extends Component {
  state={
    matchInfo:''
  }
  componentDidMount() {
    this.getMatchInfo(this.props.matchId)
  }
  componentDidUpdate(prevProps) {
    if (prevProps.matchId !== this.props.matchId) {
      this.getMatchInfo(this.props.matchId)
    }
  }
  getMatchInfo = (matchId) => {
    // 請求後臺介面獲取賽事信息
    // ...
    this.setState({
      matchInfo:serverResult // serverResult是後臺介面的返回值
    })
  }

  render() {
    const { matchInfo } = this.state
    return <div>{matchInfo}</div>;
  }
}

這樣的代碼在我們的業務中經常會出現,通過修改傳入賽事組件的ID,去改變這個賽事組件的信息。

在上面的代碼中,受生命周期影響,我們需要在載入完畢和Id更新時都寫上重覆的邏輯和關聯邏輯。

所以現在你應該比較好理解這句話:業務邏輯分散在組件的各個生命周期方法之中,導致重覆邏輯或關聯邏輯

為瞭解決這一點,React提供了useEffect這個鉤子。

但是在講這個之前,我們需要先瞭解到React帶來的一個新的思想:同步。

我們在上面的代碼中所做的實際上就是在把組件內的狀態和組件外的狀態進行同步。

所以在使用Hook之前,我們需要先摒棄生命周期的思想,而用同步的思想去思考這個問題。

現在再讓我們看看改造後的代碼:

import React, { Component } from "react";

export default function Match({matchId}) {
  const [ matchInfo, setMatchInfo ] = React.useState('')
  React.useEffect(() => {
    // 請求後臺介面獲取賽事信息
    // ...
    setMatchInfo(serverResult) // serverResult是後臺介面的返回值
  }, [matchId])
  
  return <div>{matchInfo}</div>;
}

看到這個代碼,再對比上面的代碼,你心中第一反應應該就是:簡單。

React.useEffect接受兩個參數,第一個參數是Effect函數,第二個參數是一個數組。

組件載入的時候,執行Effect函數。

組件更新會去判斷數組中的各個值是否變動,如果不變,那麼不會執行Effect函數。

而如果不傳第二個參數,那麼無論載入還是更新,都會執行Effect函數。

順便提一句,這裡有組件載入和更新的生命周期的概念了,那麼也應該是有組件卸載的概念的:

import React, { Component } from "react";

export default function Match({matchId}) {
  const [ matchInfo, setMatchInfo ] = React.useState('')
  React.useEffect(() => {
    // 請求後臺介面獲取賽事信息
    // ...
    setMatchInfo(serverResult) // serverResult是後臺介面的返回值

    return ()=>{
      // 組件卸載後的執行代碼
    }
  }, [matchId])
  
  return <div>{matchInfo}</div>;
}
}

這個常用於事件綁定解綁之類的。

用自定義Hook解決高階組件

React的高階組件是用來提煉重覆邏輯的組件工廠,簡單一點來說就是個函數,輸入參數為組件A,輸出的是帶有某邏輯的組件A+。

回想一下上面的Match組件,假如這個組件是頁面A的首頁頭部用來展示賽事信息,然後現在頁面B的側邊欄也需要展示賽事信息。

問題就在於頁面A的這塊UI需要用div,而頁面B側邊欄的這塊UI需要用到span。

保證今天早點下班的做法是複製A頁面的代碼到頁面B,然後改下render的UI即可。

保證以後早點下班的做法是使用高階組件,請看下麵的代碼:

import React from "react";

function hocMatch(Component) {
  return class Match React.Component {
    componentDidMount() {
      this.getMatchInfo(this.props.matchId)
    }
    componentDidUpdate(prevProps) {
      if (prevProps.matchId !== this.props.matchId) {
        this.getMatchInfo(this.props.matchId)
      }
    }
    getMatchInfo = (matchId) => {
      // 請求後臺介面獲取賽事信息
    }
    render () {
      return (
        <Component {...this.props} />
      )
    }
  }
}

const MatchDiv=hocMatch(DivUIComponent)
const MatchSpan=hocMatch(SpanUIComponent)

<MatchDiv matchId={1} matchInfo={matchInfo} />
<MatchSpan matchId={1} matchInfo={matchInfo} />

但是實際上有的時候我們的高階組件可能會更複雜,比如react-redux的connect,這就是高階組件的複雜化使用方式。

又比如:

hocPage(
  hocMatch(
    hocDiv(DivComponent)
  )
)

毫無疑問高階組件能讓我們復用很多邏輯,但是過於複雜的高階組件會讓之後的維護者望而卻步。

而Hook的玩法是使用自定義Hook去提煉這些邏輯,首先看看我們之前使用了Hook的函數式組件:

import React, { Component } from "react";

export default function Match({matchId}) {
  const [ matchInfo, setMatchInfo ] = React.useState('')
  React.useEffect(() => {
    // 請求後臺介面獲取賽事信息
    // ...
    setMatchInfo(serverResult) // serverResult是後臺介面的返回值
  }, [matchId])
  
  return <div>{matchInfo}</div>;
}

然後,自定義Hook:

function useMatch(matchId){
  const [ matchInfo, setMatchInfo ] = React.useState('')
  React.useEffect(() => {
    // 請求後臺介面獲取賽事信息
    // ...
    setMatchInfo(serverResult) // serverResult是後臺介面的返回值
  }, [matchId])
  return [matchInfo]
}

接下來,修改原來的Match組件

export default function Match({matchId}) {
  const [matchInfo]=useMatch(matchId)
  return <div>{matchInfo}</div>;
}

相比高階組件,自定義Hook更加簡單,也更加容易理解。

現在我們再來處理以下這種情況:

hocPage(
  hocMatch(
    hocDiv(DivComponent)
  )
)

我們的代碼將不會出現這種不斷嵌套情況,而是會變成下麵這種:

export default function PageA({matchId}) {
  const [pageInfo]=usePage(pageId)
  const [matchInfo]=useMatch(matchId)
  const [divInfo]=useDiv(divId)

  return <ul>
    <li>{pageInfo}</li>
    <li>{matchInfo}</li>
    <li>{divInfo}</li>
  </ul>
}

是否需要改造舊的class組件?

現在我們瞭解到了Hook的好,所以就需要去改造舊的class組件。

官方推薦不需要專門為了hook去改造class組件,並且保證將繼續更新class相關功能。

實際上我們也沒有必要專門去改造舊項目中的class組件,因為工作量並不小。

但是我們完全可以在新的項目或者新的組件中去使用它。

總結

Hook是對函數式組件的一次增強,使得函數式組件可以做到class組件的state和生命周期。

Hook的語法更加簡練易懂,消除了class的生命周期方法導致的重覆邏輯代碼,解決了高階組件難以理解和使用困難的問題。

然而Hook並沒有讓函數式組件能做到class組件做不到的事情,它只是讓很多事情變得更加簡單而已。

class組件並不會消失,但hook化的函數式組件將是趨勢。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 構造函數,實例對象和原型對象,三者關係 構造函數裡面有原型(prototype)屬性,即原型對象 原型對象里的constryctor構造器指向構造函數 通過構造函數,實例化,創建的就是實例對象。 實例對象通過__proto__屬性調用原型對象裡面的方法 構造函數可以實例化對象 構造函數中有一個屬性叫 ...
  • 今天總結一下常用的JS數組方法,以免搞忘了或者記混了,大家覺得還有哪些數組方法在項目里用的特別多我沒提到的可以補充。。 1.map :遍曆數組的每一項並對其進行操作。 有返回值 且 不改變原數組。 var arr = [1, 2, 3, 4, 5, 6]; var res = arr.map(ite ...
  • JS高級 構造函數,通過原型添加方法,原型的作用: 共用數據, 節省記憶體空間 構造函數 //構造函數 function Person(sex, age) { this.sex = sex; this.age = age; } 通過原型添加方法 //通過原型添加方法 Person.prototype. ...
  • 體會面向對象和麵向過程的編程思想 ChangeStyle是自定義的構造函數,再通過原型添加方法的函數。 實例化對象,導入json參數,和創建cs,調用原型添加的方法函數 過渡,先熟悉記憶 <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...
  • 原型的引入:解決:通過構造函數創建對象帶來的問題,即浪費記憶體(一個對象開一個記憶體,多個對象開多個記憶體) 通過原型來添加方法,解決數據共用,節省記憶體空間 <script> function Person(name, age) { this.name = name; this.age = age; } ...
  • 實例對象和構造函數之間的關係: 1. 實例對象是通過構造函數來創建的 創建的過程叫實例化 2. 如何判斷對象是不是這個數據類型? 1) 通過構造器的方式 實例對象.構造器==構造函數名字 2) 對象 instanceof 構造函數名字 儘可能的使用第二種方式來識別,為什麼?原型講完再說 //面向對象 ...
  • 創建對象:工廠模式和自定義構造函數的區別 共同點: 都是函數, 都可以創建對象, 都可以傳入參數 區別: 工廠模式: 函數名是小寫 有new, 有返回值 new之後的對象是當前的對象 直接調用函數就可以創建對象 //工廠模式創建對象 function createObject(name, age) ...
  • JS高級 三種創建對象的方式 字面量的方式 (實例對象) 調用系統的構造函數 自定義構造函數方式 //創建對象 >實例化一個對象,的同時對屬性進行初始化 var per=new Person("小紅",20); 自動逸構造函數創建對象做的事情: 1.開闢空間存儲對象 2.把this設置為當前的對象 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...