UNPKG

@formily/core

Version:

English | [简体中文](./README.zh-cn.md)

315 lines (292 loc) 7.73 kB
import { each, reduce, map, isFn, FormPath, FormPathPattern, Subscribable } from '@formily/shared' import { FormGraphNodeRef, FormGraphMatcher, FormGraphEacher, FormGraphProps } from '../types' export class FormGraph<NodeType = any> extends Subscribable<{ type: string payload: FormGraphNodeRef }> { private refrences: { [key in string]: FormGraphNodeRef } private nodes: { [key in string]: NodeType } private buffer: { path: FormPath ref: FormGraphNodeRef latestParent?: { ref: FormGraphNodeRef path: FormPath } }[] private matchStrategy: FormGraphProps['matchStrategy'] public size: number constructor(props: FormGraphProps = {}) { super() this.refrences = {} this.nodes = {} this.size = 0 this.buffer = [] this.matchStrategy = props.matchStrategy } /** * 模糊匹配API * @param path * @param matcher */ select(path: FormPathPattern, eacher?: FormGraphMatcher<NodeType>) { const pattern = FormPath.parse(path) if (!eacher) { const node = this.get(pattern) if (node) { return node } } for (let nodePath in this.nodes) { const node = this.nodes[nodePath] if ( isFn(this.matchStrategy) ? this.matchStrategy(pattern, nodePath) : pattern.match(nodePath) ) { if (isFn(eacher)) { const result = eacher(node, FormPath.parse(nodePath)) if (result === false) { return node } } else { return node } } } } get(path: FormPathPattern) { return this.nodes[FormPath.parse(path).toString()] } selectParent(path: FormPathPattern) { const selfPath = FormPath.parse(path) const parentPath = FormPath.parse(path).parent() if (selfPath.toString() === parentPath.toString()) return undefined return this.get(parentPath) } selectChildren(path: FormPathPattern) { const ref = this.refrences[FormPath.parse(path).toString()] if (ref && ref.children) { return reduce( ref.children, (buf, path) => { return buf.concat(this.get(path)).concat(this.selectChildren(path)) }, [] ) } return [] } exist(path: FormPathPattern) { return !!this.get(FormPath.parse(path)) } /** * 递归遍历所有children * 支持模糊匹配 */ eachChildren(eacher: FormGraphEacher<NodeType>, recursion?: boolean): void eachChildren( path: FormPathPattern, eacher: FormGraphEacher<NodeType>, recursion?: boolean ): void eachChildren( path: FormPathPattern, selector: FormPathPattern, eacher: FormGraphEacher<NodeType>, recursion?: boolean ): void eachChildren( path: any, selector: any = true, eacher: any = true, recursion: any = true ) { if (isFn(path)) { recursion = selector eacher = path path = '' selector = '*' } if (isFn(selector)) { recursion = eacher eacher = selector selector = '*' } const ref = this.refrences[FormPath.parse(path).toString()] if (ref && ref.children) { return each(ref.children, path => { if (isFn(eacher)) { const node = this.get(path) if ( node && (isFn(this.matchStrategy) ? this.matchStrategy(selector, path) : FormPath.parse(selector).match(path)) ) { eacher(node, path) if (recursion) { this.eachChildren(path, selector, eacher, recursion) } } } }) } } /** * 递归遍历所有parent */ eachParent(path: FormPathPattern, eacher: FormGraphEacher<NodeType>) { const selfPath = FormPath.parse(path) const ref = this.refrences[selfPath.toString()] if (isFn(eacher)) { if (ref && ref.parent) { const node = this.get(ref.parent.path) this.eachParent(ref.parent.path, eacher) eacher(node, ref.parent.path) } } } /** * 遍历所有父节点与所有子节点 */ eachParentAndChildren( path: FormPathPattern, eacher: FormGraphEacher<NodeType> ) { const selfPath = FormPath.parse(path) const node = this.get(selfPath) if (!node) return this.eachParent(selfPath, eacher) if (isFn(eacher)) { eacher(node, selfPath) this.eachChildren(selfPath, eacher) } } getLatestParent(path: FormPathPattern) { const selfPath = FormPath.parse(path) const parentPath = FormPath.parse(path).parent() if (selfPath.toString() === parentPath.toString()) return undefined if (this.refrences[parentPath.toString()]) return { ref: this.refrences[parentPath.toString()], path: FormPath.parse(parentPath.toString()) } return this.getLatestParent(parentPath) } map(mapper: (node: NodeType) => any) { return map(this.nodes, mapper) } reduce<T>( reducer: (buffer: T, node: NodeType, key: string) => T, initial: T ) { return reduce(this.nodes, reducer, initial) } appendNode(path: FormPathPattern, node: NodeType) { const selfPath = FormPath.parse(path) const parentPath = selfPath.parent() const parentRef = this.refrences[parentPath.toString()] const selfRef: FormGraphNodeRef = { path: selfPath, children: [] } if (this.get(selfPath)) return this.nodes[selfPath.toString()] = node this.refrences[selfPath.toString()] = selfRef this.size++ if (parentRef) { parentRef.children.push(selfPath) selfRef.parent = parentRef } else { const latestParent = this.getLatestParent(selfPath) if (latestParent) { latestParent.ref.children.push(selfPath) selfRef.parent = latestParent.ref this.buffer.push({ path: selfPath, ref: selfRef, latestParent: latestParent }) } } this.buffer.forEach(({ path, ref, latestParent }, index) => { if ( path.parent().match(selfPath) || (selfPath.includes(latestParent.path) && path.includes(selfPath) && selfPath.toString() !== path.toString()) ) { selfRef.children.push(path) ref.parent = selfRef latestParent.ref.children.splice( latestParent.ref.children.indexOf(path), 1 ) this.buffer.splice(index, 1) } }) this.notify({ type: 'GRAPH_NODE_DID_MOUNT', payload: selfRef }) } remove(path: FormPathPattern) { const selfPath = FormPath.parse(path) const selfRef = this.refrences[selfPath.toString()] if (!selfRef) return this.notify({ type: 'GRAPH_NODE_WILL_UNMOUNT', payload: selfRef }) if (selfRef.children) { selfRef.children.forEach(path => { this.remove(path) }) } this.buffer = this.buffer.filter(({ ref }) => { return selfRef !== ref }) delete this.nodes[selfPath.toString()] delete this.refrences[selfPath.toString()] this.size-- if (selfRef.parent) { selfRef.parent.children.forEach((path, index) => { if (path.match(selfPath)) { selfRef.parent.children.splice(index, 0) } }) } } replace(path: FormPathPattern, node: NodeType) { const selfPath = FormPath.parse(path) const selfRef = this.refrences[selfPath.toString()] if (!selfRef) return this.notify({ type: 'GRAPH_NODE_WILL_UNMOUNT', payload: selfRef }) this.nodes[selfPath.toString()] = node this.notify({ type: 'GRAPH_NODE_DID_MOUNT', payload: selfRef }) } }