Skip to content

Commit

Permalink
feat: getChildrenKey 支持
Browse files Browse the repository at this point in the history
  • Loading branch information
zhangshichun committed Jul 17, 2023
1 parent c5e4981 commit 306e361
Show file tree
Hide file tree
Showing 13 changed files with 552 additions and 112 deletions.
2 changes: 1 addition & 1 deletion README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

## Features (特性)

👉[特性(Features)](./docs/guide/features.md)
👉[特性(Features) & 配置(Configs)](./docs/guide/features.md)

## Functions (方法列表)

Expand Down
52 changes: 48 additions & 4 deletions docs/guide/features.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Features (特性)
# Features & Configs (配置项)

## 三种搜索策略(`strategy`)
> 所有方法的配置项均至少支持以下三个核心配置
## 一、`options.strategy`:搜索策略

所有本库提供的方法都支持以下三种策略(`strategy`):

Expand All @@ -14,12 +16,54 @@
{ strategy: 'post' }
```

## 自定义的 `ChildrenKey`
## 二、`options.childrenKey` 支持树结构子节点 `key` 的命名

支持传入 `ChildrenKey` 参数,你不仅可以用 `children` 表示子节点;
支持传入 `options.childrenKey` 参数,你不仅可以用 `children` 表示子节点;

也可以用 `subItems``babies` 等所有你能想到的词语表示子节点:

```js
{ childrenKey: 'babies' }
```

## 三、`options.getChildrenKey` 支持一棵树上多种 `childrenKey`

下面这种结构的树也是可以被解析的了:

```js
const treeMultiChildrenKey: Tree = {
key: '1',
children: [
{
key: '2',
subItems: [
{
key: '3'
}
]
},
{
key: '4',
subItems: [
{
key: '5'
}
]
}
]
}
```

但你需要在 `options.getChildrenKey` 返回响应的 `childrenKey`:

```js
{
getChildrenKey: (tree, meta) => {
if (meta.depth === 1) {
return 'subItems'
}
}
}
```

(返回为 `undefined` 时,依然会使用 `options.childrenKey` 作为默认的 `key`)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"build:esm": "cross-env NODE_ENV=production rollup -c rollup.esm.config.js",
"postbuild": "tsc --emitDeclarationOnly --declaration --project ts.config.json --outDir dist/esm",
"test": "cross-env TS_NODE_PROJECT='test/tsconfig.test.json' mocha",
"test-with-coverage": "cross-env TS_NODE_PROJECT='test/tsconfig.test.json' nyc --reporter=text mocha",
"nyc": "cross-env TS_NODE_PROJECT='test/tsconfig.test.json' nyc --reporter=text mocha",
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs",
"predeploy": "yarn docs:build",
Expand Down
47 changes: 24 additions & 23 deletions src/filter.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,24 @@
import type { ChildrenKey, Tree, BaseOptions, BaseCallbackMeta } from "./types";
import { getFinalChildrenKey } from "./helpers/common";
import type { ChildrenKey, Tree, BaseOptions, BaseCallbackMeta, BaseInnerOptions } from "./types";

export type FilterOptions = BaseOptions

export type FilterCallbackMeta<T extends ChildrenKey> = BaseCallbackMeta<T>

export type FilterCallback<T extends ChildrenKey> = (treeItem: Tree<T>, meta: FilterCallbackMeta<T>) => any

type FilterInnerOption<T extends ChildrenKey> = {
childrenKey: ChildrenKey
parents: Tree<T>[],
depth: number
}

type FilterInnerOption<T extends ChildrenKey> = BaseInnerOptions<T>

type FilterImpl<T extends ChildrenKey> = (treeItem: Tree<T>, callback: FilterCallback<T>, options: FilterInnerOption<T>) => Tree<T> | undefined




// 前置遍历
const preImpl: FilterImpl<ChildrenKey> = (treeItem, callback, options) => {
const res = callback(treeItem, options)
if (!res) {
return undefined
}
const children = treeItem[options.childrenKey]
const finalChildrenKey = getFinalChildrenKey(treeItem, options, options)
const children = treeItem[finalChildrenKey]
let newChildren
if (children && Array.isArray(children)) {
newChildren = children.map((childItem) => {
Expand All @@ -37,13 +31,14 @@ const preImpl: FilterImpl<ChildrenKey> = (treeItem, callback, options) => {
}
return {
...treeItem,
[options.childrenKey]: newChildren
[finalChildrenKey]: newChildren
}
}

// 子节点优先遍历
const postImpl: FilterImpl<ChildrenKey> = (treeItem, callback, options) => {
const children = treeItem[options.childrenKey]
const finalChildrenKey = getFinalChildrenKey(treeItem, options, options)
const children = treeItem[finalChildrenKey]
let newChildren
if (children && Array.isArray(children)) {
newChildren = children.map((childItem) => {
Expand All @@ -60,7 +55,7 @@ const postImpl: FilterImpl<ChildrenKey> = (treeItem, callback, options) => {
}
return {
...treeItem,
[options.childrenKey]: newChildren,
[finalChildrenKey]: newChildren,
}
}

Expand All @@ -87,15 +82,17 @@ const breadthImpl: FilterImpl<ChildrenKey> = (treeItem, callback, options) => {
let result: Tree<ChildrenKey>
const resultCache = new WeakMap<any, boolean>()
const newNodeCache = new WeakMap<any, Tree<ChildrenKey>>()
const childrenKeyCache = new WeakMap()
const runQueue = (): Tree<ChildrenKey> | undefined => {
if (queue.length === 0) {
return result
}

const queueItem = queue.shift() as QueueItem
const treeItem = queueItem.tree
if (treeItem[options.childrenKey] && Array.isArray(treeItem[options.childrenKey])) {
const subQueueItems = treeItem[options.childrenKey].map((subTree: Tree<ChildrenKey>) => (
const finalChildrenKey = getFinalChildrenKey(treeItem, queueItem.options, queueItem.options)
if (treeItem[finalChildrenKey] && Array.isArray(treeItem[finalChildrenKey])) {
const subQueueItems = treeItem[finalChildrenKey].map((subTree: Tree<ChildrenKey>) => (
{
tree: subTree,
options: {
Expand All @@ -121,20 +118,22 @@ const breadthImpl: FilterImpl<ChildrenKey> = (treeItem, callback, options) => {
if (isTopNode && !callbackResult) {
return undefined
}
let newNode = genNewNoChildrenNode(treeItem, queueItem.options.childrenKey)
let newNode = genNewNoChildrenNode(treeItem, finalChildrenKey)
// topNode callback true, set the topNode
if (isTopNode) {
result = newNode
}
resultCache.set(queueItem.tree, callbackResult)
newNodeCache.set(queueItem.tree, newNode)
childrenKeyCache.set(queueItem.tree, finalChildrenKey)
// Not top node, have a valid parent, and callback truthy
if (callbackResult) {
const parentNewNode = newNodeCache.get(parent)
if (parentNewNode && !parentNewNode[queueItem.options.childrenKey]) {
parentNewNode[queueItem.options.childrenKey] = []
if (callbackResult && parent) {
const parentNewNode: any = newNodeCache.get(parent)
const parentChildrenKey = childrenKeyCache.get(parent)
if (!parentNewNode[parentChildrenKey]) {
parentNewNode[parentChildrenKey] = []
}
parentNewNode?.[queueItem.options.childrenKey]?.push?.(newNode)
parentNewNode[parentChildrenKey].push(newNode)
}
return runQueue()
}
Expand All @@ -154,12 +153,14 @@ function filter<T extends ChildrenKey>(tree: Tree<T>[], callback: FilterCallback
function filter<T extends ChildrenKey>(tree: Tree<T> | Tree<T>[], callback: FilterCallback<T>, options: FilterOptions = {}): Tree<T> | Tree<T>[] | undefined {
const childrenKey = options.childrenKey ?? 'children'
const strategy = options.strategy ?? 'pre'
const getChildrenKey = options.getChildrenKey
const isForest = Array.isArray(tree)
const method = strategies[strategy]
const innerOptions = {
childrenKey,
depth: 0,
parents: [] as Tree<T>[]
parents: [] as Tree<T>[],
getChildrenKey
}
return isForest ? tree.map(tree => {
return method(tree, callback, innerOptions)
Expand Down
25 changes: 13 additions & 12 deletions src/find.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { ChildrenKey, Tree, BaseOptions, BaseCallbackMeta } from "./types";
import { getFinalChildrenKey } from "./helpers/common";
import { ChildrenKey, Tree, BaseOptions, BaseCallbackMeta, BaseInnerOptions } from "./types";

export type FindOptions = BaseOptions
export type FindCallbackMeta<T extends ChildrenKey> = BaseCallbackMeta<T>
export type FindCallback<T extends ChildrenKey> = (treeItem: Tree<T>, meta: FindCallbackMeta<T>) => boolean | undefined
type FindInnerOption<T extends ChildrenKey> = {
childrenKey: ChildrenKey
parents: Tree<T>[],
depth: number
}
type FindInnerOption<T extends ChildrenKey> = BaseInnerOptions<T>
type FindImpl<T extends ChildrenKey> = (treeItem: Tree<T>, callback: FindCallback<T>, options: FindInnerOption<T>) => Tree<T>|undefined

// 前置深度优先遍历
Expand All @@ -16,7 +13,8 @@ const preImpl: FindImpl<ChildrenKey> = (treeItem, callback, options) => {
if (callbackResult) {
return treeItem
}
const children = treeItem[options.childrenKey]
const finalChildrenKey = getFinalChildrenKey(treeItem, options, options)
const children = treeItem[finalChildrenKey]
if (!children || !Array.isArray(children)) {
return undefined
}
Expand All @@ -31,8 +29,8 @@ const preImpl: FindImpl<ChildrenKey> = (treeItem, callback, options) => {

// 后置深度优先遍历
const postImpl: FindImpl<ChildrenKey> = (treeItem, callback, options) => {
const children = treeItem[options.childrenKey]

const finalChildrenKey = getFinalChildrenKey(treeItem, options, options)
const children = treeItem[finalChildrenKey]
if (children && Array.isArray(children)) {
const findOne = children.find((childItem) => {
return postImpl(childItem, callback, {
Expand Down Expand Up @@ -71,8 +69,9 @@ const breadthImpl: FindImpl<ChildrenKey> = (treeItem, callback, options) => {
}
const queueItem = queue.shift() as QueueItem
const treeItem = queueItem.tree
if (treeItem[options.childrenKey] && Array.isArray(treeItem[options.childrenKey])) {
const subQueueItems = treeItem[options.childrenKey].map((subTree: Tree) => (
const finalChildrenKey = getFinalChildrenKey(treeItem, queueItem.options, queueItem.options)
if (treeItem[finalChildrenKey] && Array.isArray(treeItem[finalChildrenKey])) {
const subQueueItems = treeItem[finalChildrenKey].map((subTree: Tree) => (
{
tree: subTree,
options: {
Expand Down Expand Up @@ -102,11 +101,13 @@ const strategies = {
function find<T extends ChildrenKey>(tree: Tree<T> | Tree<T>[], callback: FindCallback<T>, options?: FindOptions): Tree<T> | undefined {
const childrenKey = options?.childrenKey ?? 'children'
const strategy = options?.strategy ?? 'pre'
const getChildrenKey = options?.getChildrenKey
const method = strategies[strategy]
const innerOptions = {
childrenKey,
depth: 0,
parents: [] as Tree[]
parents: [] as Tree[],
getChildrenKey
}
if (Array.isArray(tree)) {
for(let i = 0, count = tree.length; i < count; i++) {
Expand Down
60 changes: 33 additions & 27 deletions src/foreach.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ChildrenKey, Tree, BaseOptions, BaseCallbackMeta } from "./types";
import { getFinalChildrenKey } from "./helpers/common";
import type { ChildrenKey, Tree, BaseOptions, BaseCallbackMeta, BaseInnerOptions } from "./types";

export type ForeachOptions = BaseOptions

Expand All @@ -7,11 +8,7 @@ export type ForeachCallbackMeta<T extends ChildrenKey> = BaseCallbackMeta<T>
export type ForeachCallback<T extends ChildrenKey> = (treeItem: Tree<T>, meta: ForeachCallbackMeta<T>) => void


type ForeachInnerOption<T extends ChildrenKey> = {
childrenKey: ChildrenKey
parents: Tree<T>[],
depth: number
}
type ForeachInnerOption<T extends ChildrenKey> = BaseInnerOptions<T>


type ForeachImpl<T extends ChildrenKey> = (treeItem: Tree<T>, callback: ForeachCallback<T>, options: ForeachInnerOption<T>) => void
Expand All @@ -20,28 +17,32 @@ type ForeachImpl<T extends ChildrenKey> = (treeItem: Tree<T>, callback: ForeachC
// 前置遍历
const preImpl: ForeachImpl<ChildrenKey> = (treeItem, callback, options) => {
callback(treeItem, options)
const children = treeItem[options.childrenKey]
const finalChildrenKey = getFinalChildrenKey(treeItem, options, options)
const children = treeItem[finalChildrenKey]
if (children && Array.isArray(children)) {
const nextLevelOptions = {
...options,
parents: [...options.parents, treeItem],
depth: options.depth + 1
}
children.forEach((childItem) => {
preImpl(childItem, callback, {
...options,
parents: [...options.parents, treeItem],
depth: options.depth + 1
})
preImpl(childItem, callback, nextLevelOptions)
})
}
}

// 后置遍历
const postImpl: ForeachImpl<ChildrenKey> = (treeItem, callback, options) => {
const children = treeItem[options.childrenKey]
const finalChildrenKey = getFinalChildrenKey(treeItem, options, options)
const children = treeItem[finalChildrenKey]
if (children && Array.isArray(children)) {
const nextLevelOptions = {
...options,
parents: [...options.parents, treeItem],
depth: options.depth + 1
}
children.forEach((childItem) => {
postImpl(childItem, callback, {
...options,
parents: [...options.parents, treeItem],
depth: options.depth + 1
})
postImpl(childItem, callback, nextLevelOptions)
})
}
callback(treeItem, options)
Expand All @@ -67,15 +68,18 @@ const breadthImpl: ForeachImpl<ChildrenKey> = (treeItem, callback, options) => {
}
const queueItem = queue.shift() as QueueItem
const treeItem = queueItem.tree
if (treeItem[options.childrenKey] && Array.isArray(treeItem[options.childrenKey])) {
const subQueueItems = treeItem[options.childrenKey].map((subTree: Tree) => (

const finalChildrenKey = getFinalChildrenKey(treeItem, queueItem.options, queueItem.options)
if (treeItem[finalChildrenKey] && Array.isArray(treeItem[finalChildrenKey])) {
const nextLevelOptions = {
...queueItem.options,
parents: [...queueItem.options.parents, treeItem],
depth: queueItem.options.depth + 1
}
const subQueueItems = treeItem[finalChildrenKey].map((subTree: Tree) => (
{
tree: subTree,
options: {
...queueItem.options,
parents: [...queueItem.options.parents, treeItem],
depth: queueItem.options.depth + 1
}
options: nextLevelOptions
}
))
queue.push(...subQueueItems)
Expand All @@ -92,15 +96,17 @@ const strategies = {
'breadth': breadthImpl
}

function foreach<T extends ChildrenKey> (tree: Tree<T> | Tree<T>[] , callback: ForeachCallback<T>, options?: ForeachOptions): void{
function foreach<T extends ChildrenKey>(tree: Tree<T> | Tree<T>[], callback: ForeachCallback<T>, options?: ForeachOptions): void {
const childrenKey = options?.childrenKey ?? 'children'
const strategy = options?.strategy ?? 'pre'
const getChildrenKey = options?.getChildrenKey
const isForest = Array.isArray(tree)
const method = strategies[strategy]
const innerOptions = {
childrenKey,
depth: 0,
parents: [] as Tree[]
parents: [] as Tree[],
getChildrenKey
}
isForest ? tree.forEach(tree => {
method(tree, callback, innerOptions)
Expand Down
Loading

0 comments on commit 306e361

Please sign in to comment.