前言
本篇文章主要针对
vue3 composition api
react hooks
函数功能形式 or 单一业务功能形式的自定义hooks 封装管用思想做介绍
相信大家也都了解过hooks,函数式编程;
但如何写好一个hooks,能让这个函数更灵活,这是我们开发前 定方案所考虑的事情;
vue3 composition api
我们接下来先看看 vue3 提供的一些常用钩子有哪些;
import { onMounted, ref, onUnmounted, reactive, nextTick, watch, computed } from 'vue';
复制代码
封装vue3 自定义usehooks 借用到这些就可以实现了;
在vue3 中通过ref reactive computed 函数所创建的变量都是 被 proxy 代理过的 ;
我们在封装过程中不需要担心数据经过改变后的更新流程,
在这一点中个人认为是要比react 函数式组件的心智负担要小一点的;
vue3 鼠标监听x,y;
//引入需要使用的Composition API
import { ref, onMounted, onUnmounted } from 'vue'
//实现鼠标追踪器功能
function useMousePosition(){
//初始化x轴和y轴的值
const x = ref(0)
const y = ref(0)
//获取鼠标点击后x轴和y轴的值
const updateMouse = (e: MouseEvent) => {
x.value = e.pageX
y.value = e.pageY
}
//鼠标点击时执行updateMouse函数
onMounted(() => {
document.addEventListener('click', updateMouse)
})
//鼠标点击结束后对当前点击事件执行销毁操作
onUnmounted(() => {
document.removeEventListener('click', updateMouse)
})
//返回x和y的值
return {x, y}
}
//导出函数
export default useMousePosition
复制代码
这是官方的一个demo 例子;
这个代码 我们不难看出是一个鼠标监听 单一事件抽离;
最后返回出来,鼠标位置的 x, y;
其实在这一串代码中,里面具体的事件各种;不需要关心太多太多中间执行的逻辑;
只需要返回出去 让使用者可以进行响应式变化的值即可;
<template>
<div id="app">
<h1>鼠标追踪器</h1>
<h1>X:{{pageX}},Y:{{pageY}}</h1>
</div>
</template>
<script lang="ts">
//引入函数
import useMousePosition from './useMousePosition'
export default{
name: 'App',
setup(){
//引用函数中返回的值
const { x: pageX, y:pageY } = useMousePosition()
//返回值
return{
pageX,
pageY
}
}
};
</script>
复制代码
上方就是在页面中使用的情况, 名字可以通过结构赋值来进行更改;
vue3 useUserList
import { onMounted, ref } from 'vue';
import commonApi from '@/views/project/api/common.api.js';
// 存储用户下拉数据
let userList = null;
/**
* @description: 获取用户下拉列表数据
* @param {boolean} isRefresh 是否再次用户下拉接口
* @returns {{value: Number, label: String}[]}
*/
export async function getUserList(isRefresh) {
if(userList && !isRefresh) {
return userList
} else {
try {
const res = await commonApi.getUserInfoList();
if(res.code === 1000) {
userList = res.data.map((item) => ({value: item.value, label: item.text}));
}
return userList;
} catch(e){
console.log(e)
}
}
}
/**
* @description: 封装公用 函数
* @param {boolean} isRefresh 是否再次用户下拉接口
*/
export function useUserList(isRefresh) {
const list = ref([]);
const loading = ref(true);
const getList = async () => {
list.value = await getUserList(isRefresh);
loading.value = false;
}
onMounted(() => {
getList();
})
return {
list,
loading
}
}
复制代码
上面代码所示,这里有个文件作用域内的变量去存储 接口数据;
在请求函数上包裹判断一下,这样做的目的就是为了;
让第一次请求后的数据缓存起来; 因为与接口缓存相关的,
当然需要提供一个刷新的参数口了;
这样就可以清晰看出来,第一个函数getUserList是取接口数据并且缓存到文件变量内部;
第二个函数useUserList 这就是setup 使用的,然后我们暴露出来list, loading;
就可以满足使用者了;
可以看出这个文件暴露出去两个函数,一个是接口相关的getUserList 最后会返回list,但不是响应式的,
useUserList是响应式list;在这一层可以做一些拦截处理等等;
最后公共接口页面使用的舒服即可;
setup() {
// [
// {value: 1, label: 'zhidl'}
// ]
const { list: userList } = useUserList();
return {
userList,
}
}
复制代码
这是在我们页面使用中;就看起来够清晰了吧, 这只是一个简单的例子;
公用的userList,可以使用此思路封装,单独所有功能函数,封装自己的即可;
最后函数具体放哪里,怎么放;这就是文件管理所考虑的事情拉;
vue3 useReducer
redux 的思想可以简单概括为:
- store维护全局的state数据状态,
- 各个组件可以按需使用state中的数据,并监听state的变化
reducer
接收action并返回新的state,组件可以通过dispatch
传递action触发reducer- state更新后,通知相关依赖更新数据
此时我们想实现一个简单的模型 与react hooks useReducer 使用方式一样:
function reducer(state, action){
// 根据action进行处理
// 返回新的state
}
const initialState = {}
const {state, dispatch} = useReducer(reducer, initialState);
复制代码
借助与 vue的响应式系统,不需要实现任何发布 订阅模式:
// index.js
import {ref} from 'vue'
export default function useReducer(reducer, initialState = {}) {
const state = ref(initialState)
// 约定action格式为 {type:string, payload: any}
const dispatch = (action) => {
state.value = reducer(state.value, action)
}
return {
state,
dispatch
}
}
// 实现一个useRedux 负责传递reducer 和 action
import useReducer from './index'
function reducer(state, action) {
switch (action.type) {
case "reset":
return initialState;
case "increment":
return {count: state.count + 1};
case "decrement":
return {count: state.count - 1};
}
}
function useStore() {
return useReducer(reducer, initialState);
}
复制代码
组件中的使用
<template>
<div>
<button @click="dispatch({type:'decrement'})">-</button>
<span>{{ state.count }}</span>
<button @click="dispatch({type:'increment'})">+</button>
</div>
</template>
<script>
export default {
name: "useReducer",
setup() {
const {state, dispatch} = useStore()
return {
state,
dispatch
}
}
}
</script>
复制代码
这个vue 版本的redux 是比较简陋的。包括中间件、combineReducers、connect
等方法均为实现
本片文章主要介绍的是 vue hooks封装,在此就不多做讲述了;
react hooks
import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
复制代码
react 的hooks套路与上面大概一样,如果写惯了vue的同学,需要注意的是react 函数式渲染 每次是从上往下执行的机制;
所以 往往会觉得有心智负担,产出闭包,副作用依赖,等等问题;
关键还是在于 对 react 提供的基础 hooks的理解,
首先我们要理解的是react 组件更新;
单向数据流的原因, 更新方式主要分为一下两种;
被动更新 props的变化比较; 主动更新手动触发 setState 这两种方式;
这时候就会有疑问:那我的redux context更新呢;
其实按我理解来说;它也是属于更改props传递值到组件上的,所以引起组件的diff,以及后续的更新dom
通常 我们创建的数据变量是 useState useRef 这两种方式;
包装 引用类型,函数的是 useMemo, useCallback,
他们主要是避免
{} === {} // false
(() => {}) === (() => {}) // false
复制代码
也可以称之为 继续使用上一次更新闭包所产生的对象 或 函数,
useEffect 是副作用依赖执行函数,
useEffect(fn, []);
复制代码
根据第二参数的依赖性的变化而执行;
对于这些钩子的简单介绍就到这里,要想了解更深刻,还是需要在实际使用过程中去感悟;
接下来我们来看看一些自定义react hooks的逻辑封装吧;
react useLockFn 重复提交
import { useRef, useCallback } from 'react';
function useLockFn<P extends any[] = any[], V extends any = any>(fn: (...args: P) => Promise<V>) {
const lockRef = useRef(false);
return useCallback(
async (...args: P) => {
if (lockRef.current) return;
lockRef.current = true;
try {
const ret = await fn(...args);
lockRef.current = false;
return ret;
} catch (e) {
lockRef.current = false;
throw e;
}
},
[fn],
);
}
export default useLockFn;
复制代码
这是一个重复提交的自定义hooks封装;
可以看到在该函数中借用useRef 变量加锁;
最后返回出去一个函数的执行;该函数可以看到使用 useCallback包装的
这样包装有个好处就是,本身的执行就是依赖于fn的变化,如果fn是静态函数,就不需要重复的创建新的执行函数;
内部执行就是借用到了 async await,在await上一次完全执行后从中间 设置状态的;
import React, { useState } from 'react';
import { message } from 'antd';
import { useLockFn } from 'ahooks';
function mockApiRequest() {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 2000);
});
}
export default () => {
const [count, setCount] = useState(0);
const submit = useLockFn(async () => {
message.info('Start to submit');
await mockApiRequest();
setCount((val) => val + 1);
message.success('Submit finished');
});
return (
<>
<p>Submit count: {count}</p>
<button onClick={submit}>Submit</button>
</>
);
};
复制代码
通常我们用到判断重复提交的时候都是在组件中,创建自己的变量来控制;
通过这样一系列包装,就相当于把该变量踢出到自己封装的useLockFn中了
react useUserList
/*
* @author: zhidl
* @Date: 2021-08-26 16:10:14
* @description:
* @LastEditTime: 2021-08-26 16:10:15
* @LastEditors: zhidl
*/
import { useState, useEffect } from 'react';
// 存储用户下拉数据
let userList = null;
export async function getUserList(isRefresh) {
if(userList && !isRefresh) {
return userList
} else {
try {
const res = await axios.post('***/user-list', {});
if(res.code === 1000) {
userList = res.data.map((item) => ({value: item.value, label: item.text}));
}
return userList;
} catch(e){
console.log(e)
}
}
}
export function useUserList(isRefresh) {
const [list, setList] = useState([]);
const [loading, setLoading] = useState(true);
const getList = async () => {
const apiUserList = await getUserList(isRefresh);
// 在这里可以对接口的一些数据之类的进行劫持处理
setList(apiUserList);
setLoading(false);
}
useEffect(() => {
getList();
}, [])
return {
list,
loading
}
}
复制代码
可以看到这个封装其实跟vue差不多的; 也是同样的把接口list做了一层拦截;
页面使用情况也大体相同在此不多做赘述;
总结
自定义hooks本质 就是利用vue or react 提供的基础hooks来封装一些功能性,或者切合项目业务形式的函数
且合函数式编程,单功能或者单业务节藕的方式;
让一些功能,或者重复性的代码块进行良好的抽离;
在组件中可快速复用相同块的代码
具体怎么抽离合理,还需多深入了解函数编程理念
路漫漫其修远兮,吾将上下而求索;
感谢观看,有见解不对的地方,望大佬指正;
一款优秀的开源插件react hooks:a-hooks