vue3 & react 封装自定义useHooks

前言


本篇文章主要针对
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是取接口数据并且缓存到文件变量内部;
(切记一般这个变量,不可以导出,只可以通过导出函数取到变量,不然容易混用)\color{red}{(切记一般这个变量,不可以导出,只可以通过导出函数取到变量,不然容易混用)}
第二个函数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

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享