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;