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的源码还没有发布,有不少实现是猜想的,欢迎提出问题,一块儿探讨!

感受有用的话麻烦点个赞谢谢,您的点赞是我持续的动力~

相关文章
相关标签/搜索