Skip to content

今天这期我们从工程化的角度审视下ahooks的源码项目,下期开始,我们读下state相关的hook,读源码,学思想,练实战

为什么要写源码库的工程化

我个人出于团队技术栈的研究深度问题,经常会去研究下React生态中的组件库,框架的源码,但是碰到了很多问题,比较常见的就是工程化,编译打包工具五花八门,一通npm i&&npm run devx下来,没几个项目是能正常跑的,要么依赖冲突,要么就是各种花式报错

还有些质量非常高的库,比如React本身,Ant-Design,Ag-grid等等都非常复杂,React源码类型检查是Flow,打开scripts目录下一大堆,对于大家的学习还是造成了不小的困扰。image-20231209123441756

像Ant-Design也有多种类型的打包工具和产物,esm,umd,cjs等等,近些年也出现了很多类似lerna等monorepo方案。有些库的源码项目你会看到多个时代工程化技术混合使用的样子。

于是乎,我一开始在做技术沉淀的时候如何选择工程化工具上是有点懵的,在看ahooks源码的时候发现了dumi,它很好地提供了一站式地解决很多问题

项目脚手架-dumi

image-20231209124358618

  • 更好的编译性能
  • 内置全文搜索
  • 全新主题系统
  • 约定式路由增强
  • 资产元数据 2.0
  • 继续为组件研发而生

这个是dumi2.x官网给大家的介绍,关于如何去使用,大家可以去看下,总体而言,dumi能够带给我们的东西可以总结为以下几个方面

  • 内建的完整编译工具,father可以解决源码构建,dumi负责组件开发和文档生成
  • 文档,demo,测试,静态站点解决方案
  • 丰富的配置项和灵活的插件化方案
  • 文档国际化

这些都治好了我的选择恐惧症。虽然现在ahook还是使用的dumi 1.x的版本,但是不妨碍我们去理解和使用它

ahook源码项目结构

为了方便后续学习和修改部分源码,我们可以选择fork到自己的仓库

image-20231209125903558

我们来对照分析下主要的目录的作用

config

站点的配置项,站点目录等

docs

站点主页和总体的文档页面,比如指南和里面blog等

example

提供了两个完整的demo,basic是基于umi 3.x的,taro是跨端框架下的使用的demo

packages

包含两个源码包,hooks和use-url-state,前者是我们学习的重点,后者主要用来将状态记录到url参数中,将来细聊

image-20231209131204246

我们整个系列整体要盘的就是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使用文档

image-20231209140501280

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

文档站点

pnpm run dev
// 如果出现ERR_OSSL_EVP_UNSUPPORTED错误的话
// export NODE_OPTIONS=--openssl-legacy-provider

image-20231209144937764

image-20231209144955138

测试

bash
pnpm run test
pnpm run coveralls //利用coveralls模块将本地生成的lcov数据发送到coveralls.io平台,在这个平台上可以展示你的测试覆盖率

image-20231209145103787

image-20231209145235173

编译

bash
pnpm run build

image-20231209145728915

编译报错了哈哈,看样子的话是类型重载和匹配的问题,我们暂时忽略

总结

我们本次的重点是通过ahook源码仓库和dumi的使用,了解源码仓库所要包含的工程化工具,直观地去了解和学习,ahook的工程化和源码结构是怎样的,我觉得这个实战步骤对于学习源码相当有意义的。只是看代码,容易理解不够深入,fork源码项目,看,读,写,调才能更加深刻,并融入到自己的日常工作中去。当然dumi本身作为组件工程化的一体化工具,本身也包罗万象,由各种底层的工具在支撑,值得深挖的。

下期预告

ahooks中State相关的hook及源码实现。

关注我,敬请期待