ahooks中有许多优秀的hook实践 , 但是它是面向react的用户, 由于作者目前任职的公司是用vue3, 所以就想把useRequest改造成vue3版本来用, 毕竟不想造轮子, 那么它有什么功能呢, 基本涵盖了前端的大多数场景了(useRequest官方文档)
以下是改造过程
首先, react因为流程设计的问题啊, 需要手动监听依赖变化. vue3在这方面呢, 有它的优势性. 所以替换的关键只是在依赖监听, 触发更新这步, 为了迁移效率, 我们遵循个最小变动的原则. 就是不改变原有流程.
以下是改造后的目录结构, 对外只暴露useRequest函数
因为目前只想迁移useRequest, 所以把这块用到的其他hooks都放在它的目录下
useRequestImplement.ts
// 旧
import useCreation from '../../useCreation';
import useLatest from '../../useLatest';
import useMemoizedFn from '../../useMemoizedFn';
import useMount from '../../useMount';
import useUnmount from '../../useUnmount';
import useUpdate from '../../useUpdate';
// 新
import useCreation from '../hooks/useCreation';
import useMemoizedFn from '../hooks/useMemoizedFn';
import {
onMounted,
onBeforeUnmount,
computed,
} from 'vue';
// 旧
const serviceRef = useLatest(service);
const update = useUpdate();
// 新
const serviceRef = service;
const update = () => {};
// useMount 用 onMounted 替换
// useUnmount 用 onBeforeUnmount 替换
// 旧 关键
return {
loading: fetchInstance.state.loading,
data: fetchInstance.state.data,
error: fetchInstance.state.error,
params: fetchInstance.state.params || [],
cancel: useMemoizedFn(fetchInstance.cancel.bind(fetchInstance)),
refresh: useMemoizedFn(fetchInstance.refresh.bind(fetchInstance)),
refreshAsync: useMemoizedFn(fetchInstance.refreshAsync.bind(fetchInstance)),
run: useMemoizedFn(fetchInstance.run.bind(fetchInstance)),
runAsync: useMemoizedFn(fetchInstance.runAsync.bind(fetchInstance)),
mutate: useMemoizedFn(fetchInstance.mutate.bind(fetchInstance)),
} as Result<TData, TParams>;
// 新
const exportRef = computed(() => ({
loading: fetchInstance.state.loading,
data: fetchInstance.state.data,
error: fetchInstance.state.error,
params: fetchInstance.state.params || [],
cancel: useMemoizedFn(fetchInstance.cancel.bind(fetchInstance)),
refresh: useMemoizedFn(fetchInstance.refresh.bind(fetchInstance)),
refreshAsync: useMemoizedFn(fetchInstance.refreshAsync.bind(fetchInstance)),
run: useMemoizedFn(fetchInstance.run.bind(fetchInstance)),
runAsync: useMemoizedFn(fetchInstance.runAsync.bind(fetchInstance)),
mutate: useMemoizedFn(fetchInstance.mutate.bind(fetchInstance)),
}));
return exportRef;
最关键的一步, 就是把对外暴露的对象变为计算属性. 因为react的更新是整个组件重新渲染的. vue3不是这个机制.
useCreation
// 旧
import type { DependencyList } from 'react';
import { useRef } from 'react';
import depsAreSame from '../utils/depsAreSame';
export default function useCreation<T>(factory: () => T, deps: DependencyList) {
const { current } = useRef({
deps,
obj: undefined as undefined | T,
initialized: false,
});
if (current.initialized === false || !depsAreSame(current.deps, deps)) {
current.deps = deps;
current.obj = factory();
current.initialized = true;
}
return current.obj as T;
}
// 新
import { ref } from 'vue';
import depsAreSame from '../../utils/depsAreSame';
export default function useCreation<T>(factory: () => T, deps: []) {
const { value } = ref({
deps,
obj: undefined as undefined | T,
initialized: false,
});
if (value.initialized === false || !depsAreSame(value.deps, deps)) {
value.deps = deps;
value.obj = factory();
value.initialized = true;
}
return value.obj as T;
}
useMemoizedFn
// 旧
import { useMemo, useRef } from 'react';
import { isFunction } from '../utils';
import isDev from '../utils/isDev';
type noop = (this: any, ...args: any[]) => any;
type PickFunction<T extends noop> = (
this: ThisParameterType<T>,
...args: Parameters<T>
) => ReturnType<T>;
function useMemoizedFn<T extends noop>(fn: T) {
if (isDev) {
if (!isFunction(fn)) {
console.error(`useMemoizedFn expected parameter is a function, got ${typeof fn}`);
}
}
const fnRef = useRef<T>(fn);
// why not write `fnRef.current = fn`?
// https://github.com/alibaba/hooks/issues/728
fnRef.current = useMemo(() => fn, [fn]);
const memoizedFn = useRef<PickFunction<T>>();
if (!memoizedFn.current) {
memoizedFn.current = function (this, ...args) {
return fnRef.current.apply(this, args);
};
}
return memoizedFn.current as T;
}
export default useMemoizedFn;
// 新
// import { isFunction } from '../utils';
// import isDev from '../utils/isDev';
import { ref, computed } from 'vue';
type noop = (this: any, ...args: any[]) => any;
type PickFunction<T extends noop> = (
this: ThisParameterType<T>,
...args: Parameters<T>
) => ReturnType<T>;
function useMemoizedFn<T extends noop>(fn: T) {
// if (isDev) {
// if (!isFunction(fn)) {
// console.error(`useMemoizedFn expected parameter is a function, got ${typeof fn}`);
// }
// }
const fnRef = ref<T>(fn);
// why not write `fnRef.current = fn`?
// https://github.com/alibaba/hooks/issues/728
// fnRef.value = computed(() => fn);
const memoizedFn = ref<PickFunction<T>>();
if (!memoizedFn.value) {
memoizedFn.value = function (this, ...args) {
return fnRef.value.apply(this, args);
};
}
return memoizedFn.value as T;
}
export default useMemoizedFn;
到这阶段, 流程已经通了
剩下的就是把插件一个个接入就行了, 原理同上面的改造, 为了方便大家学习交流,已经把改造后的代码, 做了个demo 项目地址
总结
轮子能不造就不造吧, 吃现成的优秀实践多好. 改造一下, 做做语法迁移就好了