9102,做爲前端必須知道 hook 怎麼玩了

背景

很榮幸在6月8號那天參加了在上海舉辦的vueconf,其中尤大本人講解的vue3.0的介紹中,見識到了vue3.0的一些新特性,其中最重要的一項RFC就是 Vue Function-based API RFC,很巧的在不久前正好研究了一下react hook,感受2者的在思想上有着殊途同歸之妙,因此有了一個想總結一下關於hook的想法,同時看到不少人關於hook的介紹都是分開講的,固然可能和vue3.0對於這個特性的說明剛剛問世也有必定的關係,so,lets begin~javascript

什麼是hook

首先咱們須要瞭解什麼是hook,拿react的介紹來看,它的定義是:html

它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性前端

在16.8之前的版本中,咱們在寫react組件的時候,大部分都都是class component,由於基於class的組件react提供了更多的可操做性,好比擁有本身的state,以及一些生命週期的實現,對於複雜的邏輯來說class的支持程度是更高的:vue

class Hello extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() { // do sth... }

  componentWillUnmount() { // do sth... }
  
  // other methods or lifecycle...
  
  render() {
    return (
      <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div>
    );
  }
}
複製代碼

同時,對於function component來講,react也是支持的,可是function component只能擁有props,不能擁有state,也就是隻能實現stateless component:java

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
複製代碼

react 並無提供在函數組件中設置state以及生命週期的一些操做方法,因此那個時候,極少的場景下適合採用函數組件,可是16.8版本出現hook之後狀況獲得了改變,hook的目標就是--讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性,來看個例子:react

import React, { useState } from 'react';

function Example() {
  // 聲明一個新的叫作 「count」 的 state 變量
  const [count, setCount] = useState(0);

  return (
    <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div>
  );
}
複製代碼

useState就是react提供的一個Hook,經過它咱們就能夠在function組件中設置本身想要的state了,不只可使用還能夠很方便的去經過setState(注意不是class中的setState,這裏指的是上述例子中的setCount)更改,固然,react提供了不少hook來支持不一樣的行爲和操做,下面咱們還會再簡單介紹,咱們在看下vue hook,這是尤大在vueconf上分享的一段代碼:git

import { value, computed, watch, onMounted } from 'vue'

const App = {
  template: ` <div> <span>count is {{ count }}</span> <span>plusOne is {{ plusOne }}</span> <button @click="increment">count++</button> </div> `,
  setup() {
    // reactive state
    const count = value(0)
    // computed state
    const plusOne = computed(() => count.value + 1)
    // method
    const increment = () => { count.value++ }
    // watch
    watch(() => count.value * 2, val => {
      console.log(`count * 2 is ${val}`)
    })
    // lifecycle
    onMounted(() => {
      console.log(`mounted`)
    })
    // expose bindings on render context
    return {
      count,
      plusOne,
      increment
    }
  }
}
複製代碼

從上面的例子中不難看出,和react hook的用法很是類似,而且尤大也有說這個RFC是借鑑了react hook的想法,可是規避了一些react的問題,而後這裏解釋一下爲何我把vue的這個RFC也稱爲是hook,由於在react hook的介紹中有這麼一句話,什麼是hook--Hook 是一些可讓你在函數組件裏「鉤入」 React state 及生命週期等特性的函數,那麼vue提供的這些API的做用也是相似的--可讓你在函數組件裏「鉤入」 value(2.x中的data) 及生命週期等特性的函數,因此,暫且就叫vue-hook吧~github

hook的時代意義

那麼,hook的時代意義是什麼?咱們從頭來講,框架是服務於業務的,業務中很難避免的一個問題就是-- 邏輯複用,一樣的功能,一樣的組件,在不同的場合下,咱們有時候不得不去寫2+次,爲了不耦合,後來各大框架紛紛想出了一些辦法:redux

  • mixin
  • HOC
  • slot

各大框架的使用狀況:api

  • react 和 vue都曾用過mixin(react 目前已經廢棄),
  • Higher-Order-Components(HOC) react中用的相對多一點,vue的話,嵌套template有點。。彆扭,
  • slot vue中用的多一些,react基本不須要slot這種用法,

上述這些方法均可以實現邏輯上的複用,可是都有一些額外的問題:

  • mixin的問題:

