一、前言
本文基于开源项目:
广东靓仔在看ahooks项目代码的时候发现了一些有意思的东西,这里分享下。
二、ahooks是什么
ahooks是一套高质量可靠的 React Hooks 库
具有以下特性:
- 易学易用
- 支持 SSR
- 对输入输出函数做了特殊处理,且避免闭包问题
- 包含大量提炼自业务的高级 Hooks
- 包含丰富的基础 Hooks
- 使用 TypeScript 构建,提供完整的类型定义文件
安装:
$ npm install --save ahooks # or $ yarn add ahooks
使用:
import { useRequest } from 'ahooks';
三、项目目录
广东靓仔把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
效果如下:
篇幅有限,这里稍微提下:
1、菜单目录,我们可以通过在config配置navs、menus
2、dumi配置中en-US
是默认语言,如果需要中文创建一个带 zh-CN
locale 后缀的同名 Markdown 文件即可。
更多的dumi相关知识可以前往官方文档查看:
五、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。
七、总结
在我们阅读完官方文档后,我们一定会进行更深层次的学习,比如看下框架底层是如何运行的,以及源码的阅读。
这里广东靓仔给下一些小建议:
- 在看源码前,我们先去官方文档复习下框架设计理念、源码分层设计
- 阅读下框架官方开发人员写的相关文章
- 借助框架的调用栈来进行源码的阅读,通过这个执行流程,我们就完整的对源码进行了一个初步的了解
- 接下来再对源码执行过程中涉及的所有函数逻辑梳理一遍