Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions examples/ts-counter/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,12 @@ class AppView extends ViewModule {
const app = createApp({
main: AppView,
render,
// devOptions: {
// autoComputed: true,
// },
devOptions: {
autoComputed: true,
checkAction: (options) => {
console.log('checkAction', options);
},
},
});

app.bootstrap(document.getElementById('root'));
1 change: 1 addition & 0 deletions packages/reactant-module/src/constants/reduxKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export const enableAutoComputedKey: unique symbol =
export const enableAutoFreezeKey: unique symbol = Symbol('enableAutoFreeze');
export const strictKey: unique symbol = Symbol('strict');
export const enableInspectorKey: unique symbol = Symbol('enableInspector');
export const checkActionKey: unique symbol = Symbol('checkAction');
export const actionIdentifier = 'REACTANT_ACTION' as const;
9 changes: 8 additions & 1 deletion packages/reactant-module/src/core/createStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
strictKey,
enableAutoComputedKey,
signalMapKey,
checkActionKey,
} from '../constants';
import { getStagedState } from '../decorators';
import type {
Expand Down Expand Up @@ -103,7 +104,7 @@ export function createStore<T = any>({
const enablePatches = devOptions.enablePatches ?? false;
const enableInspector = devOptions.enableInspector ?? false;
const strict = devOptions.strict ?? false;

const checkAction = devOptions.checkAction;
const _pushPluginHooks = generatePluginHooks();
const _assignPluginHooks = generatePluginHooks();

Expand Down Expand Up @@ -435,6 +436,12 @@ export function createStore<T = any>({
configurable: false,
value: enableInspector,
},
// check action method
[checkActionKey]: {
enumerable: false,
configurable: false,
value: checkAction,
},
});
} catch (e) {
console.error(
Expand Down
27 changes: 26 additions & 1 deletion packages/reactant-module/src/core/getRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import {
containerKey,
stateKey,
initStateKey,
enablePatchesKey,
enableAutoFreezeKey,
strictKey,
enableInspectorKey,
checkActionKey,
} from '../constants';
import type { Service } from '../interfaces';
import type { DevOptions, Service } from '../interfaces';

export interface Ref {
readonly store?: ReduxStore;
Expand All @@ -17,6 +22,11 @@ export interface Ref {
readonly identifier?: string;
readonly state?: Record<string, any>;
readonly initState?: Record<string, any>;
readonly enablePatches?: boolean;
readonly enableAutoFreeze?: boolean;
readonly strict?: boolean;
readonly enableInspector?: boolean;
readonly checkAction?: DevOptions['checkAction'];
}

/**
Expand All @@ -43,5 +53,20 @@ export const getRef = (instance: object): Ref => {
get initState() {
return _instance[initStateKey];
},
get enablePatches() {
return _instance[enablePatchesKey];
},
get enableAutoFreeze() {
return _instance[enableAutoFreezeKey];
},
get strict() {
return _instance[strictKey];
},
get enableInspector() {
return _instance[enableInspectorKey];
},
get checkAction() {
return _instance[checkActionKey];
},
};
};
50 changes: 27 additions & 23 deletions packages/reactant-module/src/decorators/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,8 @@
/* eslint-disable func-names */
import { create } from 'mutative';
import { Patches, ReactantAction, Service } from '../interfaces';
import {
storeKey,
actionIdentifier,
enablePatchesKey,
enableAutoFreezeKey,
identifierKey,
enableInspectorKey,
strictKey,
} from '../constants';
import { actionIdentifier } from '../constants';
import { getRef } from '../core';

let stagedState: Record<string, unknown> | undefined;

Expand Down Expand Up @@ -56,20 +49,33 @@ const action = (
);
}
const value = function (this: Service, ...args: unknown[]) {
if (typeof this[storeKey] === 'undefined') {
const ref = getRef(this);
if (typeof ref.store === '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.`
);
}
if (typeof ref.checkAction === 'function') {
ref.checkAction({
target: this,
ref,
method: key,
args,
});
} else if (__DEV__ && ref.checkAction !== undefined) {
throw new Error(
`The method '${key}' decorated by '@action' must be checked by 'checkAction' option function.`
);
}
if (typeof stagedState === 'undefined') {
try {
const lastState = this[storeKey]?.getState();
const lastState = ref.store.getState();
let state: Record<string, unknown>;
let patches: Patches | undefined;
let inversePatches: Patches | undefined;
if (this[enablePatchesKey]) {
if (ref.enablePatches) {
[state, patches, inversePatches] = create(
lastState,
(draftState) => {
Expand All @@ -83,8 +89,8 @@ const action = (
},
{
enablePatches: true,
strict: this[strictKey],
enableAutoFreeze: this[enableAutoFreezeKey],
strict: ref.strict,
enableAutoFreeze: ref.enableAutoFreeze,
}
);
} else {
Expand All @@ -100,29 +106,27 @@ const action = (
}
},
{
strict: this[strictKey],
enableAutoFreeze: this[enableAutoFreezeKey],
strict: ref.strict,
enableAutoFreeze: ref.enableAutoFreeze,
}
);
}
stagedState = undefined;
if (__DEV__) {
const methodName = `${this[
identifierKey
]?.toString()}.${key.toString()}`;
if (this[enableInspectorKey] && lastState === state) {
const methodName = `${ref.identifier}.${key.toString()}`;
if (ref.enableInspector && lastState === state) {
console.warn(
`There are no state updates to method '${methodName}'`
);
}
}
this[storeKey]!.dispatch<ReactantAction>({
type: this[identifierKey]!,
ref.store.dispatch<ReactantAction>({
type: ref.identifier!,
method: key,
params: args,
state,
_reactant: actionIdentifier,
...(this[enablePatchesKey]
...(ref.enablePatches
? {
_patches: patches,
_inversePatches: inversePatches,
Expand Down
13 changes: 12 additions & 1 deletion packages/reactant-module/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ import {
strictKey,
enableAutoComputedKey,
signalMapKey,
checkActionKey,
} from './constants';
import { PluginModule } from './core';
import { PluginModule, Ref } from './core';
import type { Signal } from './core/signal';

export type Patch = IPatch<true>;
Expand Down Expand Up @@ -72,6 +73,15 @@ export interface DevOptions {
* Redux dev tools enhancer options.
*/
reduxDevToolsOptions?: ReduxDevToolsOptions;
/**
* Check action method.
*/
checkAction?: (options: {
target: Service;
ref: Ref;
method: string;
args: any[];
}) => void;
}

export type ReduxDevToolsOptions = Pick<
Expand Down Expand Up @@ -99,6 +109,7 @@ export interface Service<T extends Record<string, any> = Record<string, any>> {
readonly [containerKey]?: Container;
readonly [modulesKey]?: Record<string, any>;
readonly [initStateKey]?: Record<string, any>;
readonly [checkActionKey]?: DevOptions['checkAction'];
[identifierKey]?: string;
[nameKey]?: string;
}
Expand Down
84 changes: 84 additions & 0 deletions packages/reactant-module/test/decorators/action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -636,4 +636,88 @@ describe('@action', () => {
);
warn.mockReset();
});

test('base with `checkAction`', () => {
interface Todo {
text: string;
}
@injectable({
name: 'todo',
})
class TodoList {
@state
list: Todo[] = [
{
text: 'foo',
},
];

@action
add(text: string) {
this.list.slice(-1)[0].text = text;
this.list.push({ text });
}

@action
noChange() {
this.list.slice(-1)[0].text = this.list.slice(-1)[0].text;
}
}

const actionFn = jest.fn();

const middleware: Middleware = (store) => (next) => (_action) => {
actionFn(_action);
return next(_action);
};

const ServiceIdentifiers = new Map();
const modules = [TodoList, applyMiddleware(middleware)];
const container = createContainer({
ServiceIdentifiers,
modules,
options: {
defaultScope: 'Singleton',
},
});
const todoList = container.get(TodoList);
const fn = jest.fn();
const store = createStore({
modules,
container,
ServiceIdentifiers,
loadedModules: new Set(),
load: (...args: any[]) => {
//
},
dynamicModules: new Map(),
pluginHooks: {
middleware: [],
beforeCombineRootReducers: [],
afterCombineRootReducers: [],
enhancer: [],
preloadedStateHandler: [],
afterCreateStore: [],
provider: [],
},
devOptions: {
checkAction: fn,
},
});
todoList.noChange();
expect(fn).toBeCalledTimes(1);
const options = fn.mock.calls[0][0];
expect(options.target).toEqual(todoList);
expect(options.ref.store).toEqual(store);
expect(options.ref.container).toEqual(container);
expect(options.ref.identifier).toEqual('todo');
expect(options.ref.state).toEqual({ list: [{ text: 'foo' }] });
expect(options.ref.initState).toEqual({ list: [{ text: 'foo' }] });
expect(options.ref.strict).toEqual(false);
expect(options.ref.enablePatches).toEqual(false);
expect(options.ref.enableInspector).toEqual(false);
expect(options.ref.checkAction).toEqual(fn);
expect(options.method).toEqual('noChange');
expect(options.args).toEqual([]);
});
});
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10458,9 +10458,9 @@ picocolors@^1.1.0:
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==

picomatch@^2.0.4:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==

picomatch@^2.2.3:
version "2.3.0"
Expand Down