From 2bee4ddb2d59fa5bb84e8869622ab551e5d644a8 Mon Sep 17 00:00:00 2001
From: Zheng Song <41896553+szhsin@users.noreply.github.com>
Date: Thu, 22 Dec 2022 00:33:50 +1100
Subject: [PATCH 1/8] Counter example to use reducer
---
examples/examples/counter/Counter.tsx | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/examples/examples/counter/Counter.tsx b/examples/examples/counter/Counter.tsx
index 5879ec0..dede71d 100644
--- a/examples/examples/counter/Counter.tsx
+++ b/examples/examples/counter/Counter.tsx
@@ -3,6 +3,17 @@ import { createState, selector, useSnapshot } from 'reactish-state';
import { applyMiddleware, persist, reduxDevtools } from 'reactish-state/middleware';
import styles from './styles.module.css';
+type ActionTypes = 'INCREASE' | 'DECREASE';
+
+const reducer = (state: number, { type, by = 1 }: { type: ActionTypes; by?: number }) => {
+ switch (type) {
+ case 'INCREASE':
+ return state + by;
+ case 'DECREASE':
+ return state - by;
+ }
+};
+
const persistMiddleware = persist({ prefix: 'counter-', getStorage: () => sessionStorage });
const counterState = createState({
@@ -12,7 +23,9 @@ const counterState = createState({
(set, get) => ({
increase: () => set((i) => i + 1),
increaseBy: (by: number) => set(get() + by),
- reset: () => set(0)
+ reset: () => set(0),
+ dispatch: (action: { type: ActionTypes; by?: number }) =>
+ set((state) => reducer(state, action), action)
}),
{ key: 'count' }
);
@@ -34,7 +47,7 @@ const Counter = ({ id = 1 }: { id: number | string }) => {
const [step, setStep] = useState(1);
const count = useSnapshot(counterState);
const summary = useSnapshot(countSummary);
- const { increase, increaseBy, reset } = counterState.actions;
+ const { increase, increaseBy, reset, dispatch } = counterState.actions;
console.log(`#${id} count: ${count}`, 'summary:', summary);
@@ -57,6 +70,8 @@ const Counter = ({ id = 1 }: { id: number | string }) => {
+
+
From d74064e1a9e886227f3c0760f6a2b3424c73d773 Mon Sep 17 00:00:00 2001
From: Zheng Song <41896553+szhsin@users.noreply.github.com>
Date: Thu, 22 Dec 2022 19:47:39 +1100
Subject: [PATCH 2/8] selector to support plugin
---
src/common.ts | 4 ++++
src/index.ts | 2 +-
src/vanilla/selector.ts | 52 ++++++++++++++++++++++++-----------------
3 files changed, 36 insertions(+), 22 deletions(-)
diff --git a/src/common.ts b/src/common.ts
index be9b083..a007077 100644
--- a/src/common.ts
+++ b/src/common.ts
@@ -17,3 +17,7 @@ export interface Config {
export interface Middleware {
(set: Setter, get: Getter, config?: Config): Setter;
}
+
+export interface Plugin {
+ (reactish: Reactish): void;
+}
diff --git a/src/index.ts b/src/index.ts
index 81a260d..141e719 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,3 @@
export { state, createState } from './vanilla/state';
-export { selector } from './vanilla/selector';
+export { selector, createSelector } from './vanilla/selector';
export { useSnapshot } from './react/useSnapshot';
diff --git a/src/vanilla/selector.ts b/src/vanilla/selector.ts
index f827a43..0793708 100644
--- a/src/vanilla/selector.ts
+++ b/src/vanilla/selector.ts
@@ -1,10 +1,14 @@
-import type { Reactish, Listener } from '../common';
+import type { Reactish, Listener, Plugin, Config } from '../common';
type ReactishArray = Reactish[];
type ReactishValueArray = {
[index in keyof R]: ReturnType;
};
type SelectorFunc = (...args: ReactishValueArray) => T;
+interface Selector {
+ (...items: [...R, SelectorFunc]): Reactish;
+ (...items: [...R, SelectorFunc, Config]): Reactish;
+}
const isEqual = (args1: unknown[], args2: unknown[]) => {
for (let i = 0; i < args1.length; i++) {
@@ -13,25 +17,31 @@ const isEqual = (args1: unknown[], args2: unknown[]) => {
return true;
};
-const selector = (...items: [...R, SelectorFunc]) => {
- const lastIndex = items.length - 1;
- const selectorFunc = items[lastIndex] as SelectorFunc;
- items.length = lastIndex;
- let cache: { args: unknown[]; ret: T } | undefined;
+const createSelector = ({ plugin }: { plugin?: Plugin } = {}) =>
+ ((...items: [...R, SelectorFunc, Config?]) => {
+ const lastIndex = items.length - 1;
+ const selectorFunc = items[lastIndex] as SelectorFunc;
+ items.length = lastIndex;
+ let cache: { args: unknown[]; ret: T } | undefined;
- return {
- get: () => {
- const args = (items as ReactishArray).map((item) => item.get()) as ReactishValueArray;
- if (cache && isEqual(args, cache.args)) return cache.ret;
- const ret = selectorFunc(...args);
- cache = { args, ret };
- return ret;
- },
- subscribe: (listener: Listener) => {
- const unsubscribers = (items as ReactishArray).map((item) => item.subscribe(listener));
- return () => unsubscribers.forEach((unsubscribe) => unsubscribe());
- }
- };
-};
+ const selector = {
+ get: () => {
+ const args = (items as ReactishArray).map((item) => item.get()) as ReactishValueArray;
+ if (cache && isEqual(args, cache.args)) return cache.ret;
+ const ret = selectorFunc(...args);
+ cache = { args, ret };
+ return ret;
+ },
+ subscribe: (listener: Listener) => {
+ const unsubscribers = (items as ReactishArray).map((item) => item.subscribe(listener));
+ return () => unsubscribers.forEach((unsubscribe) => unsubscribe());
+ }
+ };
+
+ plugin?.(selector);
+ return selector;
+ }) as Selector;
+
+const selector = createSelector();
-export { selector };
+export { selector, createSelector };
From a622f5d09834010c9dc5d7ecb03a6a65534e57e0 Mon Sep 17 00:00:00 2001
From: Zheng Song <41896553+szhsin@users.noreply.github.com>
Date: Thu, 22 Dec 2022 20:38:31 +1100
Subject: [PATCH 3/8] Add the config parameter to selector
---
src/common.ts | 2 +-
src/vanilla/selector.ts | 12 +++++++-----
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/src/common.ts b/src/common.ts
index a007077..65cc36c 100644
--- a/src/common.ts
+++ b/src/common.ts
@@ -19,5 +19,5 @@ export interface Middleware {
}
export interface Plugin {
- (reactish: Reactish): void;
+ (reactish: Reactish, config?: Config): void;
}
diff --git a/src/vanilla/selector.ts b/src/vanilla/selector.ts
index 0793708..43709e2 100644
--- a/src/vanilla/selector.ts
+++ b/src/vanilla/selector.ts
@@ -18,10 +18,12 @@ const isEqual = (args1: unknown[], args2: unknown[]) => {
};
const createSelector = ({ plugin }: { plugin?: Plugin } = {}) =>
- ((...items: [...R, SelectorFunc, Config?]) => {
- const lastIndex = items.length - 1;
- const selectorFunc = items[lastIndex] as SelectorFunc;
- items.length = lastIndex;
+ ((...items: unknown[]) => {
+ const { length } = items;
+ const cutoff = typeof items[length - 1] === 'function' ? length - 1 : length - 2;
+ const selectorFunc = items[cutoff] as SelectorFunc;
+ const config = items[cutoff + 1] as Config | undefined;
+ items.length = cutoff;
let cache: { args: unknown[]; ret: T } | undefined;
const selector = {
@@ -38,7 +40,7 @@ const createSelector = ({ plugin }: { plugin?: Plugin } = {}) =>
}
};
- plugin?.(selector);
+ plugin?.(selector, config);
return selector;
}) as Selector;
From 69378a2c0fccded17f40b407e76444832d6b5983 Mon Sep 17 00:00:00 2001
From: Zheng Song <41896553+szhsin@users.noreply.github.com>
Date: Thu, 22 Dec 2022 22:01:40 +1100
Subject: [PATCH 4/8] Add rollup build for plugin
---
plugin/package.json | 7 +++++++
rollup.config.mjs | 2 +-
src/middleware/reduxDevtools.ts | 4 +---
src/plugin/applyPlugin.ts | 8 ++++++++
4 files changed, 17 insertions(+), 4 deletions(-)
create mode 100644 plugin/package.json
create mode 100644 src/plugin/applyPlugin.ts
diff --git a/plugin/package.json b/plugin/package.json
new file mode 100644
index 0000000..f1b175d
--- /dev/null
+++ b/plugin/package.json
@@ -0,0 +1,7 @@
+{
+ "private": true,
+ "sideEffects": false,
+ "main": "../dist/plugin/cjs/index.js",
+ "module": "../dist/plugin/es/index.js",
+ "types": "../types/plugin/index.d.ts"
+}
diff --git a/rollup.config.mjs b/rollup.config.mjs
index e181ee2..d0cd342 100644
--- a/rollup.config.mjs
+++ b/rollup.config.mjs
@@ -29,4 +29,4 @@ const createBuild = (path = '') => ({
]
});
-export default [createBuild(), createBuild('middleware/')];
+export default [createBuild(), createBuild('middleware/'), createBuild('plugin/')];
diff --git a/src/middleware/reduxDevtools.ts b/src/middleware/reduxDevtools.ts
index 84ac119..57cd6fe 100644
--- a/src/middleware/reduxDevtools.ts
+++ b/src/middleware/reduxDevtools.ts
@@ -4,9 +4,7 @@ import type { Middleware } from '../common';
const reduxDevtools: Middleware = (set, get, config) => {
if (typeof window === 'undefined' || !window.__REDUX_DEVTOOLS_EXTENSION__) return set;
- const devtools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({
- name: config?.key
- });
+ const devtools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({ name: config?.key });
devtools.init(get());
return function (value, action) {
diff --git a/src/plugin/applyPlugin.ts b/src/plugin/applyPlugin.ts
new file mode 100644
index 0000000..a57b0b4
--- /dev/null
+++ b/src/plugin/applyPlugin.ts
@@ -0,0 +1,8 @@
+import type { Plugin } from '../common';
+
+const applyPlugin: (...plugins: Plugin[]) => Plugin =
+ (...plugins) =>
+ (...args) =>
+ plugins.forEach((plugin) => plugin(...args));
+
+export { applyPlugin };
From 9b69776487f0ab48877ee7f2cba4f20bfad9f342 Mon Sep 17 00:00:00 2001
From: Zheng Song <41896553+szhsin@users.noreply.github.com>
Date: Thu, 22 Dec 2022 22:02:09 +1100
Subject: [PATCH 5/8] Create reduxDevtools plugin
---
src/plugin/index.ts | 2 ++
src/plugin/reduxDevtools.ts | 13 +++++++++++++
2 files changed, 15 insertions(+)
create mode 100644 src/plugin/index.ts
create mode 100644 src/plugin/reduxDevtools.ts
diff --git a/src/plugin/index.ts b/src/plugin/index.ts
new file mode 100644
index 0000000..d18525d
--- /dev/null
+++ b/src/plugin/index.ts
@@ -0,0 +1,2 @@
+export { applyPlugin } from './applyPlugin';
+export { reduxDevtools } from './reduxDevtools';
diff --git a/src/plugin/reduxDevtools.ts b/src/plugin/reduxDevtools.ts
new file mode 100644
index 0000000..d01218a
--- /dev/null
+++ b/src/plugin/reduxDevtools.ts
@@ -0,0 +1,13 @@
+import type {} from '@redux-devtools/extension';
+import type { Plugin } from '../common';
+
+const reduxDevtools: Plugin = ({ get, subscribe }, config) => {
+ if (typeof window === 'undefined' || !window.__REDUX_DEVTOOLS_EXTENSION__) return;
+
+ console.log('config?.key', config?.key);
+ const devtools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({ name: config?.key });
+ devtools.init(get());
+ subscribe(() => devtools.init(get()));
+};
+
+export { reduxDevtools };
From 4a0ced3c7b0fa7e1c66c40b3a1aa2899be34e85d Mon Sep 17 00:00:00 2001
From: Zheng Song <41896553+szhsin@users.noreply.github.com>
Date: Thu, 22 Dec 2022 22:43:24 +1100
Subject: [PATCH 6/8] Add `subscribe` to middleware api
---
src/common.ts | 5 +++--
src/middleware/applyMiddleware.ts | 4 ++--
src/middleware/immer.ts | 2 +-
src/middleware/persist.ts | 2 +-
src/middleware/reduxDevtools.ts | 2 +-
src/plugin/reduxDevtools.ts | 1 -
src/vanilla/state.ts | 17 +++++++++--------
7 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/src/common.ts b/src/common.ts
index 65cc36c..446cac9 100644
--- a/src/common.ts
+++ b/src/common.ts
@@ -4,10 +4,11 @@ export type Setter = (
action?: string | { type: string; [key: string]: unknown }
) => void;
export type Listener = () => void;
+export type Subscriber = (listener: Listener) => () => void;
export interface Reactish {
get: Getter;
- subscribe: (listener: Listener) => () => void;
+ subscribe: Subscriber;
}
export interface Config {
@@ -15,7 +16,7 @@ export interface Config {
}
export interface Middleware {
- (set: Setter, get: Getter, config?: Config): Setter;
+ (api: Reactish & { set: Setter }, config?: Config): Setter;
}
export interface Plugin {
diff --git a/src/middleware/applyMiddleware.ts b/src/middleware/applyMiddleware.ts
index 38294c6..dbc2f4f 100644
--- a/src/middleware/applyMiddleware.ts
+++ b/src/middleware/applyMiddleware.ts
@@ -2,7 +2,7 @@ import type { Middleware } from '../common';
const applyMiddleware: (...middlewares: Middleware[]) => Middleware =
(...middlewares) =>
- (set, get, config) =>
- middlewares.reduceRight((prev, curr) => curr(prev, get, config), set);
+ (api, config) =>
+ middlewares.reduceRight((set, middleware) => middleware({ ...api, set }, config), api.set);
export { applyMiddleware };
diff --git a/src/middleware/immer.ts b/src/middleware/immer.ts
index d65d9ea..cf6cb6a 100644
--- a/src/middleware/immer.ts
+++ b/src/middleware/immer.ts
@@ -2,7 +2,7 @@ import { produce } from 'immer';
import type { Middleware } from '../common';
const immer: Middleware =
- (set) =>
+ ({ set }) =>
(value, ...rest) =>
set(typeof value === 'function' ? produce(value as (draft: unknown) => void) : value, ...rest);
diff --git a/src/middleware/persist.ts b/src/middleware/persist.ts
index c1d3f4e..d08b947 100644
--- a/src/middleware/persist.ts
+++ b/src/middleware/persist.ts
@@ -12,7 +12,7 @@ type Persist = (options?: {
const persist: Persist = ({ prefix, getStorage = () => localStorage } = {}) => {
const states: [string, Setter][] = [];
- const middleware: PersistMiddleware = (set, get, config) => {
+ const middleware: PersistMiddleware = ({ set, get }, config) => {
let key = config?.key || '';
if (!key)
throw new Error(
diff --git a/src/middleware/reduxDevtools.ts b/src/middleware/reduxDevtools.ts
index 57cd6fe..e60c698 100644
--- a/src/middleware/reduxDevtools.ts
+++ b/src/middleware/reduxDevtools.ts
@@ -1,7 +1,7 @@
import type {} from '@redux-devtools/extension';
import type { Middleware } from '../common';
-const reduxDevtools: Middleware = (set, get, config) => {
+const reduxDevtools: Middleware = ({ set, get }, config) => {
if (typeof window === 'undefined' || !window.__REDUX_DEVTOOLS_EXTENSION__) return set;
const devtools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({ name: config?.key });
diff --git a/src/plugin/reduxDevtools.ts b/src/plugin/reduxDevtools.ts
index d01218a..6c892ea 100644
--- a/src/plugin/reduxDevtools.ts
+++ b/src/plugin/reduxDevtools.ts
@@ -4,7 +4,6 @@ import type { Plugin } from '../common';
const reduxDevtools: Plugin = ({ get, subscribe }, config) => {
if (typeof window === 'undefined' || !window.__REDUX_DEVTOOLS_EXTENSION__) return;
- console.log('config?.key', config?.key);
const devtools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({ name: config?.key });
devtools.init(get());
subscribe(() => devtools.init(get()));
diff --git a/src/vanilla/state.ts b/src/vanilla/state.ts
index 1b6852a..52ad647 100644
--- a/src/vanilla/state.ts
+++ b/src/vanilla/state.ts
@@ -1,4 +1,4 @@
-import type { Setter, Reactish, Listener, Config, Middleware } from '../common';
+import type { Reactish, Setter, Listener, Subscriber, Config, Middleware } from '../common';
type ActionCreator = ((set: Setter, get: () => T) => A) | null | undefined;
@@ -24,17 +24,18 @@ const createState =
});
}
};
- if (middleware) set = middleware(set, get, config);
+ const subscribe: Subscriber = (listener) => {
+ listeners.add(listener);
+ return () => {
+ listeners.delete(listener);
+ };
+ };
+ if (middleware) set = middleware({ set, get, subscribe }, config);
return {
get,
set,
- subscribe: (listener) => {
- listeners.add(listener);
- return () => {
- listeners.delete(listener);
- };
- },
+ subscribe,
actions: actionCreator && actionCreator(set, get)
} as State>;
};
From a4e1a49d782eebc5278dbc0d762b9b8e065ccc0d Mon Sep 17 00:00:00 2001
From: Zheng Song <41896553+szhsin@users.noreply.github.com>
Date: Thu, 22 Dec 2022 22:44:33 +1100
Subject: [PATCH 7/8] Update build artefact
---
dist/cjs/index.js | 89 +++++++++++--------
dist/es/index.js | 2 +-
dist/es/vanilla/selector.js | 71 ++++++++-------
dist/es/vanilla/state.js | 19 ++--
dist/middleware/cjs/index.js | 42 ++++++---
.../es/_virtual/_rollupPluginBabelHelpers.js | 16 ++++
dist/middleware/es/applyMiddleware.js | 12 ++-
dist/middleware/es/immer.js | 3 +-
dist/middleware/es/persist.js | 10 ++-
dist/middleware/es/reduxDevtools.js | 4 +-
dist/plugin/cjs/index.js | 31 +++++++
dist/plugin/es/applyPlugin.js | 15 ++++
dist/plugin/es/index.js | 2 +
dist/plugin/es/reduxDevtools.js | 14 +++
types/common.d.ts | 10 ++-
types/index.d.ts | 2 +-
types/plugin/applyPlugin.d.ts | 3 +
types/plugin/index.d.ts | 2 +
types/plugin/reduxDevtools.d.ts | 3 +
types/vanilla/selector.d.ts | 16 ++--
types/vanilla/state.d.ts | 2 +-
21 files changed, 262 insertions(+), 106 deletions(-)
create mode 100644 dist/middleware/es/_virtual/_rollupPluginBabelHelpers.js
create mode 100644 dist/plugin/cjs/index.js
create mode 100644 dist/plugin/es/applyPlugin.js
create mode 100644 dist/plugin/es/index.js
create mode 100644 dist/plugin/es/reduxDevtools.js
create mode 100644 types/plugin/applyPlugin.d.ts
create mode 100644 types/plugin/index.d.ts
create mode 100644 types/plugin/reduxDevtools.d.ts
diff --git a/dist/cjs/index.js b/dist/cjs/index.js
index c65bdf7..29ec6cd 100644
--- a/dist/cjs/index.js
+++ b/dist/cjs/index.js
@@ -20,16 +20,21 @@ var createState = function createState(_temp) {
});
}
};
- if (middleware) set = middleware(set, get, config);
+ var subscribe = function subscribe(listener) {
+ listeners.add(listener);
+ return function () {
+ listeners["delete"](listener);
+ };
+ };
+ if (middleware) set = middleware({
+ set: set,
+ get: get,
+ subscribe: subscribe
+ }, config);
return {
get: get,
set: set,
- subscribe: function subscribe(listener) {
- listeners.add(listener);
- return function () {
- listeners["delete"](listener);
- };
- },
+ subscribe: subscribe,
actions: actionCreator && actionCreator(set, get)
};
};
@@ -42,44 +47,54 @@ var isEqual = function isEqual(args1, args2) {
}
return true;
};
-var selector = function selector() {
- for (var _len = arguments.length, items = new Array(_len), _key = 0; _key < _len; _key++) {
- items[_key] = arguments[_key];
- }
- var lastIndex = items.length - 1;
- var selectorFunc = items[lastIndex];
- items.length = lastIndex;
- var cache;
- return {
- get: function get() {
- var args = items.map(function (item) {
- return item.get();
- });
- if (cache && isEqual(args, cache.args)) return cache.ret;
- var ret = selectorFunc.apply(void 0, args);
- cache = {
- args: args,
- ret: ret
- };
- return ret;
- },
- subscribe: function subscribe(listener) {
- var unsubscribers = items.map(function (item) {
- return item.subscribe(listener);
- });
- return function () {
- return unsubscribers.forEach(function (unsubscribe) {
- return unsubscribe();
- });
- };
+var createSelector = function createSelector(_temp) {
+ var _ref = _temp === void 0 ? {} : _temp,
+ plugin = _ref.plugin;
+ return function () {
+ for (var _len = arguments.length, items = new Array(_len), _key = 0; _key < _len; _key++) {
+ items[_key] = arguments[_key];
}
+ var length = items.length;
+ var cutoff = typeof items[length - 1] === 'function' ? length - 1 : length - 2;
+ var selectorFunc = items[cutoff];
+ var config = items[cutoff + 1];
+ items.length = cutoff;
+ var cache;
+ var selector = {
+ get: function get() {
+ var args = items.map(function (item) {
+ return item.get();
+ });
+ if (cache && isEqual(args, cache.args)) return cache.ret;
+ var ret = selectorFunc.apply(void 0, args);
+ cache = {
+ args: args,
+ ret: ret
+ };
+ return ret;
+ },
+ subscribe: function subscribe(listener) {
+ var unsubscribers = items.map(function (item) {
+ return item.subscribe(listener);
+ });
+ return function () {
+ return unsubscribers.forEach(function (unsubscribe) {
+ return unsubscribe();
+ });
+ };
+ }
+ };
+ plugin == null ? void 0 : plugin(selector, config);
+ return selector;
};
};
+var selector = /*#__PURE__*/createSelector();
var useSnapshot = function useSnapshot(state) {
return shim.useSyncExternalStore(state.subscribe, state.get, state.get);
};
+exports.createSelector = createSelector;
exports.createState = createState;
exports.selector = selector;
exports.state = state;
diff --git a/dist/es/index.js b/dist/es/index.js
index 6794365..1c03d05 100644
--- a/dist/es/index.js
+++ b/dist/es/index.js
@@ -1,3 +1,3 @@
export { createState, state } from './vanilla/state.js';
-export { selector } from './vanilla/selector.js';
+export { createSelector, selector } from './vanilla/selector.js';
export { useSnapshot } from './react/useSnapshot.js';
diff --git a/dist/es/vanilla/selector.js b/dist/es/vanilla/selector.js
index 97e0349..3a77fd3 100644
--- a/dist/es/vanilla/selector.js
+++ b/dist/es/vanilla/selector.js
@@ -4,38 +4,47 @@ var isEqual = function isEqual(args1, args2) {
}
return true;
};
-var selector = function selector() {
- for (var _len = arguments.length, items = new Array(_len), _key = 0; _key < _len; _key++) {
- items[_key] = arguments[_key];
- }
- var lastIndex = items.length - 1;
- var selectorFunc = items[lastIndex];
- items.length = lastIndex;
- var cache;
- return {
- get: function get() {
- var args = items.map(function (item) {
- return item.get();
- });
- if (cache && isEqual(args, cache.args)) return cache.ret;
- var ret = selectorFunc.apply(void 0, args);
- cache = {
- args: args,
- ret: ret
- };
- return ret;
- },
- subscribe: function subscribe(listener) {
- var unsubscribers = items.map(function (item) {
- return item.subscribe(listener);
- });
- return function () {
- return unsubscribers.forEach(function (unsubscribe) {
- return unsubscribe();
- });
- };
+var createSelector = function createSelector(_temp) {
+ var _ref = _temp === void 0 ? {} : _temp,
+ plugin = _ref.plugin;
+ return function () {
+ for (var _len = arguments.length, items = new Array(_len), _key = 0; _key < _len; _key++) {
+ items[_key] = arguments[_key];
}
+ var length = items.length;
+ var cutoff = typeof items[length - 1] === 'function' ? length - 1 : length - 2;
+ var selectorFunc = items[cutoff];
+ var config = items[cutoff + 1];
+ items.length = cutoff;
+ var cache;
+ var selector = {
+ get: function get() {
+ var args = items.map(function (item) {
+ return item.get();
+ });
+ if (cache && isEqual(args, cache.args)) return cache.ret;
+ var ret = selectorFunc.apply(void 0, args);
+ cache = {
+ args: args,
+ ret: ret
+ };
+ return ret;
+ },
+ subscribe: function subscribe(listener) {
+ var unsubscribers = items.map(function (item) {
+ return item.subscribe(listener);
+ });
+ return function () {
+ return unsubscribers.forEach(function (unsubscribe) {
+ return unsubscribe();
+ });
+ };
+ }
+ };
+ plugin == null ? void 0 : plugin(selector, config);
+ return selector;
};
};
+var selector = /*#__PURE__*/createSelector();
-export { selector };
+export { createSelector, selector };
diff --git a/dist/es/vanilla/state.js b/dist/es/vanilla/state.js
index 4b8aaa8..be7b216 100644
--- a/dist/es/vanilla/state.js
+++ b/dist/es/vanilla/state.js
@@ -16,16 +16,21 @@ var createState = function createState(_temp) {
});
}
};
- if (middleware) set = middleware(set, get, config);
+ var subscribe = function subscribe(listener) {
+ listeners.add(listener);
+ return function () {
+ listeners["delete"](listener);
+ };
+ };
+ if (middleware) set = middleware({
+ set: set,
+ get: get,
+ subscribe: subscribe
+ }, config);
return {
get: get,
set: set,
- subscribe: function subscribe(listener) {
- listeners.add(listener);
- return function () {
- listeners["delete"](listener);
- };
- },
+ subscribe: subscribe,
actions: actionCreator && actionCreator(set, get)
};
};
diff --git a/dist/middleware/cjs/index.js b/dist/middleware/cjs/index.js
index 6ce046e..66d9ae5 100644
--- a/dist/middleware/cjs/index.js
+++ b/dist/middleware/cjs/index.js
@@ -2,18 +2,36 @@
var immer$1 = require('immer');
+function _extends() {
+ _extends = Object.assign ? Object.assign.bind() : function (target) {
+ for (var i = 1; i < arguments.length; i++) {
+ var source = arguments[i];
+ for (var key in source) {
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key];
+ }
+ }
+ }
+ return target;
+ };
+ return _extends.apply(this, arguments);
+}
+
var applyMiddleware = function applyMiddleware() {
for (var _len = arguments.length, middlewares = new Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}
- return function (set, get, config) {
- return middlewares.reduceRight(function (prev, curr) {
- return curr(prev, get, config);
- }, set);
+ return function (api, config) {
+ return middlewares.reduceRight(function (set, middleware) {
+ return middleware(_extends({}, api, {
+ set: set
+ }), config);
+ }, api.set);
};
};
-var immer = function immer(set) {
+var immer = function immer(_ref) {
+ var set = _ref.set;
return function (value) {
for (var _len = arguments.length, rest = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
rest[_key - 1] = arguments[_key];
@@ -30,7 +48,9 @@ var persist = function persist(_temp) {
return localStorage;
} : _ref$getStorage;
var states = [];
- var middleware = function middleware(set, get, config) {
+ var middleware = function middleware(_ref2, config) {
+ var set = _ref2.set,
+ get = _ref2.get;
var key = (config == null ? void 0 : config.key) || '';
if (!key) throw new Error('[reactish-state] state should be provided with a string `key` in the config object when the `persist` middleware is used.');
if (prefix) key = prefix + key;
@@ -41,9 +61,9 @@ var persist = function persist(_temp) {
};
};
middleware.hydrate = function () {
- states.forEach(function (_ref2) {
- var key = _ref2[0],
- set = _ref2[1];
+ states.forEach(function (_ref3) {
+ var key = _ref3[0],
+ set = _ref3[1];
var value = getStorage().getItem(key);
value && set(JSON.parse(value), 'HYDRATE');
});
@@ -52,7 +72,9 @@ var persist = function persist(_temp) {
return middleware;
};
-var reduxDevtools = function reduxDevtools(set, get, config) {
+var reduxDevtools = function reduxDevtools(_ref, config) {
+ var set = _ref.set,
+ get = _ref.get;
if (typeof window === 'undefined' || !window.__REDUX_DEVTOOLS_EXTENSION__) return set;
var devtools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({
name: config == null ? void 0 : config.key
diff --git a/dist/middleware/es/_virtual/_rollupPluginBabelHelpers.js b/dist/middleware/es/_virtual/_rollupPluginBabelHelpers.js
new file mode 100644
index 0000000..5aab82d
--- /dev/null
+++ b/dist/middleware/es/_virtual/_rollupPluginBabelHelpers.js
@@ -0,0 +1,16 @@
+function _extends() {
+ _extends = Object.assign ? Object.assign.bind() : function (target) {
+ for (var i = 1; i < arguments.length; i++) {
+ var source = arguments[i];
+ for (var key in source) {
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key];
+ }
+ }
+ }
+ return target;
+ };
+ return _extends.apply(this, arguments);
+}
+
+export { _extends as extends };
diff --git a/dist/middleware/es/applyMiddleware.js b/dist/middleware/es/applyMiddleware.js
index dd07fda..e622773 100644
--- a/dist/middleware/es/applyMiddleware.js
+++ b/dist/middleware/es/applyMiddleware.js
@@ -1,11 +1,15 @@
+import { extends as _extends } from './_virtual/_rollupPluginBabelHelpers.js';
+
var applyMiddleware = function applyMiddleware() {
for (var _len = arguments.length, middlewares = new Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}
- return function (set, get, config) {
- return middlewares.reduceRight(function (prev, curr) {
- return curr(prev, get, config);
- }, set);
+ return function (api, config) {
+ return middlewares.reduceRight(function (set, middleware) {
+ return middleware(_extends({}, api, {
+ set: set
+ }), config);
+ }, api.set);
};
};
diff --git a/dist/middleware/es/immer.js b/dist/middleware/es/immer.js
index a414ceb..5fa521d 100644
--- a/dist/middleware/es/immer.js
+++ b/dist/middleware/es/immer.js
@@ -1,6 +1,7 @@
import { produce } from 'immer';
-var immer = function immer(set) {
+var immer = function immer(_ref) {
+ var set = _ref.set;
return function (value) {
for (var _len = arguments.length, rest = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
rest[_key - 1] = arguments[_key];
diff --git a/dist/middleware/es/persist.js b/dist/middleware/es/persist.js
index 7118764..762d4db 100644
--- a/dist/middleware/es/persist.js
+++ b/dist/middleware/es/persist.js
@@ -6,7 +6,9 @@ var persist = function persist(_temp) {
return localStorage;
} : _ref$getStorage;
var states = [];
- var middleware = function middleware(set, get, config) {
+ var middleware = function middleware(_ref2, config) {
+ var set = _ref2.set,
+ get = _ref2.get;
var key = (config == null ? void 0 : config.key) || '';
if (!key) throw new Error('[reactish-state] state should be provided with a string `key` in the config object when the `persist` middleware is used.');
if (prefix) key = prefix + key;
@@ -17,9 +19,9 @@ var persist = function persist(_temp) {
};
};
middleware.hydrate = function () {
- states.forEach(function (_ref2) {
- var key = _ref2[0],
- set = _ref2[1];
+ states.forEach(function (_ref3) {
+ var key = _ref3[0],
+ set = _ref3[1];
var value = getStorage().getItem(key);
value && set(JSON.parse(value), 'HYDRATE');
});
diff --git a/dist/middleware/es/reduxDevtools.js b/dist/middleware/es/reduxDevtools.js
index 6b7ad86..bb0e929 100644
--- a/dist/middleware/es/reduxDevtools.js
+++ b/dist/middleware/es/reduxDevtools.js
@@ -1,4 +1,6 @@
-var reduxDevtools = function reduxDevtools(set, get, config) {
+var reduxDevtools = function reduxDevtools(_ref, config) {
+ var set = _ref.set,
+ get = _ref.get;
if (typeof window === 'undefined' || !window.__REDUX_DEVTOOLS_EXTENSION__) return set;
var devtools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({
name: config == null ? void 0 : config.key
diff --git a/dist/plugin/cjs/index.js b/dist/plugin/cjs/index.js
new file mode 100644
index 0000000..295f50d
--- /dev/null
+++ b/dist/plugin/cjs/index.js
@@ -0,0 +1,31 @@
+'use strict';
+
+var applyPlugin = function applyPlugin() {
+ for (var _len = arguments.length, plugins = new Array(_len), _key = 0; _key < _len; _key++) {
+ plugins[_key] = arguments[_key];
+ }
+ return function () {
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
+ return plugins.forEach(function (plugin) {
+ return plugin.apply(void 0, args);
+ });
+ };
+};
+
+var reduxDevtools = function reduxDevtools(_ref, config) {
+ var get = _ref.get,
+ subscribe = _ref.subscribe;
+ if (typeof window === 'undefined' || !window.__REDUX_DEVTOOLS_EXTENSION__) return;
+ var devtools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({
+ name: config == null ? void 0 : config.key
+ });
+ devtools.init(get());
+ subscribe(function () {
+ return devtools.init(get());
+ });
+};
+
+exports.applyPlugin = applyPlugin;
+exports.reduxDevtools = reduxDevtools;
diff --git a/dist/plugin/es/applyPlugin.js b/dist/plugin/es/applyPlugin.js
new file mode 100644
index 0000000..1f1810e
--- /dev/null
+++ b/dist/plugin/es/applyPlugin.js
@@ -0,0 +1,15 @@
+var applyPlugin = function applyPlugin() {
+ for (var _len = arguments.length, plugins = new Array(_len), _key = 0; _key < _len; _key++) {
+ plugins[_key] = arguments[_key];
+ }
+ return function () {
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
+ return plugins.forEach(function (plugin) {
+ return plugin.apply(void 0, args);
+ });
+ };
+};
+
+export { applyPlugin };
diff --git a/dist/plugin/es/index.js b/dist/plugin/es/index.js
new file mode 100644
index 0000000..2c89724
--- /dev/null
+++ b/dist/plugin/es/index.js
@@ -0,0 +1,2 @@
+export { applyPlugin } from './applyPlugin.js';
+export { reduxDevtools } from './reduxDevtools.js';
diff --git a/dist/plugin/es/reduxDevtools.js b/dist/plugin/es/reduxDevtools.js
new file mode 100644
index 0000000..e5163fb
--- /dev/null
+++ b/dist/plugin/es/reduxDevtools.js
@@ -0,0 +1,14 @@
+var reduxDevtools = function reduxDevtools(_ref, config) {
+ var get = _ref.get,
+ subscribe = _ref.subscribe;
+ if (typeof window === 'undefined' || !window.__REDUX_DEVTOOLS_EXTENSION__) return;
+ var devtools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({
+ name: config == null ? void 0 : config.key
+ });
+ devtools.init(get());
+ subscribe(function () {
+ return devtools.init(get());
+ });
+};
+
+export { reduxDevtools };
diff --git a/types/common.d.ts b/types/common.d.ts
index 846b008..b5e53aa 100644
--- a/types/common.d.ts
+++ b/types/common.d.ts
@@ -4,13 +4,19 @@ export declare type Setter = (newValue: T | ((value: T) => T), action?: strin
[key: string]: unknown;
}) => void;
export declare type Listener = () => void;
+export declare type Subscriber = (listener: Listener) => () => void;
export interface Reactish {
get: Getter;
- subscribe: (listener: Listener) => () => void;
+ subscribe: Subscriber;
}
export interface Config {
key?: string;
}
export interface Middleware {
- (set: Setter, get: Getter, config?: Config): Setter;
+ (api: Reactish & {
+ set: Setter;
+ }, config?: Config): Setter;
+}
+export interface Plugin {
+ (reactish: Reactish, config?: Config): void;
}
diff --git a/types/index.d.ts b/types/index.d.ts
index 81a260d..141e719 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -1,3 +1,3 @@
export { state, createState } from './vanilla/state';
-export { selector } from './vanilla/selector';
+export { selector, createSelector } from './vanilla/selector';
export { useSnapshot } from './react/useSnapshot';
diff --git a/types/plugin/applyPlugin.d.ts b/types/plugin/applyPlugin.d.ts
new file mode 100644
index 0000000..f32cd11
--- /dev/null
+++ b/types/plugin/applyPlugin.d.ts
@@ -0,0 +1,3 @@
+import type { Plugin } from '../common';
+declare const applyPlugin: (...plugins: Plugin[]) => Plugin;
+export { applyPlugin };
diff --git a/types/plugin/index.d.ts b/types/plugin/index.d.ts
new file mode 100644
index 0000000..d18525d
--- /dev/null
+++ b/types/plugin/index.d.ts
@@ -0,0 +1,2 @@
+export { applyPlugin } from './applyPlugin';
+export { reduxDevtools } from './reduxDevtools';
diff --git a/types/plugin/reduxDevtools.d.ts b/types/plugin/reduxDevtools.d.ts
new file mode 100644
index 0000000..d5f1df8
--- /dev/null
+++ b/types/plugin/reduxDevtools.d.ts
@@ -0,0 +1,3 @@
+import type { Plugin } from '../common';
+declare const reduxDevtools: Plugin;
+export { reduxDevtools };
diff --git a/types/vanilla/selector.d.ts b/types/vanilla/selector.d.ts
index b0ca9d0..9b64f57 100644
--- a/types/vanilla/selector.d.ts
+++ b/types/vanilla/selector.d.ts
@@ -1,11 +1,15 @@
-import type { Reactish, Listener } from '../common';
+import type { Reactish, Plugin, Config } from '../common';
declare type ReactishArray = Reactish[];
declare type ReactishValueArray = {
[index in keyof R]: ReturnType;
};
declare type SelectorFunc = (...args: ReactishValueArray) => T;
-declare const selector: (...items: [...R, SelectorFunc]) => {
- get: () => T;
- subscribe: (listener: Listener) => () => void;
-};
-export { selector };
+interface Selector {
+ (...items: [...R, SelectorFunc]): Reactish;
+ (...items: [...R, SelectorFunc, Config]): Reactish;
+}
+declare const createSelector: ({ plugin }?: {
+ plugin?: Plugin | undefined;
+}) => Selector;
+declare const selector: Selector;
+export { selector, createSelector };
diff --git a/types/vanilla/state.d.ts b/types/vanilla/state.d.ts
index d5d8933..7244dc5 100644
--- a/types/vanilla/state.d.ts
+++ b/types/vanilla/state.d.ts
@@ -1,4 +1,4 @@
-import type { Setter, Reactish, Config, Middleware } from '../common';
+import type { Reactish, Setter, Config, Middleware } from '../common';
declare type ActionCreator = ((set: Setter, get: () => T) => A) | null | undefined;
interface State = undefined> extends Reactish {
set: Setter;
From ebdff7e5b4b6972fee81bfd1585595b516f21812 Mon Sep 17 00:00:00 2001
From: Zheng Song <41896553+szhsin@users.noreply.github.com>
Date: Thu, 22 Dec 2022 23:10:52 +1100
Subject: [PATCH 8/8] Use reduxDevtools in todo example
---
examples/examples/todo/todo.ts | 35 ++++++++++++++++++++--------------
1 file changed, 21 insertions(+), 14 deletions(-)
diff --git a/examples/examples/todo/todo.ts b/examples/examples/todo/todo.ts
index 55de69c..8402217 100644
--- a/examples/examples/todo/todo.ts
+++ b/examples/examples/todo/todo.ts
@@ -1,5 +1,6 @@
-import { createState, selector } from 'reactish-state';
+import { createState, createSelector } from 'reactish-state';
import { persist, reduxDevtools, applyMiddleware, immer } from 'reactish-state/middleware';
+import { reduxDevtools as devtoolsPlugin } from 'reactish-state/plugin';
const persistMiddleware = persist({ prefix: 'todoApp-' });
const state = createState({
@@ -38,6 +39,7 @@ const todoListState = state(
type VisibilityFilter = 'ALL' | 'COMPLETED' | 'IN_PROGRESS';
const visibilityFilterState = state('IN_PROGRESS' as VisibilityFilter, null, { key: 'filter' });
+const selector = createSelector({ plugin: devtoolsPlugin });
const visibleTodoList = selector(
todoListState,
visibilityFilterState,
@@ -50,22 +52,27 @@ const visibleTodoList = selector(
case 'IN_PROGRESS':
return todoList.filter(({ isCompleted }) => !isCompleted);
}
- }
+ },
+ { key: 'visible-todos' }
);
-const statsSelector = selector(todoListState, (todoList) => {
- const totalNum = todoList.length;
- const completedNum = todoList.filter((item) => item.isCompleted).length;
- const uncompletedNum = totalNum - completedNum;
- const percentCompleted = totalNum === 0 ? 0 : (completedNum / totalNum) * 100;
+const statsSelector = selector(
+ todoListState,
+ (todoList) => {
+ const totalNum = todoList.length;
+ const completedNum = todoList.filter((item) => item.isCompleted).length;
+ const uncompletedNum = totalNum - completedNum;
+ const percentCompleted = totalNum === 0 ? 0 : (completedNum / totalNum) * 100;
- return {
- totalNum,
- completedNum,
- uncompletedNum,
- percentCompleted
- };
-});
+ return {
+ totalNum,
+ completedNum,
+ uncompletedNum,
+ percentCompleted
+ };
+ },
+ { key: 'stats' }
+);
export type { VisibilityFilter };
export const { hydrate: hydrateStore } = persistMiddleware;