作为一个合格点的不算太牛逼的前端工程师🦁,你得无时无刻都在注意技术方向的势头,而hooks
将是作为装逼大师的你必须掌握的技能点,这也是几大前沿框架发展的一大核心势头,本文着重讲讲React和Vue中hooks的前景及用法,废话不多说直接来正文📢:
一、什么是 hooks
?
1.1 vue 的定义
2019年6月,尤雨溪在 vue/github-issues 里提出了关于 vue3 Component API
的提案。(vue hooks的基础)
hook是钩子的意思,看到“钩子”是不是就想到了钩子函数?事实上,hooks 还真是函数的一种写法。
vue3 借鉴 react hooks 开发出了 Composition API ,所以也就意味着 Composition API 也能进行自定义封装 hooks。
vue3 中的 hooks 就是函数的一种写法,就是将文件的一些单独功能的js代码进行抽离出来,放到单独的js文件中,或者说是一些可以复用的公共方法/功能。其实 hooks 和 vue2 中的 mixin 有点类似,但是相对 mixins 而言, hooks 更清楚复用功能代码的来源, 更清晰易懂。
1.2 react 的定义
Hook 是 React 16.8 的新增特性。
Hooks本质上就是一类特殊的函数,它们可以为你的函数型组件(function component)注入一些特殊的功能,让您在不编写类的情况下使用 state(状态) 和其他 React 特性。
二、 为什么hooks是大势所趋?
从以上可以看到,React 和 Vue3 中都已经相继引入,同时也被越来越多的人接受和广泛使用
除此之外, solid.js
、 preact
等框架,也是开始选择加入 hooks
思想,由此可见hooks将是未来几年行业的热门思想和话题。
三、 命名规范 和 指导思想
hooks
的命名都是以 use
作为开头,这个规范也包括了那么我们自定义的 hooks
。
为什么?
因为约定。
在 react
官方文档里,对 hooks
的定义和使用提出了 “一个假设、两个只在” 核心指导思想。
一个假设: 假设任何以 「use
」 开头并紧跟着一个大写字母的函数就是一个 Hook
。
第一个只在: 只在 React
函数组件中调用 Hook
,而不在普通函数中调用 Hook
。(Eslint
通过判断一个方法是不是大坨峰命名来判断它是否是 React
函数)
第二个只在: 只在最顶层使用 Hook
,而不要在循环,条件或嵌套函数中调用 Hook。
因为是约定,因而 useXxx
的命名并非强制,你依然可以将你自定义的 hook
命名为 byXxx
或其他方式,但不推荐。
因为约定的力量在于:我们不用细看实现,也能通过命名来了解一个它是什么。
以上 “一个假设、两个只在” 总结自 react
官网:
-
zh-hans.reactjs.org/docs/hooks-…
-
zh-hans.reactjs.org/docs/hooks-…
以上这段摘自“摸鱼的春哥”在【浅谈:为啥vue和react都选择了Hooks🏂】中的介绍,说的非常形象也易懂,可以建议大家读读这篇文章。
四、hooks
存在的场景和解决哪些难题呢?
4.1 vue已经在用的组合api
import { useSlots, useAttrs } from 'vue';
import { useRouter } from 'vue-router';
// 以上这些方法,也是 vue3 中相关的 Hook!
这些useSlots,useAttrs,useRouter内置的类有没有看起来很眼熟,前面说过定义是什么来着,use+大写字母开头的驼峰,没错,是它,它就是hooks。
已经由原来的Vue和Router类细化为具体的hooks函数。从而提供了组件复用、状态管理等开发能力的方法。
如何玩hooks,这儿来个自定义🌰:
干什么事之前呢,我们经常说要搞清楚前景,不然两眼一抹黑🙂!
场景:封住一个可复用的共用事件集合hooks,类名为 useAddEvent:
vue 的 Hooks
写法依赖于 组合式API
,所以依赖 <script setup>或者defineComponent的setUp类;
// src/hooks/useEvent.ts
const useAddEvent = function({element, type, callback}) {
if (element.addEventListener) { // 支持使用 addEventListener()
if (type.slice(0,2) === "on") // 以 "on" 开头,不需要,则去掉
type = type.slice(2);
element.addEventListener(type, callback);
} else if (element.attachEvent) { // 支持使用 attachEvent()
if (type.slice(0, 2) !== "on") // 没有以 "on" 开头,需要,则加上
type = "on" + type;
element.attachEvent(type, callback);
}else{
type.slice(0, 2) !== "on" ? element['on'+ type] = callback : element[type] = callback;
}
}
export default useAddEvent;
hook文件的使用:在需要用到该hook
功能的组件中的使用,在 test.vue文件中调用如下:
// src/views/test.vue
<template>
<div ref="scroll">
</div>
</template>
<script lang="ts">
import { defineComponent} from 'vue'
// 引入hooks
import useAddEvent from '@/hooks/useAddEvent'
export default defineComponent({
setup () {
// 使用hooks功能
useAddEvent({
element: this.$refs.scroll,
type: 'scroll', // 触发事件类型,支持on类型
callback: debounce(buriedPoint, 1000){...} // 节流函数处理
});
}
})
</script>
or
// src/views/test.vue
<template>
<div ref="scroll">
</div>
</template>
<script setup lang="ts">
// import { computed, ref } from 'vue'
// 引入hooks
import useAddEvent from '@/hooks/useAddEvent'
// 使用hooks功能
useAddEvent({
element: this.$refs.scroll,
type: 'scroll', // 触发事件类型,支持on类型
callback: debounce(buriedPoint, 1000){...} // 节流函数处理
});
</script>
4.1 react开发时不得不遇到的问题
状态逻辑难以复用: 业务变得复杂之后,组件之间共享状态变得频繁,组件复用和状态逻辑管理就变得十分复杂。使用redux也会加大项目的复杂度和体积。
组成复杂难以维护: 复杂的组件中有各种难以管理的状态和副作用,在同一个生命周期中你可能会因为不同情况写出各种不相关的逻辑,但实际上我们通常希望一个函数只做一件事情。
类的this指向性问题: 我们用class来创建react组件时,为了保证this的指向正确,我们要经常写这样的代码:const that = this
,或者是this.handleClick = this.handleClick.bind(this)>
;一旦this使用错误,各种bug就随之而来。
为了解决这些麻烦,hooks 允许我们使用简单的特殊函数实现class的各种功能。
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>
}
useState
在 React 组件中,我们经常要使用 state 来进行数据的实时响应,根据 state 的变化重新渲染组件更新视图。
因为纯函数不能有状态,在 hooks 中,useState
就是一个用于为函数组件引入状态(state)的状态钩子。
const [state, setState] = useState(initialState);
useState
的唯一参数是状态初始值(initial state),它返回了一个数组,这个数组的第[0]项是当前当前的状态值,第[1]项是可以改变状态值的方法函数。
useEffect
useEffect(create, deps);
useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。该 Hook 接收一个函数,该函数会在组件渲染到屏幕之后才执行。
和 react 类的生命周期相比,useEffect Hook 可以当做 componentDidMount,componentDidUpdate 和 componentWillUnmount 的组合。默认情况下,react 首次渲染和之后的每次渲染都会调用一遍传给 useEffect 的函数。
useEffect 的性能问题
因为 React 首次渲染和之后的每次渲染都会调用一遍传给 useEffect 的函数,所以大多数情况下很有可能会产生性能问题。
为了解决这个问题,可以将数组作为可选的第二个参数传递给 useEffect。数组中可选择性写 state 中的数据,代表只有当数组中的 state 发生变化是才执行函数内的语句,以此可以使用多个useEffect
分离函数关注点。如果是个空数组,代表只执行一次,类似于 componentDidUpdata。
解绑副作用
在 React 类中,经常会需要在组件卸载时做一些事情,例如移除监听事件等。 在 class 组件中,我们可以在 componentWillUnmount 这个生命周期中做这些事情,而在 hooks 中,我们可以通过 useEffect 第一个函数中 return 一个函数来实现相同效果。以下是一个简单的清除定时器例子:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(count => count + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
return (
<>
Count: {count}
</>
);
}
五、说说vue
和 react
自定义hook
的异同
-
相似点:总体思路是一致的 都遵照着 "定义状态数据","操作状态数据","隐藏细节" 作为核心思路。
-
差异点:
组合式
API
和React
函数组件
有着本质差异vue3
的组件里,setup
是作为一个早于 “created” 的生命周期存在的,无论如何,在一个组件的渲染过程中只会进入一次。React
函数组件
则完全不同,如果没有被memorized
,它们可能会被不停地触发,不停地进入并执行方法,因此需要开销的心智相比于vue
其实是更多的。