radix3 路由库解析

570 阅读2分钟

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

  1. lookup 是用于查找具体的路径
  2. 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
}

打完收工