hooks怎麼樣,為什麼vue和react都選擇它!

2022-03-01 22:00:12
本篇文章我們來了解下hooks,聊聊為什麼和react都選擇它,為什麼我們需要 hooks ,以及vue 和 react 自定義 Hook 的異同,希望對大家有所幫助!

閱讀本文,你將:

  • 初步瞭解 Hooksvuereact 的現狀

  • 聽一聽本文作者關於 Hooks 的定義和總結

  • 弄懂「為什麼我們需要 Hooks

  • 進行一些簡單的 Hooks 實踐

一、hooks: 什麼叫大勢所趨?

2019年年初,react16.8.x 版本正式具備了 hooks 能力。

2019年6月,尤雨溪在 vue/github-issues 裡提出了關於 vue3 Component API 的提案。(vue hooks的基礎)

在後續的 reactvue3 相關版本中,相關 hooks 能力都開始被更多人所接受。【相關推薦:】

除此之外,solid.jspreact 等框架,也是開始選擇加入 hooks 大家庭。

1.png

可以預見,雖然目前仍然是 class Componenthooks api 並駕齊驅的場面,但未來幾年裡,hooks 極有可能取代 class Component 成為業內真正的主流。

二、什麼是 hooks

年輕時你不懂我,就像後來我不懂 hooks

2.1 hooks 的定義

"hooks" 直譯是 「勾點」,它並不僅是 react,甚至不僅是前端界的專用術語,而是整個行業所熟知的用語。通常指:

系統執行到某一時期時,會呼叫被註冊到該時機的回撥函數。

比較常見的勾點有:windows 系統的勾點能監聽到系統的各種事件,瀏覽器提供的 onloadaddEventListener 能註冊在瀏覽器各種時機被呼叫的方法。

以上這些,都可以被稱一聲 "hook"。

但是很顯然,在特定領域的特定話題下,hooks 這個詞被賦予了一些特殊的含義。

[email protected] 之前,當我們談論 hooks 時,我們可能談論的是「元件的生命週期」。

但是現在,hooks 則有了全新的含義。

react 為例,hooks 是:

一系列以 「use」 作為開頭的方法,它們提供了讓你可以完全避開 class式寫法,在函數式元件中完成生命週期、狀態管理、邏輯複用等幾乎全部元件開發工作的能力。

簡化一下:

一系列方法,提供了在函數式元件中完成開發工作的能力。

