radix3
一个基于 Radix Tree 的路由库radix3,使用说明直接看 README
概念
用树,就免不了节点
export const NODE_TYPES = {
NORMAL: 0 as 0,
WILDCARD: 1 as 1,
PLACEHOLDER: 2 as 2
}
三只节点, 普通节点、wildcard 节点、占位符节点
路由嘛,解决三个问题,增、删、查。
先用 ctx 存储上下文, rootNode 代表 根节点
const ctx: RadixRouterContext = {
rootNode: createRadixNode(),
staticRoutesMap: {}
}
节点数据结构
function createRadixNode (options: Partial<RadixNode> = {}): RadixNode {
return {
type: options.type || NODE_TYPES.NORMAL,
parent: options.parent || null,
children: {},
data: options.data || null,
paramName: options.paramName || null,
wildcardChildNode: null,
placeholderChildNode: null
}
}
其中 wildcardChildNode 代表 通配符节点,placeholderChildNode 代表占位符节点
需要注意的是,staticRoutesMap 只存储静态的路径路由,即不带 通配符 和 占位符的路径
增
function insert (ctx: RadixRouterContext, path: string, data: any) {
// 是否静态路由
let isStaticRoute = true
// 切割路径
const sections = path.split('/')
let node = ctx.rootNode
// 循环路径
for (let i = 0; i < sections.length; i++) {
const section = sections[i]
const children = node.children
let childNode
// 判断子节点是否存在当前路径
if ((childNode = children[section])) {
node = childNode
} else {
// 不存在当前节点就获取节点类型,然后创建节点
const type = getNodeType(section)
// Create new node to represent the next part of the path
childNode = createRadixNode({ type, parent: node })
// 建立树,把父子节点的关系建立起来
node.children[section] = childNode
// 如果是占位符节点,把占位符名字 paramName 存储起来,同时把子节点赋值给当前节点的占位符节点
if (type === NODE_TYPES.PLACEHOLDER) {
childNode.paramName = section.slice(1)
node.placeholderChildNode = childNode
isStaticRoute = false
} else if (type === NODE_TYPES.WILDCARD) {
// 通配符跟上面占位符差不多
node.wildcardChildNode = childNode
isStaticRoute = false
}
// 将子节点赋值给当前节点
node = childNode
}
}
// Store whatever data was provided into the node
// 把数据存储到当前节点数据,注意这个当前节点就是最后的子节点
node.data = data
// Optimization, if a route is static and does not have any
// variable sections, we can store it into a map for faster retrievals
// 只对纯静态节点进行存储
if (isStaticRoute === true) {
ctx.staticRoutesMap[path] = node
}
return node
}
查
路由的查找,对于匹配的优先级,具体路径 > 占位符 > 通配符
查看节点有两个方法,lookup 和 lookupAll
- lookup 是用于查找具体的路径
- lookupAll 用于查找所有符合前缀的路径
// 查找具体路径
function lookup (ctx: RadixRouterContext, path: string): MatchedRoute {
const staticPathNode = ctx.staticRoutesMap[path]
if (staticPathNode) {
// 看看是不是静态路由
return staticPathNode.data
}
// 切割路由
const sections = path.split('/')
// 存储占位符
const params: MatchedRoute['params'] = {}
let paramsFound = false
let wildcardNode = null
// 从根节点找起
let node = ctx.rootNode
for (let i = 0; i < sections.length; i++) {
const section = sections[i]
// 看看当前节点是否存在通配符子节点
if (node.wildcardChildNode !== null) {
wildcardNode = node.wildcardChildNode
}
// Exact matches take precedence over placeholders
const nextNode = node.children[section]
// 看看具体子节点是否存在
if (nextNode !== undefined) {
node = nextNode
} else {
// 否则先看看占位符节点,如果占位符节点存在,存储参数
node = node.placeholderChildNode
if (node !== null) {
params[node.paramName] = section
paramsFound = true
} else {
//如果不存在,跳出循环
break
}
}
}
// 如果节点为空或者节点数据为空 同时存在通配符节点,这时才把通配符节点赋值给当前节点
if ((node === null || node.data === null) && wildcardNode !== null) {
node = wildcardNode
}
// 如果节点不存在,直接返回空
if (!node) {
return null
}
// 如果存在占位符,返回占位符以及数据
if (paramsFound) {
return {
...node.data,
params: paramsFound ? params : undefined
}
}
// 不然,返回路由注册时候的数据
return node.data
}
删
删除路由,也就是把路由中的每个节点从树中删除
function remove (ctx: RadixRouterContext, path: string) {
let success = false
// 切割路由
const sections = path.split('/')
// 从根节点找起
let node = ctx.rootNode
// 一直找到最后的路径
for (let i = 0; i < sections.length; i++) {
const section = sections[i]
node = node.children[section]
if (!node) {
return success
}
}
// 如果当前节点存在数据
if (node.data) {
const lastSection = sections[sections.length - 1]
// 清空当前节点的数据
node.data = null
// 如果当前节点没有子节点,那么当前节点删除
if (Object.keys(node.children).length === 0) {
const parentNode = node.parent
delete parentNode[lastSection]
parentNode.wildcardChildNode = null
parentNode.placeholderChildNode = null
}
success = true
}
return success
}