初步瞭解 Hooks
在 vue
與 react
的現狀
聽一聽本文作者關於 Hooks
的定義和總結
弄懂「為什麼我們需要 Hooks
」
進行一些簡單的 Hooks
實踐
2019年年初,react
在 16.8.x
版本正式具備了 hooks
能力。
2019年6月,尤雨溪在 vue/github-issues 裡提出了關於 vue3 Component API
的提案。(vue hooks的基礎)
在後續的 react
和 vue3
相關版本中,相關 hooks
能力都開始被更多人所接受。【相關推薦:】
除此之外,solid.js
、 preact
等框架,也是開始選擇加入 hooks
大家庭。
可以預見,雖然目前仍然是 class Component
和 hooks api
並駕齊驅的場面,但未來幾年裡,hooks
極有可能取代 class Component
成為業內真正的主流。
hooks
?年輕時你不懂我,就像後來我不懂
hooks
。
hooks
的定義"hooks" 直譯是 「勾點」,它並不僅是 react
,甚至不僅是前端界的專用術語,而是整個行業所熟知的用語。通常指:
系統執行到某一時期時,會呼叫被註冊到該時機的回撥函數。
比較常見的勾點有:windows
系統的勾點能監聽到系統的各種事件,瀏覽器提供的 onload
或 addEventListener
能註冊在瀏覽器各種時機被呼叫的方法。
以上這些,都可以被稱一聲 "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!
如:useSlots
、 useAttrs
、 useRouter
等。
但主觀來說,我認為vue
組合式API其本身就是「vue hooks」的關鍵一環,起到了 react hooks
裡對生命週期、狀態管理的核心作用。(如 onMounted
、 ref
等等)。
如果按這個標準來看的話,vue
和 react
中 hooks
的定義,似乎都差不多。
那麼為什麼要提到是以 「use」
作為開頭的方法呢?
通常來說,hooks
的命名都是以 use
作為開頭,這個規範也包括了那麼我們自定義的 hooks
。
為什麼?
因為(愛情 誤)約定。
在 react
官方檔案裡,對 hooks
的定義和使用提出了 「一個假設、兩個只在」 核心指導思想。(播音腔)
一個假設: 假設任何以 「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官方檔案直接表明:
為什麼呢?
因為 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.js
和 mixin-b.js
以同時獲得它們能力的時候,不幸的事情發生了:
由於這兩個 mixin
功能的開發者惺惺相惜,它們都定義了 this.name 作為屬性。
這種時候,你會深深懷疑,mixins
究竟是不是一種科學的複用方式。
弊端三:梅開二度?代價很大!
仍然說上面的例子,如果我的需求發生了改變,我需要的不再是一個簡單的狀態 name
,而是分別需要 firstName
和 lastName
。
此時 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
,它們簡直太棒了!
就衝 「狀態邏輯複用」 這個理由,Hooks
就已經香得我口水直流了。
3.2 程式碼組織
熵減,宇宙哲學到編碼哲學。
專案、模組、頁面、功能,如何高效而清晰地組織程式碼,這一個看似簡單的命題就算寫幾本書也無法完全說清楚。
但一個頁面中,N件事情的程式碼在一個元件內互相糾纏確實是在 Hooks
出現之前非常常見的一種狀態。
那麼 Hooks
寫法在程式碼組織上究竟能帶來怎樣的提升呢?
(假設上圖中每一種顏色就程式碼一種高度相關的業務邏輯)
無論是 vue
還是 react
, 通過 Hooks
寫法都能做到,將「分散在各種宣告週期裡的程式碼塊」,通過 Hooks
的方式將相關的內容聚合到一起。
這樣帶來的好處是顯而易見的:「高度聚合,可閱讀性提升」。伴隨而來的便是 「效率提升,bug變少」。
按照「物理學」裡的理論來說,這種程式碼組織方式,就算是「熵減」了。
3.3 比 class
元件更容易理解
尤其是
this
。
在 react
的 class
寫法中,隨處可見各種各樣的 .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裡完成 useState
和 useMemo
相關工作的 API
並沒有通過 useXxx
來命名,而是遵從了 Vue
一脈相承而來的 ref
和 computed
。
雖然不符合 react Hook
定義的 Hook
約定,但 vue
的 api
不按照 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
那麼,我們應該怎麼開始撰寫 「自定義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 vue
和 react
自定義 Hook
的異同
相似點: 總體思路是一致的 都遵照著 "定義狀態資料","操作狀態資料","隱藏細節" 作為核心思路。
差異點: 組合式API
和 React函陣列件
有著本質差異vue3
的元件裡, setup
是作為一個早於 「created」 的生命週期存在的,無論如何,在一個元件的渲染過程中只會進入一次。React函陣列件
則完全不同,如果沒有被 memorized
,它們可能會被不停地觸發,不停地進入並執行方法,因此需要開銷的心智相比於vue
其實是更多的。
本文轉載自:https://juejin.cn/post/7066951709678895141
作者:春哥的夢想是摸魚
(學習視訊分享:)
以上就是hooks怎麼樣,為什麼vue和react都選擇它!的詳細內容,更多請關注TW511.COM其它相關文章!