1、 起源背景
起源于 React,在 Hooks 出现之前,函数组件对比类组件(class)形式有很多局限,例如:
1. 不能使用 state、ref 等属性,只能通过函数传参的方式使用 props
2. 没有生命周期钩子
3. 监听清理和资源释放问题
4. 组件间逻辑复用困难
当组件要销毁时,很多情况下都需要清除注册的监听事件、释放申请的资源。
事件监听、资源申请需要在 Mount 钩子中申请,当组件销毁时还必须在 Unmount 勾子中进行清理,这样写使得同一资源的生成和销毁逻辑不在一起,因为生命周期被迫划分成两个部分。
Vue 里面,在setup里面使用的响应式是有EffectScope包裹的会自动处理,所以并不需要手动处理,但在setup外面使用的响应式需要手动清理。
组件涵盖了太多的功能,导致暴露的能力接口过于复杂,与本身钻进绑定的逻辑太多,所以我们要细化组件的颗粒度,但是在这个过程中不能以牺牲代码的可读性和可维护性为代价。
2、 是什么
计算机行业概念:系统运行到某一时期时,会调用被注册到该时机的回调函数。
react: 一系列方法,提供了在函数式组件中完成开发工作的能力。
vue: 在 vue 组合式API里,以 “use” 作为开头的,一系列提供了组件复用、状态管理等开发能力的方法。
综合理解:
1、 是函数,不是新技术,是在前端组件化开发的场景下演变出来的新的编程理念:OOP ==》FP,因为hooks是以函数的形式展示,所以我们编写函数的相关规范可以套进写hooks中;
2、 是逻辑的内聚,我们不一定要以复用为目的去抽象hooks,它也可以是一种逻辑的内聚,单纯的为了我们代码的提高可读性,可维护性,它把原来分离在各个生命周期中的逻辑整合在一起了;
3、 从组件颗粒度的角度来看,可以是小小的一个逻辑,也可以是多个小逻辑的组合;
4、 从组件逻辑抽象的角度来看,把通用的东西抽象出来然后复用;
5、 没有囊括生命周期钩子和状态的hooks,其实更像是我们以往编程中分离的函数,换汤不换药。
2、命名和规范
一般来说,hooks都是以 use 开头的。
套用 react 官方文档的解释:一个假设,两个只在
zh-hans.reactjs.org/docs/hooks-…
一个假设:假设任何以use开头的并一个大写字母的函数就是一个hook
第一个只在: 只在 React 函数组件中调用 Hook,而不在普通函数中调用 Hook。(区分普通函数方法与Hooks)
第二个只在: 只在最顶层使用 Hook,而不要在循环,条件或嵌套函数中调用 Hook。
3、优点
1、 没有this
2、 让函数拥有生命周期钩子和自己的状态
3、 代码高度聚合,具有高可读性和高可维护性
4、 逻辑复用,属性和方法容易追溯,没重名问题,由于闭包可以多次复用
5、 比mixin、HOC更轻量,改造成本更小
6、 状态复用,组件状态的粒度变得更小,组件中的状态和 UI 变得更为清晰和隔离
7、 灵活性,Hooks 中可以引用另外的 Hooks形成新的Hooks,组合变化万千
8、 渐进式,可以慢慢抽离逻辑和状态,不一定需要全量修改
4、缺点
1、 只能放在顶层,不能放在回调中、循环、条件中(严格来看这不是缺点,因为本来的约定就是这样,放在回调、循环里面的是普通方法而不属于hooks)
2、 在闭包场景可能会引用到旧的状态值(严格来看这不是缺点,因为优秀的hooks是不会再顶层设置变量的,所以不会存在这个问题)
5、Vue 与 React 之间 Hooks的异同点
1、相似点: 总体思路是一致的 都遵照着 "定义状态数据","操作状态数据","隐藏细节" 作为核心思路。
2、差异点:
a. vue3 的组件里, setup 是作为一个早于 “created” 的生命周期存在的,无论如何,在一个组件的渲染过程中只会进入一次。
b. React 函数组件 则完全不同,如果没有被 memorized,它们可能会被不停地触发,不停地进入并执行方法。
c. React的hooks具有完整的生命周期,可以是一个很完善的程序,而vue并不能很好做到这一样,因为vue并没有完全独立的上下文。
d. react:只能在“函数组件”内使用hooks,vue:只能在“setup”周期内使用hooks
6、实际应用
1、 赋能组件:编写无头组件
组件的组成部分(基础术语)
视图:css和html、react的jsx或者vue的template代码 ==> 展示视图
交互:生命周期,按钮交互,事件,回调 ==》 实现需求
业务:登录注册,获取数据等与组件无关的业务抽象 ==》 基础功能
拆分原则
高内聚:组件代码只跟组件功能相关
低耦合:不与外部组件产生过多交互
代码位置
a. 只被页面内的组件复用,就放到页面文件夹下
b. 只在当前业务场景下的不同页面复用,就放到当前业务模块的文件夹下
c. 可以在不同业务场景间通用,就放到最顶层的公共文件夹,或者考虑做成组件库
怎么做
复杂业务:业务逻辑hooks、交互逻辑hooks、组件暴露数据,方法到视图
简单业务:业务逻辑hooks、组件中写交互逻辑、组件暴露数据,方法到视图
复杂业务示例:
业务逻辑:
const useUser = () => {
// 用户状态
const userInfo = ref({});
// 获取用户状态
const getUserInfo = () => {}
// 修改用户状态
const changeUserInfo = () => {};
// 检查两次输入的密码是否相同
const checkRepeatPass = (oldPass,newPass) => {}
// 修改密码
const changePassword = () => {};
return {
userInfo,
getUserInfo,
changeUserInfo,
checkRepeatPass,
changePassword,
}
}
交互逻辑:
const useUserControl = () => {
// 组合用户业务逻辑hook
const { userInfo, getUserInfo, changeUserInfo, checkRepeatPass, changePassword } = useUser();
// loading状态
const loading = ref(false);
// 弹窗状态
const errorModalState = reactive({
visible: false,
errorText: '',
});
// 初始化数据
const initData = () => {
getUserInfo();
}
// 修改密码并提交表单
const onChangePassword = ({ oldPass, newPass ) => {
// 判断两次密码是否一致
if (checkRepeatPass(oldPass, newPass)) {
changePassword();
} else {
errorModalState.visible = true;
errorModalState.text = '两次输入的密码不一致,请修改'
}
};
return {
// 用户数据
userInfo,
// 初始化数据
initData: getUserInfo,
// 修改密码
onChangePassword,
// 修改用户信息
onChangeUserInfo: changeUserInfo,
}
}
组件使用:
<template>
</template>
<script setup>
import useUserControl from './useUserControl';
import { onMounted } from 'vue';
const { userInfo, initData, onChangePassword, onChangeUserInfo } = useUserControl();
onMounted(initData);
<script>
简单业务示例:
业务逻辑:同上
组件使用:
<template>
</template>
<script setup>
import { onMounted } from 'vue';
// 相当于交互逻辑hook
const useUser = () => {
// 代码省略
}
const { userInfo, getUserInfo, changeUserInfo, checkRepeatPass, changePassword } = useUser();
// loading状态
const loading = ref(false);
// 弹窗状态
const errorModalState = reactive({
visible: false,
errorText: '',
});
// 初始化数据
const initData = () => { getUserInfo(); }
// 修改密码表单提交
const onChangePassword = ({ oldPass, newPass ) => {};
onMounted(initData);
<script>
2、自定义hooks:逻辑状态复用
import { ref, watch } from 'vue';
const useAdd= ({ num1, num2 }) =>{
const addNum = ref(0)
watch([num1, num2], ([num1, num2]) => {
addFn(num1, num2)
})
const addFn = (num1, num2) => {
addNum.value = num1 + num2
}
return {
addNum,
addFn
}
}
export default useAdd
参考:开源hooks库:VueUse、ahooks
3、(重要)收束逻辑----faced模式:
如果有状态和生命周期钩子就是自定义hooks,纯逻辑运算的就是个函数(就是上面提到的无头组件的JS部分)
faced模式(外观模式):提供一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层的接口,让子系统更容易使用。
1、定义一个用户Hooks ==》 useUser
const useUser = () => {};
2、user 模块可以包含我们在开发中经常遇到的需求:
登录、登出、获取个人信息
const useUser = () => {
const useLogin = () => {};
const useLogout = () => {};
const useInfo = () => {};
export {
useLogin,
useLogout,
useInfo
};
};
8、总结
现在来看,hooks意义最要是体现在它可以内聚功能逻辑以及抽离逻辑上面。
9、参考:
【Web技术】1445- 如何使用 Hooks 写出高质量的 React 和 Vue 组件?
在 Vue3 中实现 React 原生 Hooks(useState、useEffect),深入理解 React Hooks原理