(記住這個關鍵詞: 函數式元件

import { useState, useEffect, useCallback } from 'react';
// 比如以上這幾個方法,就是最為典型的 Hooks

而在 vue 中, hooks 的定義可能更模糊,姑且總結一下:

vue 組合式API裡,以 「use」 作為開頭的,一系列提供了元件複用、狀態管理等開發能力的方法。

(關鍵詞:組合式API

import { useSlots, useAttrs } from 'vue';
import { useRouter } from 'vue-router';
// 以上這些方法,也是 vue3 中相關的 Hook!

如:useSlotsuseAttrsuseRouter 等。

但主觀來說,我認為vue 組合式API其本身就是「vue hooks」的關鍵一環,起到了 react hooks裡對生命週期、狀態管理的核心作用。(如 onMountedref 等等)。

2.png

如果按這個標準來看的話,vuereacthooks 的定義,似乎都差不多。

那麼為什麼要提到是以 「use」 作為開頭的方法呢?

2.2 命名規範 和 指導思想

通常來說,hooks 的命名都是以 use 作為開頭,這個規範也包括了那麼我們自定義的 hooks

為什麼?

因為(愛情 誤)約定。

react 官方檔案裡,對 hooks 的定義和使用提出了 「一個假設、兩個只在」 核心指導思想。(播音腔)

3.png

一個假設: 假設任何以 「use」 開頭並緊跟著一個大寫字母的函數就是一個 Hook

第一個只在: 只在 React 函陣列件中呼叫 Hook,而不在普通函數中呼叫 Hook。(Eslint 通過判斷一個方法是不是大坨峰命名來判斷它是否是 React 函數)

第二個只在: 只在最頂層使用 Hook,而不要在迴圈,條件或巢狀函數中呼叫 Hook。

因為是約定,因而 useXxx 的命名並非強制,你依然可以將你自定義的 hook 命名為 byXxx 或其他方式,但不推薦。

因為約定的力量在於:我們不用細看實現,也能通過命名來了解一個它是什麼。

以上 「一個假設、兩個只在」 總結自 react 官網:

  • https://zh-hans.reactjs.org/docs/hooks-rules.html

  • https://zh-hans.reactjs.org/docs/hooks-faq.html#what-exactly-do-the-lint-rules-enforce

三、為什麼我們需要 hooks ?

3.1 更好的狀態複用

懟的就是你,mixin

class 元件模式下,狀態邏輯的複用是一件困難的事情。

假設有如下需求:

當元件範例建立時,需要建立一個 state 屬性:name,並隨機給此 name 屬性附一個初始值。除此之外,還得提供一個 setName 方法。你可以在元件其他地方開銷和修改此狀態屬性。

更重要的是: 這個邏輯要可以複用,在各種業務元件裡複用這個邏輯。

在擁有 Hooks 之前,我首先會想到的解決方案一定是 mixin

程式碼如下:(此範例採用 vue2 mixin 寫法 )

// 混入檔案:name-mixin.js
export default {
  data() {
    return {
      name: genRandomName() // 假裝它能生成隨機的名字
    }
  },
  methods: {
    setName(name) {
      this.name = name
    }
  }
}
// 元件:my-component.vue
<template>
  <div>{{ name }}</div>
<template>
<script>
import nameMixin from './name-mixin';
export default {
  mixins: [nameMixin],
  // 通過mixins, 你可以直接獲得 nameMixin 中所定義的狀態、方法、生命週期中的事件等
  mounted() {
    setTimeout(() => {
      this.setName('Tom')
    }, 3000)
  }
}
<script>

粗略看來,mixins 似乎提供了非常不錯的複用能力,但是,react官方檔案直接表明:

4.png

為什麼呢?

因為 mixins 雖然提供了這種狀態複用的能力,但它的弊端實在太多了

弊端一:難以追溯的方法與屬性!

試想一下,如果出現這種程式碼,你是否會懷疑人生:

export default {
  mixins: [ a, b, c, d, e, f, g ], // 當然,這只是表示它混入了很多能力
  mounted() {
    console.log(this.name)
    // mmp!這個 this.name 來自於誰?我難道要一個個混入看實現?
  }
}

又或者:

a.js mixins: [b.js]

b.js mixins: [c.js]

c.js mixins: [d.js]

// 你猜猜看, this.name 來自於誰?
// 求求你別再說了,我血壓已經上來了

弊端二:覆蓋、同名?貴圈真亂!

當我同時想混入 mixin-a.jsmixin-b.js 以同時獲得它們能力的時候,不幸的事情發生了:

由於這兩個 mixin 功能的開發者惺惺相惜,它們都定義了 this.name 作為屬性。

這種時候,你會深深懷疑,mixins 究竟是不是一種科學的複用方式。

弊端三:梅開二度?代價很大!

仍然說上面的例子,如果我的需求發生了改變,我需要的不再是一個簡單的狀態 name,而是分別需要 firstNamelastName
此時 name-mixin.js 混入的能力就會非常尷尬,因為我無法兩次 mixins 同一個檔案。

當然,也是有解決方案的,如:

// 動態生成mixin
function genNameMixin(key, funcKey) {
  return {
    data() {
      return {
        [key]: genRandomName()
      }
    },
    methods: {
      [funcKey]: function(v) {
        this.[key] = v
      } 
    }
  }
}

export default {
  mixins: [
    genNameMixin('firstName', 'setFirstName'),
    genNameMixin('lastName', 'setLastName'),
  ]
}

確實通過動態生成 mixin 完成了能力的複用,但這樣一來,無疑更加地增大了程式的複雜性,降低了可讀性。

因此,一種新的 「狀態邏輯複用」 就變得極為迫切了——它就是 Hooks!

Hook 的狀態複用寫法:

// 單個name的寫法
const { name, setName } = useName();

// 梅開二度的寫法
const { name : firstName, setName : setFirstName } = useName();

const { name : secondName, setName : setSecondName } = useName();

相比於 mixins,它們簡直太棒了!

  1. 方法和屬性好追溯嗎?這可太好了,誰產生的,哪兒來的一目瞭然。
  2. 會有重名、覆蓋問題嗎?完全沒有!內部的變數在閉包內,返回的變數支援定義別名。
  3. 多次使用,沒開N度?你看上面的程式碼塊內不就「梅開三度」 了嗎?

就衝 「狀態邏輯複用」 這個理由,Hooks 就已經香得我口水直流了。

3.2 程式碼組織

熵減,宇宙哲學到編碼哲學。

專案、模組、頁面、功能,如何高效而清晰地組織程式碼,這一個看似簡單的命題就算寫幾本書也無法完全說清楚。

但一個頁面中,N件事情的程式碼在一個元件內互相糾纏確實是在 Hooks 出現之前非常常見的一種狀態。

那麼 Hooks 寫法在程式碼組織上究竟能帶來怎樣的提升呢?

5.png

(假設上圖中每一種顏色就程式碼一種高度相關的業務邏輯)

無論是 vue 還是 react, 通過 Hooks 寫法都能做到,將「分散在各種宣告週期裡的程式碼塊」,通過 Hooks 的方式將相關的內容聚合到一起。

這樣帶來的好處是顯而易見的:「高度聚合,可閱讀性提升」。伴隨而來的便是 「效率提升,bug變少」

按照「物理學」裡的理論來說,這種程式碼組織方式,就算是「熵減」了。

3.3 比 class 元件更容易理解

尤其是 this

reactclass 寫法中,隨處可見各種各樣的 .bind(this)。(甚至官方檔案裡也有專門的章節描述了「為什麼繫結是必要的?」這一問題)

vue 玩家別笑,computed: { a: () => { this } } 裡的 this 也是 undefined

很顯然,繫結雖然「必要」,但並不是「優點」,反而是「故障高發」地段。

但在Hooks 寫法中,你就完全不必擔心 this 的問題了。

因為:

本來無一物,何處惹塵埃。

Hooks 寫法直接告別了 this,從「函數」來,到「函數」去。

媽媽再也不用擔心我忘記寫 bind 了。

3.4 友好的漸進式

隨風潛入夜,潤物細無聲。

漸進式的含義是:你可以一點點深入使用。

無論是 vue 還是 react,都只是提供了 Hooks API,並將它們的優劣利弊擺在了那裡。並沒有通過無法接受的 break change 來強迫你必須使用 Hooks 去改寫之前的 class 元件。

你依然可以在專案裡一邊寫 class 元件,一邊寫 Hooks 元件,在專案的演進和開發過程中,這是一件沒有痛感,卻悄無聲息改變著一切的事情。

但是事情發展的趨勢卻很明顯,越來越多的人加入了 Hooks組合式API 的大軍。

四、如何開始玩 hooks

4.1 環境和版本

react 專案中, react 的版本需要高於 16.8.0

而在 vue 專案中, vue3.x 是最好的選擇,但 vue2.6+ 配合 @vue/composition-api,也可以開始享受「組合式API」的快樂。

4.2 react 的 Hooks 寫法

因為 react Hooks 僅支援「函數式」元件,因此需要建立一個函數式元件 my-component.js

// my-component.js
import { useState, useEffect } from 'React'

export default () => {
  // 通過 useState 可以建立一個 狀態屬性 和一個賦值方法
  const [ name, setName ] = useState('')

  // 通過 useEffect 可以對副作用進行處理
  useEffect(() => {
    console.log(name)
  }, [ name ])

  // 通過 useMemo 能生成一個依賴 name 的變數 message
  const message = useMemo(() => {
    return `hello, my name is ${name}`
  }, [name])

  return <div>{ message }</div>
}

細節可參考 react 官方網站:https://react.docschina.org/docs/hooks-intro.html

4.3 vue 的 Hooks 寫法

vue 的 Hooks 寫法依賴於 組合式API,因此本例採用 <script setup> 來寫:

<template>
  <div>
    {{ message }}
  </div>
</template>
<script setup>
import { computed, ref } from 'vue'
// 定義了一個 ref 物件
const name = ref('')
// 定義了一個依賴 name.value 的計算屬性
const message = computed(() => {
  return `hello, my name is ${name.value}`
})
</script>

很明顯,vue 組合式API裡完成 useStateuseMemo 相關工作的 API 並沒有通過 useXxx 來命名,而是遵從了 Vue 一脈相承而來的 refcomputed

雖然不符合 react Hook 定義的 Hook 約定,但 vueapi 不按照 react 的約定好像也並沒有什麼不妥。

參考網址:https://v3.cn.vuejs.org/api/composition-api.html

五、開始第一個自定義 hook

除了官方提供的 Hooks Api, Hooks 的另外一個重要特質,就是可以自己進行「自定義 Hooks」 的定義,從而完成狀態邏輯的複用。

開源社群也都有很多不錯的基於 Hooks 的封裝,比如 ahooks (ahooks.js.org/zh-CN/),又比如 vueuse(vueuse.org/)

我還專門寫過一篇小文章介紹 vuehook:

  • 【一庫】vueuse:我不許身為vuer,你的工具集只有lodash!。

  • https://juejin.cn/post/7030395303433863205

6.png

那麼,我們應該怎麼開始撰寫 「自定義Hooks」 呢?往下看吧!

5.1 react 玩家看這裡

react 官方網站就專門有一個章節講述「自定義Hook」。(https://react.docschina.org/docs/hooks-custom.html)

這裡,我們扔用文章開頭那個 useName 的需求為例,希望達到效果:

const { name, setName } = useName();
// 隨機生成一個狀態屬性 name,它有一個隨機名作為初始值
// 並且提供了一個可隨時更新該值的方法 setName

如果我們要實現上面效果,我們該怎麼寫程式碼呢?

import React from 'react';

export const useName = () => {
  // 這個 useMemo 很關鍵
  const randomName = React.useMemo(() => genRandomName(), []);
  const [ name, setName ] = React.useState(randomName)

  return {
    name,
    setName
  }
}

忍不住要再次感嘆一次,和 mixins 相比,它不僅使用起來更棒,就連定義起來也那麼簡單。

可能有朋友會好奇,為什麼不直接這樣寫:

const [ name, setName ] = React.useState(genRandomName())

因為這樣寫是不對的,每次使用該 Hook 的函陣列件被渲染一次時,genRandom() 方法就會被執行一次,雖然不影響 name 的值,但存在效能消耗,甚至產生其他 bug

為此,我寫了一個能復現錯誤的demo,有興趣的朋友可以點開驗證:https://codesandbox.io/s/long-cherry-kzcbqr

2022-02-03日補充更正:經掘友提醒,可以通過 React.useState(() => randomName()) 傳參來避免重複執行,這樣就不需要 useMemo 了,感謝!

5.2 vue 玩家看這裡

vue3 官網沒有關於 自定義Hook 的玩法介紹,但實踐起來也並不困難。

目標也定位實現一個 useName 方法:

import { ref } from 'vue';

export const useName = () => {
  const name = ref(genRandomName())
  const setName = (v) => {
    name.value = v
  }
  return {
    name,
    setName
  }
}

5.3 vuereact 自定義 Hook 的異同

  • 相似點: 總體思路是一致的 都遵照著 "定義狀態資料","操作狀態資料","隱藏細節" 作為核心思路。

  • 差異點: 組合式APIReact函陣列件 有著本質差異
    vue3 的元件裡, setup 是作為一個早於 「created」 的生命週期存在的,無論如何,在一個元件的渲染過程中只會進入一次。
    React函陣列件 則完全不同,如果沒有被 memorized,它們可能會被不停地觸發,不停地進入並執行方法,因此需要開銷的心智相比於vue其實是更多的。

本文轉載自:https://juejin.cn/post/7066951709678895141

作者:春哥的夢想是摸魚

(學習視訊分享:)

以上就是hooks怎麼樣,為什麼vue和react都選擇它!的詳細內容,更多請關注TW511.COM其它相關文章!