immer#Patch TypeScript Examples

The following examples show how to use immer#Patch. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: slot.ts    From textbus with GNU General Public License v3.0 6 votes vote down vote up
/**
   * 更新插槽状态的方法
   * @param fn
   */
  updateState(fn: (draft: Draft<T>) => void): T {
    let changes!: Patch[]
    let inverseChanges!: Patch[]
    const oldState = this.state
    const newState = produce(oldState, fn, (p, ip) => {
      changes = p
      inverseChanges = ip
    })
    this.state = newState
    const applyAction: ApplyAction = {
      type: 'apply',
      patches: changes,
      value: newState
    }
    this.changeMarker.markAsDirtied({
      path: [],
      apply: [applyAction],
      unApply: [{
        type: 'apply',
        patches: inverseChanges,
        value: oldState
      }]
    })
    this.stateChangeEvent.next([applyAction])
    return newState!
  }
Example #2
Source File: useStateTracking.ts    From baleen3 with Apache License 2.0 5 votes vote down vote up
export function useStateTracking<T>(initialState: T): {
  current: T
  modify: (mutator: Mutator<T>) => void
  initialize: (initializer: Initializer<T>) => void
  undo: () => void
  redo: () => void
  canUndo: boolean
  canRedo: boolean
} {
  const [current, setCurrent] = useState<T>(initialState)
  const undoStack = useRef<
    Array<{ patches: Patch[]; inversePatches: Patch[] }>
  >([])
  const undoStackPointer = useRef(-1)

  const undo = useCallback((): void => {
    if (undoStackPointer.current < 0) {
      return
    }

    setCurrent(
      (state) =>
        applyPatches(
          state,
          undoStack.current[undoStackPointer.current].inversePatches
        ) as T
    )
    undoStackPointer.current--
  }, [])

  const redo = useCallback((): void => {
    if (undoStackPointer.current === undoStack.current.length - 1) {
      return
    }
    undoStackPointer.current++

    setCurrent(
      (state) =>
        applyPatches(
          state,
          undoStack.current[undoStackPointer.current].patches
        ) as T
    )
  }, [])

  const modify = useCallback((mutator: Mutator<T>): void => {
    setCurrent((state) =>
      produce(state, mutator, (patches, inversePatches) => {
        // ignore empty change
        if (patches.length > 0) {
          const pointer = ++undoStackPointer.current
          undoStack.current.length = pointer
          undoStack.current[Number(pointer)] = { patches, inversePatches }
        }
      })
    )
  }, [])

  const initialize = useCallback((initializer: Initializer<T>): void => {
    setCurrent(initializer)
    undoStackPointer.current = -1
    undoStack.current.length = 0
  }, [])

  return {
    current,
    initialize,
    modify,
    undo,
    redo,
    canUndo: undoStackPointer.current > -1,
    canRedo: undoStackPointer.current < undoStack.current.length - 1,
  }
}
Example #3
Source File: useSyncState.ts    From core with MIT License 5 votes vote down vote up
export default function useSyncState(
  store: DocStore,
  subtree: string,
  path: string
) {
  let stateAtPath = store.getStateAtPath(subtree, path);

  return [
    stateAtPath,
    (callbackOrData: any) => {
      let newPath = path; // Avoid mutating the closure value of path
      // Do NOT use above stateAtPath, if you do, you get stale value in the closure if you are reusing this setter callback
      let stateAtPath = store.getStateAtPath(subtree, newPath);
      let replaceValue = false;
      if (typeof callbackOrData !== 'function') {
        // throw new Error(
        //   'Received an object. Expecting a callback function which receives the object you want to modify.'
        // );
        replaceValue = true;
      }

      let produceCallback = (draft: any) => {
        callbackOrData(draft);
      };

      if (replaceValue) {
        // replace the received value in its parent
        // let parentPath = [...path];
        const immerPath = jsonPatchPathToImmerPath(newPath);
        const childKey = immerPath.pop();
        newPath = immerPathToJsonPatchPath(immerPath); // immerPath.join('/');
        stateAtPath = store.getStateAtPath(subtree, newPath);
        produceCallback = (draft: any) => {
          if (childKey) {
            draft[childKey] = callbackOrData;
          } else {
            // if root path
            throw new Error('Cannot replace root doc');
          }
        };
      }
      // @ts-ignore
      let [nextState, patches, inversePatches] = produceWithPatches(
        stateAtPath,
        produceCallback
      );
      // console.log(path, 'path');
      // console.log(JSON.stringify(patches, null, 2), 'patches before');
      patches = patches.map((p: Patch) => {
        return {
          ...p,
          path: newPath + immerPathToJsonPatchPath(p.path),
        };
      });

      inversePatches = inversePatches.map((p: any) => {
        return { ...p, path: newPath + immerPathToJsonPatchPath(p.path) };
      });
      // console.log(JSON.stringify(patches, null, 2), 'patches');

      patches.forEach((patch: any, index: number) => {
        store.dispatch({
          type: 'PATCH',
          payload: { patch, inversePatch: inversePatches[index], subtree },
        });
      });

      // store.dispatch({
      //   type: 'PATCHES',
      //   payload: patches.map((patch: any, index: number) => ({
      //     patch: patch,
      //     inversePatch: inversePatches[index],
      //   })),
      // });
    },
    store.dispatch,
  ];
}
Example #4
Source File: model.ts    From reactant with MIT License 5 votes vote down vote up
model = <
  S extends Record<string, any>,
  A extends Record<string, (...args: any[]) => (state: S) => void>
