JavaScript 中常用的递归算法

243 阅读3分钟

目录

  1. 修改树状对象数组的键值 返回新数组
  2. 将树状对象数组拍平
  3. 在树状对象数组中 根据条件查找某项
  4. id parentId 关联的一维数组,处理成树状对象数组
  5. 通过子节点id 查找到所有关联的父节点
  6. React 递归渲染嵌套路由
  7. 渲染树状dom

修改树状对象数组的键值 返回新数组

一般使用 map 处理数据,返回一个新数组

// @param source 源数据格式:
// [ 
//   { id: 1, title: 'item1' }, 
//   { id: 2, title: 'item2', children: [ { id: 21, title: 'item2-1' } ] },
// ]
// 将处理成:
// [ 
//   { value: 1, label: 'item1' }, 
//   { value: 2, label: 'item2', children: [ { value: 21, label: 'item2-1' } ] }, 
// ]

处理函数:

const generateData = (source) => {
  const _loop = (data) => {
    return data.map((item) => {
      if (item.children && item.children.length > 0) {
        return {
          value: item.id,
          label: item.title,
          children: _loop(item.children)
        }
      }
      return {
        value: item.id,
        label: item.title,
      }
    })
  }
  return _loop(source)
}

将树状对象数组拍平

// @param source 源数据格式:
// [ 
//   { id: 1, title: 'item1' }, 
//   { id: 2, title: 'item2', children: [ { id: 21, title: 'item2-1' } ] },
// ]
// 将处理成:
// [ 
//   { id: 1, title: 'item1' }, 
//   { id: 2, title: 'item2' },
//   { id: 21, title: 'item2-1' },
// ]

处理函数:

const flattenData = (source) => {
  let result = []
  
  const _loop = (data) => {
    data.forEach((item) => {
      result.push(item)
      if (item.children && item.children.length > 0) {
        _loop(item.children)
      }
    })
  }
  
  _loop(source)
  return result
}

在树状对象数组中 根据条件查找某项

使用 for offor i 都可以

// @param source 源数据格式:
// [ 
//   { id: 1, title: 'item1' }, 
//   { id: 2, title: 'item2', children: [ { id: 21, title: 'item2-1' } ] }, 
// ]
// 比如这里查找到 id 匹配条件的这项

处理函数:

const findItemOfId = (source, id) => {
  for (let item of source) {
    if (item.id === id) {
      return item
    } else if (item.children && item.children.length > 0){
      const result = findItemOfId(item.children, id)
      if (result) return result
    }
  }
  return null
}

id parentId 关联的一维数组,处理成树状对象数组

// @param source 源数据格式:
// [ 
//   { id: 1, parentId: 0, title: 'item1' }, 
//   { id: 2, parentId: 0, title: 'item2' },
//   { id: 21, parentId: 2, title: 'item2-1' },
//   { id: 210, parentId: 21, title: 'item2-1-0' },
// ]
// 处理成:
// [ 
//   { id: 1, parentId: 0, title: 'item1' }. 
//   { id: 2, parentId: 0, title: 'item2', children: [ 
//     { value: 21, parentId: 2, title: 'item2-1', children: [
//       { id: 210, parentId: 21, title: 'item2-1-0' },
//     ] } 
//   ] },
// ]

处理函数:

const generateData = (source) => {
  let result = []

  const _insetChildItem = (data, curItem) => {
    data.forEach((item) => {
      if (item.id === curItem.parentId) {
        if (!item.children) item.children = []
        item.children.push({ 
          ...curItem 
        })
      } else if (item.children && item.children.length) {
        _insetChildItem(item.children, curItem)
      }
    })
  }

  source.forEach((item) => {
    if (item.parentId - 0 === 0) {
      result.push({ 
        ...item 
      })
    } else {
      _insetChildItem(result, item)
    }
  })

  return result
}

通过子节点id 查找到所有关联的父节点

// @param source 源数据格式:
// [ 
//   { id: 1, parentId: 0, title: 'item1' }, 
//   { id: 2, parentId: 0, title: 'item2' },
//   { id: 21, parentId: 2, title: 'item2-1' },
//   { id: 210, parentId: 21, title: 'item2-1-0' },
// ]
// 比如这里查找 id === 211 这项和它所有的父级项 title 得到数组:[ 'item2', ‘item2-1’, 'item2-1-1' ]

处理函数:

const getStructureArr = (source, id) => {
  let result: string[] = [source.find((item => item.id === id)?.title]
  let child = source.find((item) => item.id === id)

  while (child && child.parentId) {
    result = [source.find((item) => id === child.parentId)?.title, ...result]
    child = source.find((item) => id === child.parentId)
  }
  
  return result
}

React 递归渲染嵌套路由

import React, LazyExoticComponent, { ReactNode } from 'react'
import { Route, Navigate } from 'react-router-dom'
import { RouteProps } from 'react-router-dom'

// routes数据格式类型
type RouteType = RouteProps & {
  name: string             // 路由别名
  redirect?: string        // 重定向路径
  component?: ReactNode | LazyExoticComponent
  routes?: RouteType[]     // 子路由
}

const loop = (routes: RouteType[]): ReactNode => {
  const routesComp = routes.map((item) => {
    if (item.routes && item.routes.length > 0) {
      return (
        <Route
          path={item.path}
          element={<item.component />}
          key={item.name}
        >
          {loop(item.routes)}
        </Route>
      )
    }

    return (
      <Route
        path={item.path}
        element={!item.redirect ? <item.component /> : <Navigate to={item.redirect} />}
        key={item.name ?? item.redirect}
      />
    )
  })

  // 为一组子路由添加404跳转(path="*"是相对路径)
  return <>
    {routesComp}
    <Route path="*" element={<Navigate to={ERR_ROUTE_PATH} />} />
  </>
}

渲染树状dom

html结构:

<div id="testTree"></div>

处理函数和应用:

// 将树状数据渲染到文档片段里
const createTreeDom = (source) => {
  const fragment = document.createDocumentFragment()

  source.forEach(({ id, parentId, name }) => {
    const el = document.createElement('div')
    el.classList.add('item')
    el.setAttribute('data-self-id', id)
    el.setAttribute('data-parent-id', parentId)
    el.innerHTML = `<div class="text">${name}</div>`

    if (parentId - 0 === 0) {
      fragment.appendChild(el)
    } else {
      const _parentEl = fragment.querySelector(`[data-self-id="${parentId}"]`)
      if (_parentEl) {
        if (!_parentEl.querySelector('.sub-list')) _parentEl.insertAdjacentHTML('beforeend', '<div class="sub-list"></div>')
        _parentEl.querySelector('.sub-list').appendChild(el)
      }
    }
  })

  return fragment
}

// 测试数据
const testData = [ 
  { id: 1, parentId: 0, title: 'item1' }, 
  { id: 11, parentId: 1, title: 'item1-1' }
  { id: 2, parentId: 0, title: 'item2' },
  { id: 21, parentId: 2, title: 'item2-1' },
  { id: 210, parentId: 21, title: 'item2-1-1' },
]

const treeDom =  generateTreeDom(testData)
treeDom && document.querySelector('#testTree')?.appendChild(treeDom)