今天这期我们从工程化的角度审视下ahooks的源码项目,下期开始,我们读下state相关的hook,读源码,学思想,练实战
为什么要写源码库的工程化
我个人出于团队技术栈的研究深度问题,经常会去研究下React生态中的组件库,框架的源码,但是碰到了很多问题,比较常见的就是工程化,编译打包工具五花八门,一通npm i&&npm run devx
下来,没几个项目是能正常跑的,要么依赖冲突,要么就是各种花式报错
还有些质量非常高的库,比如React本身,Ant-Design,Ag-grid等等都非常复杂,React源码类型检查是Flow,打开scripts
目录下一大堆,对于大家的学习还是造成了不小的困扰。
像Ant-Design也有多种类型的打包工具和产物,esm,umd,cjs等等,近些年也出现了很多类似lerna
等monorepo方案。有些库的源码项目你会看到多个时代工程化技术混合使用的样子。
于是乎,我一开始在做技术沉淀的时候如何选择工程化工具上是有点懵的,在看ahooks源码的时候发现了dumi
,它很好地提供了一站式地解决很多问题
项目脚手架-dumi
- 更好的编译性能
- 内置全文搜索
- 全新主题系统
- 约定式路由增强
- 资产元数据 2.0
- 继续为组件研发而生
这个是dumi2.x官网给大家的介绍,关于如何去使用,大家可以去看下,总体而言,dumi能够带给我们的东西可以总结为以下几个方面
- 内建的完整编译工具,father可以解决源码构建,dumi负责组件开发和文档生成
- 文档,demo,测试,静态站点解决方案
- 丰富的配置项和灵活的插件化方案
- 文档国际化
这些都治好了我的选择恐惧症。虽然现在ahook还是使用的dumi 1.x的版本,但是不妨碍我们去理解和使用它
ahook源码项目结构
为了方便后续学习和修改部分源码,我们可以选择fork到自己的仓库
我们来对照分析下主要的目录的作用
config
站点的配置项,站点目录等
docs
站点主页和总体的文档页面,比如指南和里面blog等
example
提供了两个完整的demo,basic是基于umi 3.x的,taro是跨端框架下的使用的demo
packages
包含两个源码包,hooks和use-url-state,前者是我们学习的重点,后者主要用来将状态记录到url参数中,将来细聊
我们整个系列整体要盘的就是hooks这个目录中的每一个hook啦
每个hook的源码目录都相对整齐,我们单独举个例子
源码文件
index.ts或者index.tsx,核心代码逻辑
ts
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;
使用文档
index.zh-CN.md和index.en-US.md是对应hook使用文档
tests
ts
import { act, renderHook } from '@testing-library/react';
import { useState } from 'react';
import useMemoizedFn from '../';
const useCount = () => {
const [count, setCount] = useState(0);
const addCount = () => {
setCount((c) => c + 1);
};
const memoizedFn = useMemoizedFn(() => count);
return { addCount, memoizedFn };
};
let hook;
describe('useMemoizedFn', () => {
it('useMemoizedFn should work', () => {
act(() => {
hook = renderHook(() => useCount());
});
const currentFn = hook.result.current.memoizedFn;
expect(hook.result.current.memoizedFn()).toBe(0);
act(() => {
hook.result.current.addCount();
});
expect(currentFn).toEqual(hook.result.current.memoizedFn);
expect(hook.result.current.memoizedFn()).toBe(1);
});
// it('should output error when fn is not a function', () => {
// const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
// renderHook(() => useMemoizedFn(1 as any));
// expect(errSpy).toBeCalledWith('useMemoizedFn expected parameter is a function, got number');
// errSpy.mockRestore();
// });
});
对应的单元测试代码
demo
使用说明的md文件对应的demo源码,在md文件中使用进行关联
跑起来
安装依赖
bash
pnpm i
// 如果下载有问题的话,可以尝试npm config set registry https://registry.npm.taobao.org
文档站点
arduino
pnpm run dev
// 如果出现ERR_OSSL_EVP_UNSUPPORTED错误的话
// export NODE_OPTIONS=--openssl-legacy-provider
测试
bash
pnpm run test
pnpm run coveralls //利用coveralls模块将本地生成的lcov数据发送到coveralls.io平台,在这个平台上可以展示你的测试覆盖率
编译
bash
pnpm run build
编译报错了哈哈,看样子的话是类型重载和匹配的问题,我们暂时忽略
总结
我们本次的重点是通过ahook源码仓库和dumi的使用,了解源码仓库所要包含的工程化工具,直观地去了解和学习,ahook的工程化和源码结构是怎样的,我觉得这个实战步骤对于学习源码相当有意义的。只是看代码,容易理解不够深入,fork源码项目,看,读,写,调才能更加深刻,并融入到自己的日常工作中去。当然dumi本身作为组件工程化的一体化工具,本身也包罗万象,由各种底层的工具在支撑,值得深挖的。
下期预告
ahooks中State相关的hook及源码实现。