>(
  scheme: Scheme<S, A>
): Actions<A> & Service<S> & S => {
  let module: Service<S>;
  Object.keys(scheme.actions).forEach((key) => {
    const fn = scheme.actions[key];
    Object.assign(scheme.actions, {
      [key]: (...args: any[]) => {
        let state: Immutable<S> | S | undefined;
        let patches: Patch[] | undefined;
        let inversePatches: Patch[] | undefined;
        if (module[enablePatchesKey]) {
          [state, patches, inversePatches] = produceWithPatches(
            module[stateKey],
            (draftState: S) => {
              fn(...args)(draftState);
            }
          );
        } else {
          state = produce(module[stateKey], (draftState: S) => {
            fn(...args)(draftState);
          });
        }
        const lastState = module[storeKey]?.getState();
        module[storeKey]!.dispatch({
          type: module[identifierKey],
          method: key,
          state: {
            ...lastState,
            [module[identifierKey]!]: state,
          },
          _reactant: actionIdentifier,
          ...(module[enablePatchesKey]
            ? {
                _patches: patches,
                _inversePatches: inversePatches,
              }
            : {}),
        });
      },
    });
  });
  module = {
    [nameKey]: scheme.name,
    [stateKey]: {
      ...scheme.state,
    },
    ...scheme.actions,
  };
  return module as any;
}
Example #5
Source File: component.ts    From textbus with GNU General Public License v3.0 4 votes vote down vote up
/**
 * Textbus 扩展组件方法
 * @param options
 */
