阅读本章节前需要先了解@sagaroute/react
经历的三个执行阶段,详情可查看深入原理: 三个阶段
@sagaroute/react
实例在创建到生成路由列表都需要经历一系列的步骤,比如获取合并配置、生成 FileNode
列表、生成路由列表、注入路由列表。在此过程中,它会运行注册在某个步骤下的钩子函数,让开发者由机会在该步骤中运行自己的代码去更改每个阶段生成的结果,以及决定是否终止流程的执行
举例来说,weave.afterEach
会在单个路由生成后运行,那么,我们可以通过代码使路由路径从驼峰式命名(如RoleList
)换成小写横杠分割命名(如role-list
),如下所示:
new Sagaroute({
hooks: {
weave: {
afterEach(route) {
route.path = route.path
?.replace(/([A-Z])/g, '-$1')
.replace(/^-/, '')
.toLocaleLowerCase();
},
},
},
});
还有其他的钩子,会在实例初始化或执行时的不同阶段被调用,最常用的是weave.beforeEach
和gather.beforeEach
。所有的钩子函数的完整参考及其用法请参考钩子函数 API 介绍
下面是实例初始化和执行的图表,其中包含所有在不同步骤前后执行的钩子函数。你现在并不需要完全理解图中的所有内容,但以后它将是一个有用的参考
有关所有钩子函数及其各自用例的详细信息,请看钩子函数 API 介绍
执行阶段:在Sagaroute
实例初始化时,读取配置文件,获取和整理配置前执行
类型
/**
* @param {RoutingOption} inputOption SagaRoute实例化时传入的配置参数
* @returns {void|UltimateRoutingOption} 如果返回了结果,该结果将会作为最终的配置,且跳过读取配置文件、合并配置、格式化配置的步骤
*/
function buildbBefore(inputOption: RoutingOption): void | UltimateRoutingOption;
示例
const sagaroute = new Sagaroute({
dirpath: 'src/views',
hooks: {
build: {
before(option) {
console.log(option);
},
},
},
});
/**
* 打印输出:
* {
* dirpath: 'src/views',
* hooks:{
* build:{
* before: 'fn'
* }
* }
* }
*/
Sagaroute
实例初始化时,完成读取配置文件以及整理配置后执行/**
* @param {UltimateRoutingOption} ultimateOption UltimateRoutingOption类型是整理后的配置格式,与传入的配置的类型RoutingOption稍有不同
*/
function buildAfter(ultimateOption: UltimateRoutingOption): void;
Sagaroute
实例开始遍历路由文件目录前执行/**
* @param {string} dirpath 路由文件目录路径
* @param {string} layoutDirPath 全局路由目录路径
* @returns {void|null} 如果返回值为null,则执行流程在此终止
*/
function gatherBefore(dirpath: string,layoutDirPath: string) => void | null;
执行阶段:在Sagaroute
实例遍历路由文件目录过程中,解析每个 文件节点(即文件和文件夹) 之前执行
类型:
/**
* @param {string} fpath 当前准备解析的文件节点的路径
* @returns {void|FileNode|null}
* 1. 如果返回的是FileNode,会直接跳过解析步骤且把FileNode作为结果纳入FileNode列表上
* 2. 如果返回的是null,则终止对当前文件节点的解析,但不影响对其他文件节点的解析,且不会执行该文件节点的gather.afterEach钩子函数
*/
function gatherBeforeEach(fpath: string): void | FileNode | null;
示例:如果要实现特定名称的文件夹下的文件不会被解析成路由,可借此钩子函数实现
const path = require('path');
new Sagaroute({
hooks: {
gather: {
beforeEach(fpath) {
// widgets文件夹下的文件不会被解析
// window和unix的文件分隔符不同,需要用path.sep去获取
if (fpath.split(path.sep).includes('widgets')) {
return null;
}
},
},
},
});
Sagaroute
实例遍历路由文件目录过程中,解析每个文件节点生成FileNode
后执行/**
* @param {FileNode} fileNode 解析当前文件节点后生成的FileNode
* @param {string} dirpath 路由文件目录路径
* @param {string} layoutDirPath 全局路由目录路径
* @returns {void}
*/
function gatherAfterEach(fileNode: FileNode, fpath: string): void;
Sagaroute
实例完成遍历路由文件目录且生成FileNode
列表后执行/**
* @param {FileNode[]} fileNodes 解析后生成的FileNode列表
* @param {string} dirpath 路由文件目录路径
* @param {string} layoutDirPath 全局路由目录路径
* @returns {void}
*/
function gatherAfter(fileNode: FileNode, fpath: string): void;
Sagaroute
实例准备遍历FileNode
列表以生成路由列表之前执行/**
* @param {FileNode[]} fileNodes 准备要遍历的FileNode列表
* @returns {void|null} 如果返回值为null,则执行流程在此终止
*/
function weaveBefore(fileNodes: FileNode[]): void | null;
执行阶段:在Sagaroute
实例根据当前遍历到的FileNode
生成路由之前执行
类型:
/**
* @typedef FileNodeParent
* @type {object}
* @property {string} path - 父节点的路径
* @property {string} layoutNode - 父节点是否为全局路由对应的FileNode
*
* @param {FileNode} fileNode 当前遍历到的FileNode
* @param {FileNodeParent} parent 当前遍历到的FileNode的父节点信息
* @returns {void|Object|null}
* 1. 如果返回Object{ route: RouteObject; imports: Imports },则,会直接跳过生成路由步骤且把route作为结果纳入路由列表里,imports会作为依赖之一合并到依赖表中
* 2. 如果返回的是null,则终止生成对应当前FileNode的路由,但不影响对其他FileNode的路由的生成。且不会执行该FileNode对应的weave.afterEach钩子函数
*/
function weaveBeforeEach(
fileNode: FileNode,
parent: FileNodeParent,
): void | { route: RouteObject; imports: Imports } | null;
示例:如果要实现特定名称的文件夹下的文件不会被解析成路由,除了gather.beforeEach,也可借助此钩子函数实现:
const path = require('path');
new Sagaroute({
hooks: {
weave: {
beforeEach(fileNode) {
// widgets文件夹下的文件不会被解析
// window和unix的文件分隔符不同,需要用path.sep去获取
if (fileNode.path.split(path.sep).includes('widgets')) {
return null;
}
},
},
},
});
执行阶段:在Sagaroute
实例根据当前遍历到的FileNode
生成路由之后执行
类型:
/**
* @param {RouteObject} route 根据当前遍历到的FileNode生成的路由
* @param {Imports} imports 当前路由所附带的依赖集合
* @param {FileNode} fileNode 当前遍历到的FileNode
* @param {FileNodeParent} parent 当前遍历到的FileNode的父节点信息
*/
function weaveAfterEach(
route: RouteObject,
imports: Imports,
fileNode: FileNode,
parent: FileNodeParent,
): void;
示例:
如果想要把所有的路由路径从驼峰式命名(如RoleList
)换成小写横杠分割命名(如role-list
),可以借助该钩子函数实现,如下所示:
new Sagaroute({
hooks: {
weave: {
afterEach(route) {
route.path = route.path
?.replace(/([A-Z])/g, '-$1')
.replace(/^-/, '')
.toLocaleLowerCase();
},
},
},
});
如果要指定所有路由的匹配规则都遵循大小写敏感,既把所有的route.caseSensitive
设为true
,可以借助该钩子函数实现,如下所示:
new Sagaroute({
hooks: {
weave: {
afterEach(route) {
route.caseSensitive = true;
},
},
},
});
Sagaroute
实例生成路由列表后执行/**
* @param {RouteObject[]} routes 生成的路由列表
* @param {Imports} imports 生成的路由列表所附带的依赖集合
* @param {FileNode[]} fileNodes 遍历到的FileNode列表
*/
function weaveAfter(routes: RouteObject[], imports: Imports, fileNodes: FileNode[]): void;
执行阶段:在对路由模板文件进行解析生成渲染模板之前执行
类型:
/**
* @param {string} routeFilePath 路由模板文件的路径
* @returns {void|string|null}
* 1. 如果返回字符串,则直接跳过解析生成渲染模板步骤且把返回结果作为渲染模板
* 2. 如果返回的是null,则执行流程在此终止
*/
function printParseBefore(routeFilePath: string): void | string | null;
示例:如果项目中存在自定义的渲染模板,且不想用路由模板文件,则可借助该钩子函数实现:
new Sagaroute({
hooks: {
print: {
parse: {
before(routeFilePath) {
// 读取返回渲染模板内容
return fs.readFileSync(path.join(__dirname, 'template.txt'), 'utf-8');
},
},
},
},
});
/**
* @param {string} template 渲染模板
* @param {string} routeFilePath 路由模板文件路径
*/
function printParseAfter(template: string, routeFilePath: string): void;
执行阶段:在把含路由列表和依赖集合的模板变量渲染到渲染模板上之前执行
类型:
/**
* @param {Object{string: any}} view 模板变量
* @param {string} template 渲染模板
* @param {RouteObject[]} routes 路由列表
* @returns {void | string | null}
* 1. 如果返回结果为string,则跳过把模板变量渲染到渲染模板的步骤,且把返回结果当作渲染结果
* 2. 如果返回的是null,则执行流程在此终止
*/
function printInjectBefore(
view: Record<string, any>,
template: string,
routes: RouteObject[],
): void | string | null;
示例:如果想把路由列表分割到两个路由变量上,可借助该钩子函数实现:
假设存在路由文件目录,结构如下所示:
```bash
└── pages
├── user
│ ├── index.tsx
│ └── comments.tsx
└── echart
├── line.tsx
└── pie.tsx
如果要实现user和echart文件夹生成的注册路由分开放在以下路由模板文件里的两个变量chartRoutes
和userRoutes
里:
/* sagaroute-inject:userRoutes */
export const userRoutes = [];
/* sagaroute-inject:chartRoutes */
export const chartRoutes = [];
则可以借助该钩子函数实现:
import { transformRoutesToString } from '@sagaroute/react';
new Sagaroute({
hooks: {
print: {
inject: {
before(view, template, routes) {
// 往模板变量view里新增userRoutes和chartRoutes变量
// transformRoutesToString方法用于把路由列表转换为可渲染到渲染模板上的字符串
view['userRoutes'] = transformRoutesToString(
routes.filter((item: any) => item.path === 'user'),
);
view['chartRoutes'] = transformRoutesToString(
routes.filter((item: any) => item.path === 'chart'),
);
},
},
},
},
});
经过上述代码后便可实现路由分割
/**
* @param {string} content 渲染后生成的渲染结果
* @param {Object{string: any}} view 模板变量
* @param {string} template 渲染模板
*/
function printInjectAfter(content: string, view: Record<string, any>, template: string): void;
执行阶段:在把渲染结果覆写在路由模板文件之前执行
类型:
/**
* @param {string} renderedContent 渲染后生成的渲染结果
* @param {string} routeFilePath 路由模板文件的路径
* @returns {void | null} 如果返回的是null,则执行流程在此终止
*/
function printWriteBefore(renderedContent: string, routeFilePath: string): void | null;
执行阶段:在把渲染结果覆写在路由模板文件之后执行。
类型:
/**
* @param {string} routeFilePath 路由模板文件的路径
*/
function printWriteAfter(routeFilePath: string): void;
注意:除了build.before
外,所有阶段的before
和beforeEach
钩子函数都可以通过返回null
值来终止流程的执行
每个钩子都支持传入数组以支持多个钩子函数的存在,如:
new Sagaroute({
hooks: {
weave: {
afterEach: [function fn1() {}, function fn2() {}],
},
},
});
传入的钩子函数在执行的时候是串行、同步且默认无序的,如果存在多个带返回结果的钩子函数,那么默认以最后一个带返回结果的钩子函数返回的结果为准。那么问题来了,既然无序,那怎么确定哪个钩子函数是最后一个执行的?
其实每个钩子都可以定义执行顺序,我们可以用传入一个对象来定义钩子函数的执行顺序,如下所示:
new Sagaroute({
hooks: {
weave: {
afterEach: [
{ order: 1, handler: function fn1() {} },
{ order: 2, handler: function fn2() {} },
],
},
},
});
对象中的order
代表钩子函数的执行顺序,handler
代表钩子函数自身。order
越小越先执行,例如上述代码中的fn1
会比fn2
早执行,如果直接传入钩子函数而不是对象,则默认该钩子函数的order
为50
order
原则上是number
值,因此可传入任意数字,@sagaroute/react
中建议用户设立的order
值范围为0~100
,其余的数字值供开发插件使用
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。