vue3与react的hook比较

# 背景

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

2019年6月,尤雨溪在 vue/github-issues (opens new window) 里提出了关于 vue3 Component API 的提案。(vue hooks的基础)

在后续的 reactvue3 相关版本中,相关 hooks 能力都开始被更多人所接受。

除此之外,solid.jspreact 等框架,也是开始选择加入 hooks 大家庭。

可以预见,虽然目前仍然是 class Componenthooks api 并驾齐驱的场面,但未来几年里,hooks 极有可能取代 class Component 成为业内真正的主流。

# hooks简介

# 定义

"hooks" 直译是 “钩子”,它并不仅是 react,甚至不仅是前端界的专用术语,而是整个行业所熟知的用语。通常指:

系统运行到某一时期时,会调用被注册到该时机的回调函数。

比较常见的钩子有:windows 系统的钩子能监听到系统的各种事件,浏览器提供的 onloadaddEventListener 能注册在浏览器各种时机被调用的方法。

以上这些,都可以被称一声 "hook"。

案例比较

react 为例,hooks 是:

一系列以 “use” 作为开头的方法,它们提供了让你可以完全避开 class式写法,在函数式组件中完成生命周期、状态管理、逻辑复用等几乎全部组件开发工作的能力。

简化一下:

一系列方法,提供了在函数式组件中完成开发工作的能力。

(记住这个关键词: 函数式组件

import { useState, useEffect, useCallback } from 'react';
// 比如以上这几个方法,就是最为典型的 Hooks
1
2

而在 vue 中, hooks 的定义可能更模糊,姑且总结一下:

vue 组合式API里,以 “use” 作为开头的,一系列提供了组件复用、状态管理等开发能力的方法。

(关键词:组合式API

import { useSlots, useAttrs } from 'vue';
import { useRouter } from 'vue-router';
// 以上这些方法,也是 vue3 中相关的 Hook!
1
2
3

如:useSlotsuseAttrsuseRouter 等。

但主观来说,认为vue 组合式API其本身就是“vue hooks”的关键一环,起到了 react hooks里对生命周期、状态管理的核心作用。(如 onMountedref 等等)。

image-20220228194456934

# 命名规范

一个假设: 假设任何以 「use」 开头并紧跟着一个大写字母的函数就是一个 Hook

第一个只在: 只在 React 函数组件中调用 Hook,而不在普通函数中调用 Hook。(Eslint 通过判断一个方法是不是大坨峰命名来判断它是否是 React 函数)

第二个只在: 只在最顶层使用 Hook,而不要在循环,条件或嵌套函数中调用 Hook。

# 作用

拥有 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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

mixins缺点

  1. 弊端一:难以追溯的方法与属性!
  2. 弊端二:覆盖、同名
  3. 弊端三:梅开二度,代价很大!

Hook 的状态复用写法:

// 单个name的写法
const { name, setName } = useName();

// 梅开二度的写法
const { name : firstName, setName : setFirstName } = useName();
const { name : secondName, setName : setSecondName } = useName();
1
2
3
4
5
6

相比于 mixins,它们简直太棒了!

  1. 方法和属性好追溯吗?这可太好了,谁产生的,哪儿来的一目了然。
  2. 会有重名、覆盖问题吗?完全没有!内部的变量在闭包内,返回的变量支持定义别名。
  3. 多次使用,没开N度?你看上面的代码块内不就“梅开三度” 了吗?

就冲 “状态逻辑复用” 这个理由,Hooks 就已经香得我口水直流了。

# 比较实践

# 环境和版本

react 项目中, react 的版本需要高于 16.8.0

而在 vue 项目中, vue3.x 是最好的选择,但 vue2.6+ 配合 @vue/composition-api,也可以开始享受“组合式API”的快乐。

# Hooks 写法

# react

因为 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>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

细节可参考 react 官方网站:react.docschina.org/docs/hooks-… (opens new window)

# vue3

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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

很明显,vue 组合式API里完成 useStateuseMemo 相关工作的 API 并没有通过 useXxx 来命名,而是遵从了 Vue 一脉相承而来的 refcomputed

虽然不符合 react Hook 定义的 Hook 约定,但 vueapi 不按照 react 的约定好像也并没有什么不妥。

参考网址:v3.cn.vuejs.org/api/composi… (opens new window)

# 第三方hooks

除了官方提供的 Hooks Api, Hooks 的另外一个重要特质,就是可以自己进行“自定义 Hooks” 的定义,从而完成状态逻辑的复用。

开源社区也都有很多不错的基于 Hooks 的封装,

比如 ahooks (ahooks.js.org/zh-CN/ (opens new window)),

比如 vueusevueuse.org/ (opens new window)

image-20220228200022919

# 自定义hooks

# react实现

用文章开头那个 useName 的需求为例,希望达到效果:

const { name, setName } = useName();
// 随机生成一个状态属性 name,它有一个随机名作为初始值
// 并且提供了一个可随时更新该值的方法 setName
1
2
3

如果我们要实现上面效果,我们该怎么写代码呢?

import React from 'react';

export const useName = () => {
  // 这个 useMemo 很关键
  const randomName = React.useMemo(() => genRandomName(), []);
  const [ name, setName ] = React.useState(randomName)
  return {
    name,
    setName
  }
}
1
2
3
4
5
6
7
8
9
10
11

# vue实现

vue3 官网没有关于 自定义Hook 的玩法介绍,但实践起来也并不困难。

目标也定位实现一个 useName 方法:

import { ref } from 'vue';

export const useName = () => {
  const name = ref(genRandomName())
  const setName = (v) => {
    name.value = v
  }
  return {
    name,
    setName
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 两种比较

vuereact 自定义 Hook 的异同

  • 相似点: 总体思路是一致的 都遵照着 "定义状态数据","操作状态数据","隐藏细节" 作为核心思路。
  • 差异点: 组合式APIReact函数组件 有着本质差异; vue3 的组件里, setup 是作为一个早于 “created” 的生命周期存在的,无论如何,在一个组件的渲染过程中只会进入一次。 React函数组件 则完全不同,如果没有被 memorized,它们可能会被不停地触发,不停地进入并执行方法,因此需要开销的心智相比于vue其实是更多的。
上次更新: 2022/04/15, 05:41:28
×