export function defineComponent<Extends extends ComponentExtends,
  State = any>(options: ComponentOptions<Extends, State>): Component<ComponentInstance<Extends, State>, ComponentData<State>> {
  return {
    name: options.name,
    markdownSupport: options.markdownSupport,
    createInstance(contextInjector: Injector, initData?: ComponentData<State>) {
      const marker = new ChangeMarker()
      const stateChangeSubject = new Subject<any>()

      const onStateChange = stateChangeSubject.asObservable()

      const changeController: ChangeController<State> = {
        update(fn) {
          return componentInstance.updateState(fn)
        },
        onChange: onStateChange
      }

      const componentInstance: ComponentInstance<Extends, State> = {
        changeMarker: marker,
        parent: null,
        get parentComponent() {
          return componentInstance.parent?.parent || null
        },
        get state() {
          return state!
        },
        name: options.name,
        length: 1,
        onStateChange,
        type: options.type,
        slots: null as any,
        extends: null as any,
        shortcutList: null as any,
        updateState(fn) {
          let changes!: Patch[]
          let inverseChanges!: Patch[]
          const oldState = state
          const newState = produce(oldState, fn, (p, ip) => {
            changes = p
            inverseChanges = ip
          }) as State
          state = newState
          stateChangeSubject.next(newState)
          marker.markAsDirtied({
            path: [],
            apply: [{
              type: 'apply',
              patches: changes!,
              value: newState
            }],
            unApply: [{
              type: 'apply',
              patches: inverseChanges!,
              value: oldState
            }]
          })
          return newState
        },
        toJSON() {
          return {
            name: options.name,
            state: state!,
            slots: componentInstance.slots.toJSON()
          }
        },
        toString() {
          return componentInstance.slots.toString()
        }
      }
      const context: ComponentContext<State> = {
        contextInjector,
        changeController,
        slots: new Slots(componentInstance),
        componentInstance: componentInstance,
        dynamicShortcut: [],
        eventCache: new EventCache<EventTypes>(),
      }
      contextStack.push(context)
      componentInstance.extends = options.setup(initData)
      onDestroy(() => {
        eventCacheMap.delete(componentInstance)
        subscriptions.forEach(i => i.unsubscribe())
      })
      eventCacheMap.set(componentInstance, context.eventCache)
      contextStack.pop()
      componentInstance.slots = context.slots
      componentInstance.shortcutList = context.dynamicShortcut
      let state = Reflect.has(context, 'initState') ? context.initState : initData?.state

      const subscriptions: Subscription[] = [
        context.slots.onChange.subscribe(ops => {
          marker.markAsDirtied(ops)
        }),

        context.slots.onChildComponentRemoved.subscribe(instance => {
          marker.recordComponentRemoved(instance)
        }),

        context.slots.onChildSlotChange.subscribe(d => {
          marker.markAsDirtied(d.operation)
        }),

        context.slots.onChildSlotForceChange.subscribe(() => {
          marker.forceMarkDirtied()
        })
      ]

      return componentInstance
    }
  }
}
Example #6
Source File: action.ts    From reactant with MIT License 4 votes vote down vote up
action = (
  target: object,
  key: string,
  descriptor: TypedPropertyDescriptor<(...args: any[]) => void>
) => {
  const fn = descriptor.value;
  if (typeof fn !== 'function') {
    throw new Error(
      `${String(key)} can only be decorated by '@action' as a class method.`
    );
  }
  const value = function (this: Service, ...args: unknown[]) {
    if (typeof this[storeKey] === 'undefined') {
      throw new Error(
        `'this' in method '${key.toString()}' of class '${
          target.constructor.name
        }' decorated by '@action' must be bound to the current class instance.`
      );
    }
    let time: number;
    if (__DEV__) {
      time = Date.now();
    }
    if (typeof stagedState === 'undefined') {
      try {
        const lastState = this[storeKey]?.getState();
        let state: Record<string, unknown>;
        let patches: Patch[] | undefined;
        let inversePatches: Patch[] | undefined;
        if (this[enablePatchesKey]) {
          [state, patches, inversePatches] = produceWithPatches<
            Record<string, unknown>
          >(lastState, (draftState) => {
            stagedState = draftState;
            fn.apply(this, args);
          });
        } else {
          state = produce<Record<string, unknown>>(lastState, (draftState) => {
            stagedState = draftState;
            fn.apply(this, args);
          });
        }
        stagedState = undefined;
        if (__DEV__) {
          if (lastState === state) {
            console.warn(`There are no state updates to method '${fn.name}'`);
          }
          // performance checking
          const executionTime = Date.now() - time!;
          if (executionTime > 100)
            console.warn(
              `The execution time of method '${this[
                identifierKey
              ]?.toString()}.${key.toString()}' is ${executionTime} ms, it's recommended to use 'dispatch()' API.`
            );
          // performance detail: https://immerjs.github.io/immer/docs/performance
        }
        this[storeKey]!.dispatch<ReactantAction>({
          type: this[identifierKey]!,
          method: key,
          params: args,
          state,
          _reactant: actionIdentifier,
          ...(this[enablePatchesKey]
            ? {
                _patches: patches,
                _inversePatches: inversePatches,
              }
            : {}),
        });
      } finally {
        stagedState = undefined;
      }
    } else {
      // enable staged state mode.
      fn.apply(this, args);
    }
  };
  return {
    ...descriptor,
    value,
  };
}