UNPKG

@formily/core

Version:

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

243 lines (225 loc) 6.64 kB
import { clone, isEqual, isFn, each, globalThisPolyfill, Subscribable, FormPath, FormPathPattern, isValid, defaults, shallowClone } from '@formily/shared' import { Draft, Immer, enablePatches } from 'immer' import { IStateModelProvider, IStateModelFactory, StateDirtyMap, IModel, StateModel } from '../types' const hasProxy = !!globalThisPolyfill.Proxy enablePatches() const { produce } = new Immer({ autoFreeze: false }) export const createStateModel = <State = {}, Props = {}>( Factory: IStateModelFactory<State, Props> ): IStateModelProvider<State, Props> => { return class Model<DefaultProps = any> extends Subscribable<State> implements IModel<State, Props & DefaultProps> { public state: State & { displayName?: string } public props: Props & DefaultProps & { useDirty?: boolean computeState?: (draft: State, prevState: State) => void } public cache?: any public displayName?: string public dirtyNum: number public dirtys: StateDirtyMap<State> public prevState: State public batching: boolean public stackCount: number public controller: StateModel<State> constructor(defaultProps: DefaultProps) { super() this.state = { ...Factory.defaultState } this.prevState = this.state this.props = defaults(Factory.defaultProps, defaultProps) this.dirtys = {} this.cache = {} this.dirtyNum = 0 this.stackCount = 0 this.batching = false this.controller = new Factory(this.state, this.props) this.displayName = Factory.displayName this.state.displayName = this.displayName } batch = (callback?: () => void) => { this.batching = true if (isFn(callback)) { callback() } if (this.dirtyNum > 0) { this.notify(this.getState()) } this.dirtys = {} this.dirtyNum = 0 this.batching = false } getState = (callback?: (state: State) => any) => { if (isFn(callback)) { return callback(this.getState()) } else { if (isFn(this.controller.publishState)) { return this.controller.publishState(this.state) } if (!hasProxy || this.props.useDirty) { return clone(this.state) } else { return produce(this.state, () => {}) } } } getSourceState = (callback?: (state: State) => any) => { if (isFn(callback)) { return callback(this.state) } else { return this.state } } setSourceState = (callback: (state: State) => void) => { if (isFn(callback)) { callback(this.state) } } setCache = (key: string, value: any) => { this.cache[key] = shallowClone(value) } getCache = (key: string) => { return this.cache[key] } removeCache = (key: string) => { delete this.cache[key] } setState = ( callback: (state: State | Draft<State>) => State | void, silent = false ) => { if (isFn(callback)) { this.stackCount++ if (!hasProxy || this.props.useDirty) { const draft = this.getState() if (!this.batching) { this.dirtys = {} this.dirtyNum = 0 } callback(draft) if (isFn(this.props.computeState)) { this.props.computeState(draft, this.state) } if (isFn(this.controller.computeState)) { this.controller.computeState(draft, this.state) } const draftKeys = Object.keys(draft || {}) const stateKeys = Object.keys(this.state || {}) each( draftKeys.length > stateKeys.length ? draft : this.state, (value, key) => { if (!isEqual(this.state[key], draft[key])) { this.state[key] = draft[key] this.dirtys[key] = true this.dirtyNum++ } } ) if (isFn(this.controller.dirtyCheck)) { const result = this.controller.dirtyCheck(this.dirtys) if (isValid(result)) { Object.assign(this.dirtys, result) } } if (this.dirtyNum > 0 && !silent) { if (this.batching) { this.stackCount-- return } this.notify(this.getState()) this.dirtys = {} this.dirtyNum = 0 } } else { if (!this.batching) { this.dirtys = {} this.dirtyNum = 0 } //用proxy解决脏检查计算属性问题 this.state = produce( this.state, draft => { callback(draft) if (isFn(this.props.computeState)) { this.props.computeState(draft, this.state) } if (isFn(this.controller.computeState)) { this.controller.computeState(draft, this.state) } }, patches => { patches.forEach(({ path, op, value }) => { if (op === 'replace') { if (path.length > 1 ||!isEqual(this.state[path[0]], value)) { this.dirtys[path[0]] = true this.dirtyNum++ } } else { this.dirtys[path[0]] = true this.dirtyNum++ } }) } ) if (isFn(this.controller.dirtyCheck)) { const result = this.controller.dirtyCheck(this.dirtys) if (isValid(result)) { Object.assign(this.dirtys, result) } } if (this.dirtyNum > 0 && !silent) { if (this.batching) { this.stackCount-- return } this.notify(this.getState()) this.dirtys = {} this.dirtyNum = 0 } } this.stackCount-- if (!this.stackCount) { this.prevState = this.state } } } /** * 当前操作的变化情况 */ isDirty = (key?: string) => key ? this.dirtys[key] === true : this.dirtyNum > 0 getDirtyInfo = () => this.dirtys /** * *在一组操作过程中的变化情况 */ hasChanged = (path?: FormPathPattern) => { return path ? !isEqual( FormPath.getIn(this.prevState, path), FormPath.getIn(this.state, path) ) : !isEqual(this.prevState, this.state) } } as any }