深入解读ahooks
本文适合对打造工具函数库感兴趣的小伙伴阅读。
欢迎关注前端早茶,与广东靓仔携手共同进阶~
作者:广东靓仔
一、前言
本文基于开源项目:
https://github.com/alibaba/hooks
https://ahooks.js.org/guide/
https://github.com/lerna/lerna
广东靓仔在看ahooks项目代码的时候发现了一些有意思的东西,这里分享下。
二、ahooks是什么
易学易用 支持 SSR 对输入输出函数做了特殊处理,且避免闭包问题 包含大量提炼自业务的高级 Hooks
包含丰富的基础 Hooks
使用 TypeScript 构建,提供完整的类型定义文件
$ npm install --save ahooks
# or
$ yarn add ahooks
使用:
import { useRequest } from 'ahooks';
三、项目目录
看到这个目录,有种亲切的感觉,经过粗略的查看,跟dumi脚手架有点相似,经过求证,确定是使用dumi搭建的。
目录讲解:
+-- config // 项目配置
+-- docs // 组件库文档目录
| +-- guide // 组件库文档其他路由
| +-- index.en-US.md // 组件库文档首页(英文)
| +-- index.zh-CN.md // 组件库文档首页(中文)
+-- packages // lerna包
| +-- hooks // 子包hooks
| +-- use-url-state // 子包use-url-state
+-- webpack.common.js // webpack配置文件
+-- gulpfile.js // 自动化构建工具配置
+-- lerna.json // 把各个小功能拆分成独立的npm库
看到这里我们先来温习下dumi相关知识。
四、dumi
dumi是为组件开发场景而生的文档工具。
dumi经常与father搭配使用,father 负责构建,而 dumi 负责组件开发及组件文档生成。
安装使用:
$ npx @umijs/create-dumi-lib # 初始化一个文档模式的组件库开发脚手架
# or
$ yarn create @umijs/dumi-lib
$ npx @umijs/create-dumi-lib --site # 初始化一个站点模式的组件库开发脚手架
# or
$ yarn create @umijs/dumi-lib --site
效果如下:
篇幅有限,这里稍微提下:
en-US
是默认语言,如果需要中文创建一个带 zh-CN
locale 后缀的同名 Markdown 文件即可。更多的dumi相关知识可以前往官方文档查看:
https://d.umijs.org/guide
五、hooks
回到ahooks中,从项目目录中可以看到hooks文件是整个工具库的核心
+-- hooks
| +-- src // 子包hooks的源码目录
| +-- gulpfile.js // 自动化构建配置
| +-- package.json
| +-- tsconfig.json // ts配置
| +-- webpack.config.js // webpack配置
| +-- yarn.lock // 锁定版本
在查看源代码的过程中,广东靓仔看到了lodash的影子,推荐一下lodash。
下面我们来看看这个hooks具体内容。
tsconfig.json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "src"
}
}
利用extends属性从根目录的tsconfig.json配置文件里继承配置。
compilerOptions指定src文件目录(用于输出),用于控制输出目录结构
webpack.config.js
const merge = require('webpack-merge');
const common = require('../../webpack.common.js');
const path = require('path');
module.exports = merge(common, {
entry: './es/index.js', // 入口模块的文件相对路径
output: { // 输出到的目录
filename: 'ahooks.js',
library: 'ahooks',
path: path.resolve(__dirname, './dist'),
},
});
webpack.common.js 公共配置文件 -- 抽离出公共的部分 通过merge进行合并
代码裁剪下,方便理解:
module.exports = merge(common, config);
webpack.common.js
里面没有什么特殊的,打包的时候,ahooks不想把react打到bundle中,所以使用了如下配置:
externals: [
{
react: 'React',
},
],
gulpfile.js
const commonConfig = require('../../gulpfile');
exports.default = commonConfig.default;
ahooks使用了gulp自动化构建工具增强工作流程
gulpfile配置
const gulp = require('gulp');
const babel = require('gulp-babel');
const ts = require('gulp-typescript');
const del = require('del');
gulp.task('clean', async function () {
...
});
gulp.task('cjs', function () {
...
});
gulp.task('es', function () {
...
});
gulp.task('declaration', function () {
...
});
gulp.task('copyReadme', async function () {
...
});
exports.default = gulp.series('clean', 'cjs', 'es', 'declaration', 'copyReadme');
可以看到使用了gulp的series()将clean、cjs、es、declaration、copyReadme组合成更大的操作,然后依次执行
clean里面调用了del,类似于rimraf删除当前工作目录
cjs使用gulp-typescript编译 TypeScript 文件流
declaration生成相应的 .d.ts 文件
copyReadme创建一个用于将 hooks文件的元数据对象写入到文件系统的流
lerna.json
ahooks使用了lerna集中了子包在同一个站点中。
Lerna 是一个工具,可以优化使用 git 和 npm 管理多包存储库的工作流程。
广东靓仔把package.json里面关于lerna剪切了下来,如下:
"scripts": {
"bootstrap": "lerna bootstrap",
"clean": "lerna clean --yes",
"build": "lerna run build",
"pub": "yarn run build && lerna publish",
"pub:beta": "yarn run build && lerna publish --dist-tag beta"
},
Lerna 中的两个主要命令是lerna bootstrap和lerna publish。
bootstrap将 repo 中的依赖项链接在一起。publish将帮助发布任何更新的包。
ahooks的lerna配置如下:
{
"version": "3.1.9",
"packages": ["packages/*"],
"npmClient": "yarn",
"command": {
"version": {
"allowBranch": "master",
"includeMergedTags": true
},
"publish": {
"message": "chore(release): publish",
"registry": "https://registry.npmjs.org/"
}
}
}
version
:存储库的当前版本。packages: 用作包位置的 glob 数组。
npmClient
:用于指定特定客户端以运行命令的选项(也可以在每个命令的基础上指定)。更改为"yarn"
使用 yarn 运行所有命令。默认为“npm”。command.version.allowBranch: lerna version当从除master. 仅限制lerna version在主分支被认为是最佳实践
command.version.includeMergedTags: 检测到更改的包时包括来自合并分支的标签
command.publish.message:执行版本更新以进行发布时的自定义提交消息
command.publish.registry
:使用它来设置要发布到的自定义注册表 url 而不是 npmjs.org,如果需要,您必须已经过身份验证。
六、工具函数
export {
useRequest,
useControllableValue,
useDynamicList,
useVirtualList,
useResponsive,
useEventEmitter,
useLocalStorageState,
useSessionStorageState,
useSize,
configResponsive,
useUpdateEffect,
useUpdateLayoutEffect,
useBoolean,
useToggle,
useDocumentVisibility,
useSelections,
useThrottle,
useThrottleFn,
useThrottleEffect,
useDebounce,
useDebounceFn,
useDebounceEffect,
usePrevious,
useMouse,
useScroll,
useClickAway,
useFullscreen,
useInViewport,
useKeyPress,
useEventListener,
useHover,
useUnmount,
useSet,
useMemoizedFn,
useMap,
useCreation,
useDrag,
useDrop,
useMount,
useCounter,
useUpdate,
useTextSelection,
useEventTarget,
useHistoryTravel,
useCookieState,
useSetState,
useInterval,
useWhyDidYouUpdate,
useTitle,
useNetwork,
useTimeout,
useReactive,
useFavicon,
useCountDown,
useWebSocket,
useLockFn,
useUnmountedRef,
useExternal,
useSafeState,
useLatest,
useIsomorphicLayoutEffect,
useDeepCompareEffect,
useAsyncEffect,
useLongPress,
useRafState,
useTrackedEffect,
usePagination,
useAntdTable,
useFusionTable,
useInfiniteScroll,
useGetState,
clearCache,
useFocusWithin,
};
ahook写了以上工具函数,平时开发过程中自己用到的其实并没有这么多,我们可以封装自己的hooks。
七、总结
在看源码前,我们先去官方文档复习下框架设计理念、源码分层设计 阅读下框架官方开发人员写的相关文章 借助框架的调用栈来进行源码的阅读,通过这个执行流程,我们就完整的对源码进行了一个初步的了解 接下来再对源码执行过程中涉及的所有函数逻辑梳理一遍
关注我,一起携手进阶
欢迎关注前端早茶,与广东靓仔携手共同进阶~