您好,登錄后才能下訂單哦!
本篇內容主要講解“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
這個詞被賦予了一些特殊的含義。
在 react@16.x
之前,當我們談論 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
或其他方式,但不推薦。
因為約定的力量在于:我們不用細看實現,也能通過命名來了解一個它是什么。
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
,它們簡直太棒了!
方法和屬性好追溯嗎?這可太好了,誰產生的,哪兒來的一目了然。
會有重名、覆蓋問題嗎?完全沒有!內部的變量在閉包內,返回的變量支持定義別名。
多次使用,沒開N度?你看上面的代碼塊內不就“梅開三度” 了嗎?
就沖 “狀態邏輯復用” 這個理由,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> }
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
的約定好像也并沒有什么不妥。
hook
除了官方提供的 Hooks Api
, Hooks
的另外一個重要特質,就是可以自己進行“自定義 Hooks” 的定義,從而完成狀態邏輯的復用。
開源社區也都有很多不錯的基于 Hooks
的封裝,比如 ahooks
(ahooks.js.org/zh-CN/),又比如 vueuse
(vueuse.org/)
那么,我們應該怎么開始撰寫 “自定義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
其實是更多的。
到此,相信大家對“hooks有哪些特點”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。