    • 可能會相互依賴,相互耦合,不利於代碼維護;
    • 不一樣的mixin中的方法可能會相互衝突;
    • mixin很是多時,組件是能夠感知到的,甚至還要爲其作相關處理, 這樣會給代碼形成滾雪球式的複雜性
  • HOC的問題:

    • 須要在原組件上進行包裹或者嵌套,若是大量使用HOC, 將會產生很是多的嵌套,這讓調試變得很是困難;
    • HOC能夠劫持props,在不遵照約定的狀況下也可能形成衝突
    • props 也可能形成命名的衝突
    • wrapper hell

有沒有見過這樣的dom結構?

這就是wrapper hell的典型表明~

因此,hook的出現是劃時代的,它經過function抽離的方式,實現了複雜邏輯的內部封裝,根據上述咱們提出的問題總結了hook的一些優勢:

  1. 邏輯代碼的複用
  2. 減少了代碼體積
  3. 沒有this的煩惱

帶着這些思想,咱們一塊兒看下react和vue分別的實現:

react hook簡介

Dan 講解hook的視頻在這裏,若是你看不了這個,能夠嘗試看官網介紹

咱們用一樣功能的代碼來看react hook,實現一個監聽鼠標變化,並實時查看位置的功能,同時咱們把位置信息掛到title上面,用class component咱們要這樣寫:

import React, { Component } from 'react';

export default class MyClassApp extends Component {
    constructor(props) {
        super(props);
        this.state = {
            x: 0,
            y: 0
        };
        this.handleUpdate = this.handleUpdate.bind(this);
    }
    componentDidMount() {
        document.addEventListener('mousemove', this.handleUpdate);
    }
    componentDidUpdate() {
        const { x, y } = this.state;
        document.title = `(${x},${y})`;
    }
    componentWillUnmount() {
        window.removeEventListener('mousemove', this.handleUpdate);
    }
    handleUpdate(e) {
        this.setState({
            x: e.clientX,
            y: e.clientY
        });
    }
    render() {
        return (
            <div> current position x:{this.state.x}, y:{this.state.y} </div>
        );
    }
}

複製代碼

在線代碼演示在這裏

一樣的邏輯咱們換用hook來實現

import React, { useState, useEffect } from 'react';

// 自定義hook useMousePostion
const useMousePostion = () => {
    // 使用hookuseState初始化一個state
    const [postion, setPostion] = useState({ x: 0, y: 0 });
    function handleMove(e) {
        setPostion({ x: e.clientX, y: e.clientY });
    }
    // 使用useEffect處理class中生命週期能夠作到的事情
    // 注:效果同樣,可是實際的原理並不一樣,有興趣能夠去官網仔細研究
    useEffect(() => {
        // 同時能夠處理 componentDidMount 以及 componentDidUpdate 中的事情
        window.addEventListener('mousemove', handleMove);
        document.title = `(${postion.x},${postion.y})`;
        return () => {
            // return的function 能夠至關於在組件被卸載的時候執行 相似於 componentWillUnmount
            window.removeEventListener('mousemove', handleMove);
        };
        // [] 是參數,表明deps,也就是說react觸發這個hook的時機會和傳入的deps有關,內部利用objectIs實現
        // 默認不給參數會在每次render的時候調用,給空數組會致使每次比較是同樣的,只執行一次,這裏正確的應該是給postion
    }, [postion]);
    // postion 能夠被直接return,這樣達到了邏輯的複用~,哪裏須要哪裏調用就能夠了。
    return postion;
};

export default function App() {
    const { x, y } = useMousePostion(); // 內部維護本身的postion相關的邏輯
    return (
        <div> current position x: {x}, y: {y} </div>
    );
}

複製代碼

在線代碼演示在這裏

感謝評論區大佬的指正,查了react源碼,這裏爲了處理object.is的兼容性,用了objectIs

能夠看出用了hook以後,咱們把關於position的邏輯都放到一個自定義的hook--useMousePostion 中,以後複用是很方便的,並且能夠在內部進行維護postion獨有的邏輯而不影響外部內容,比起class組件,抽象能力更強。

固然,react hook 想要用好不可能這麼簡單,講解的文章也不少,這篇文章不深刻太多,只是一個拋磚引玉,下面給你們安利幾個不錯的資源:

入門:

深刻:

另外放一個筆者本身關於用hook實現redux的最佳實踐,注意是筆者本身這麼認爲的,歡迎大佬們指出問題,參考的這個文章

vue hook簡介

尤大講解的視頻在這裏

代碼由於vue3.0還沒有發佈,咱們仍是看尤大給的demo代碼:

import { value, computed, watch, onMounted } from 'vue'

function useMouse() {
  const x = value(0)
  const y = value(0)
  const update = e => {
    x.value = e.pageX
    y.value = e.pageY
  }
  onMounted(() => {
    window.addEventListener('mousemove', update)
  })
  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })
  return { x, y }
}

// 在組件中使用該函數
const Component = {
  setup() {
    const { x, y } = useMouse()
    // 與其它函數配合使用
    const { z } = useOtherLogic()
    return { x, y, z }
  },
  template: `<div>{{ x }} {{ y }} {{ z }}</div>`
}
複製代碼

能夠看出來一樣咱們能夠抽離一些須要複用的邏輯到一個單獨的函數useMouse中,而後在這個函數裏面定義的一些生命週期和value的內容會隨着setup函數的調用被「鉤入(hook)」到組件上,而且這個函數return出來的數據能夠直接被用在模板上,更具體的玩法咱們坐等3.0的出現吧。

基礎內容不過多介紹,畢竟真實的api還沒發佈,想了解具體內容的能夠來看尤大的講解

same & diff Point

看完了2個框架關於hook的實現,咱們來作個簡單的對比

  1. Same Point:
  • 出現的背景,解決的問題是同樣的,2個框架都是爲了解決邏輯複用過亂,代碼體積過大等一些問題,包括this問題,使用function函數咱們不多會和this去打交道了。
  • 使用方式相似,都是把能夠複用的一些單獨的邏輯抽離到一個單獨的函數中去,同時返回組件中須要用到的數據,而且內部會自我維護數據的更新,從而觸發視圖的更新
  1. Diff Point:

實現原理不一樣 react hook底層是基於鏈表實現,調用的條件是每次組件被render的時候都會順序執行全部的hooks,因此下面的代碼會報錯

function App(){
    const [name, setName] = useState('demo');
    if(condition){
        const [val, setVal] = useState('');
    }
}
複製代碼

由於底層是鏈表,每個hook的next是指向下一個hook的,if會致使順序不正確,從而致使報錯,因此react是不容許這樣使用hook的。

vue hook只會在setup函數被調用的時候被註冊一次,react數據更改的時候,會致使從新render,從新render又會從新把hooks從新註冊一次,因此react的上手難度更高一些,而vue之因此能避開這些麻煩的問題,根本緣由在於它對數據的響應是基於proxy的,這種場景下,只要任何一個更改data的地方,相關的function或者template都會被從新計算,所以避開了react可能遇到的性能上的問題

固然react對這些都有解決方案,想了解的同窗能夠去看官網有介紹,好比useCallback,useMemo等hook的做用,咱們看下尤大對vue和react hook的總結對比:

1.總體上更符合 JavaScript 的直覺;

2.不受調用順序的限制,能夠有條件地被調用;

3.不會在後續更新時不斷產生大量的內聯函數而影響引擎優化或是致使 GC 壓力;

4.不須要老是使用 useCallback 來緩存傳給子組件的回調以防止過分更新;

5.不須要擔憂傳了錯誤的依賴數組給 useEffect/useMemo/useCallback 從而致使回調中使用了過時的值 —— Vue 的依賴追蹤是全自動的。

不得不說,青出於藍而勝於藍,vue雖然借鑑了react,可是自然的響應式數據,完美的避開了一些react hook遇到的短板~

總結

  1. function component 將會是接下來各大框架發展的一個方向,function自然對TS的友好也是一個重要的影響;
  2. react hook的上手成本相對於vue會難一些,vue天生規避了一些react中比較難處理的地方;
  3. hook必定是大前端的一個趨勢,如今纔是剛剛開始的階段:SwiftUI-Hooks, flutter_hooks...

由於vue3.0的源碼還沒有發佈,有不少實現是猜想的,歡迎提出問題,一塊兒探討!

感受有用的話麻煩點個贊謝謝,您的點贊是我持續的動力~

相關文章
相關標籤/搜索