diff --git a/docs/api.md b/docs/api.md index 0aebe7a1..c63d5079 100644 --- a/docs/api.md +++ b/docs/api.md @@ -53,18 +53,7 @@ const model = { `reducers: { [string]: (prevState, payload) => any }` -An object of functions that change the model's state. These functions take the model's previous state and a payload, and return the model's next state. These should be pure functions relying only on the state and payload args to compute the next state. For code that relies on the "outside world" (impure functions like api calls, etc.), use effects. - -```js -const counter = { - state: 0, - reducers: { - add: (state, payload) => state + payload, - } -}; -``` - -Reducer could be use mutable method to achieve immutable state. Like the example: +An object of functions that change the model's state. These functions take the model's previous state and a payload, use mutable method to achieve immutable state. These should be pure functions relying only on the state and payload args to compute the next state. For code that relies on the "outside world" (impure functions like api calls, etc.), use effects. ```js const todo = { @@ -80,14 +69,14 @@ const todo = { ], reducers: { done(state) { - state.push({ todo: 'Tweet about it' }); + state.push({ todo: 'Tweet about it' }); // array updated directly state[1].done = true; }, }, } ``` -In Immer, reducers perform mutations to achieve the next immutable state. Keep in mind, Immer only supports change detection on plain objects and arrays, so primitive values like strings or numbers will always return a change. Like the example: +icestore use [immer](https://github.com/immerjs/immer) for immutable. In Immer, reducers perform mutations to achieve the next immutable state. Keep in mind, Immer only supports change detection on plain objects and arrays, so primitive values like strings or numbers will always return a change. Like the example: ```js const count = { @@ -107,35 +96,107 @@ See [docs/recipes](./recipes.md#immutable-description) for more details. `effects: (dispatch) => ({ [string]: (payload, rootState) => void })` -An object of functions that can handle the world outside of the model. Effects provide a simple way of handling async actions when used with async/await. +An object of functions that can handle the world outside of the model. Effects provide a simple way of handling async actions when used with async/await. In effects, call `this.reducerFoo` to update model's state: ```js const counter = { - state: { count: 0 }, - effects: (dispatch) => ({ - async add(payload, rootState) { - // wait for data to load - const response = await fetch('http://example.com/data'); - const data = await response.json(); - // pass the result to a local reducer - this.update(data); + state: 0, + reducers: { + decrement:(prevState) => prevState - 1, + }, + effects: () => ({ + async decrementAsync() { + await delay(1000); // do some asynchronous operations + this.decrement(); // pass the result to a local reducer }, }), }; ``` -You can call another action by using `dispatch`: +> Note: if you are using TypeScript and the compilerOptions `noimplicitthis: ture` is configured, you will encounter a compilation error similar to "property' setstate 'does not exist on type". You can fix it by delete the compile option or by using the `dispatch.model.reducer' method in the following example. + +###### Same name processing + +If the methods in the reducers and effects have the same name, the `reducer.foo` will be executed before the `effects.foo`: ```js +const model = { + state: [], + reducers: { + add(state, todo) { + state.push(todo); + }, + }, + effects: (dispatch: RootDispatch) => ({ + // this will run after "add" reducer finished + add(todo) { + dispatch.user.setTodos(store.getModelState('todos').length); + }, + }) +}; +``` + +###### this.setState + +By default icestore provides a reducer named `setState`, similar to [setState](https://reactjs.org/docs/react-component.html#setstate) in react Class Component, but only one parameter is supported and the parameter is of object type. + +```js +this.setState(stateChange); + +// This performs a shallow merge of stateChange into the new state, e.g., to adjust a shopping cart item quantity: +this.setState({quantity: 2}); +``` + +The internal implementation of the reducer of setstate is similar to: + +```js +const setState = (prevState, payload) => ({ + ...prevState, + ...payload, +}); +``` + +You can override the default behavior by declaring `setstate` in reducers: + +```js +const model = { + state: { count: 0, calledCounter: 0 }, + reducers: { + setState: (prevState, payload) => ({ + ...prevState, + ...payload, + calledCounter: prevState.calledCounter + 1, + }) + }, + effects: () => ({ + foo() { + this.setState({ count: 1 }); + } + }) +} +``` + +###### Model interaction + +You can call the methods of other models by declaring the first parameter `dispatch` of the effects function: + +```js +import { createStore } from '@ice/store'; + const user = { state: { foo: [], }, effects: (dispatch) => ({ - like(payload, rootState) => { - this.foo(payload); // call user's actions - dispatch.todos.foo(payload); // call actions of another model + like(payload, rootState) { + this.doSomething(payload); // call other effect or reducer in user + // another waw:dispatch.user.doSomething(payload); + dispatch.todos.foo(payload); // call the effect or reducer of other models }, + doSomething(payload) { + // ... + this.foo(payload); + } }), reducers: { foo(prevState, payload) { @@ -145,8 +206,14 @@ const user = { }, } }; + +const todos = { /* ... */ }; + +const store = createStore({ user, todos }); ``` +See [docs/recipes](./recipes.md#model-interaction) for more details. + #### options - `disableImmer` (boolean, optional, default=false) @@ -198,7 +265,7 @@ const models = { const store = createStore(models); const { Provider } = store; -const initialState = { +const initialStates = { todo: { title: 'Foo', done: true, @@ -210,7 +277,7 @@ const initialState = { }; function App() { return ( - + ); diff --git a/docs/api.zh-CN.md b/docs/api.zh-CN.md index 1229ac69..1fef6724 100644 --- a/docs/api.zh-CN.md +++ b/docs/api.zh-CN.md @@ -53,18 +53,7 @@ const model = { `reducers: { [string]: (prevState, payload) => any }` -一个改变该 model state 的所有函数的对象。这些函数采用 model 的上一次 state 和一个 payload 作为入参,并且返回 model 的下一个状态。这些函数应该是仅依赖于 state 和 payload 参数来计算下一个 state 的纯函数。对于不纯的函数,如 API 调用等的代码,请使用 effects。 - -```js -const counter = { - state: 0, - reducers: { - add: (state, payload) => state + payload, - } -}; -``` - -reducers 可以使用可变的方式来更新状态。在内部,我们是通过调用 [immer](https://github.com/immerjs/immer) 来实现的。例如: +一个改变该模型状态的函数集合。这些方法以模型的上一次 state 和一个 payload 作为入参,在方法中使用可变的方式来更新状态。这些方法应该是仅依赖于 state 和 payload 参数来计算下一个 state 的纯函数。对于有副作用的函数,请使用 effects。 ```js const todo = { @@ -80,14 +69,14 @@ const todo = { ], reducers: { done(state) { - state.push({ todo: 'Tweet about it' }); + state.push({ todo: 'Tweet about it' }); // 直接更新了数组 state[1].done = true; }, }, } ``` -在 Immer 中,reducer 对 state 进行变更检测以实现下一个不可变状态。Immer 只支持对普通对象和数组的变化检测,所以像字符串或数字这样的原始值需要返回一个新值。 例如: +icestore 内部是通过调用 [immer](https://github.com/immerjs/immer) 来实现可变状态的。Immer 只支持对普通对象和数组的变化检测,所以像字符串或数字这样的类型需要返回一个新值。 例如: ```js const count = { @@ -101,41 +90,113 @@ const count = { } ``` -参考 [docs/recipes](./recipes.zh-CN.md#immutable-description) 了解更多。 +参考 [docs/recipes](./recipes.zh-CN.md#可变状态的说明) 了解更多。 ##### effects `effects: (dispatch) => ({ [string]: (payload, rootState) => void })` -一个可以处理该模型副作用的函数对象。当与 async/await 一起使用时,Effects 提供了一种处理异步 action 方案: +一个可以处理该模型副作用的函数集合。Effects 适用于进行异步调用、[模型联动](recipes.zh-CN.md#模型联动)等场景。在 effects 内部,通过调用 `this.reducerFoo` 来更新模型状态: ```js const counter = { - state: { count: 0 }, - effects: (dispatch) => ({ - async add(payload, rootState) { - // wait for data to load - const response = await fetch('http://example.com/data'); - const data = await response.json(); - // pass the result to a local reducer - this.update(data); + state: 0, + reducers: { + decrement:(prevState) => prevState - 1, + }, + effects: () => ({ + async decrementAsync() { + await delay(1000); // 进行一些异步操作 + this.decrement(); // 调用模型 reducers 内的方法来更新状态 }, }), }; ``` -您可以通过调用 `dispatch` 来调用其他模型的方法: +> 注意:如果您正在使用 TypeScript 并且配置了编译选项 `noImplicitThis: ture`,则会遇到类似 "Property 'setState' does not exist on type" 的编译错误。您可以通过删除该编译选项,或者使用下面示例中的 `dispatch.model.reducer` 来避免此错误。 + +###### 同名处理 + +如果 reducers 和 effects 中的方法重名,则会在先执行 reducer.foo 后再执行 effects.foo: ```js +const model = { + state: [], + reducers: { + add(state, todo) { + state.push(todo); + }, + }, + effects: (dispatch: RootDispatch) => ({ + // 将会在 reducers.add 执行完成后再执行该方法 + add(todo) { + dispatch.user.setTodos(store.getModelState('todos').length); + }, + }) +}; +``` + +###### this.setState + +icestore 内置提供了名为 `setState` reducer ,其作用类似于 React Class 组件中的 [setState](https://zh-hans.reactjs.org/docs/react-component.html#setstate),但仅支持一个参数且参数是对象类型。 + +```js +this.setState(stateChange); + +// stateChange 会将传入的对象浅层合并到新的 state 中,例如,调整购物车商品数: +this.setState({quantity: 2}); +``` + +setState 的 reducer 内部实现类似于: + +```js +const setState = (prevState, payload) => ({ + ...prevState, + ...payload, +}); +``` + +您可以通过在 reducers 中声明 `setState` 来覆盖默认的行为: + +```js +const model = { + state: { count: 0, calledCounter: 0 }, + reducers: { + setState: (prevState, payload) => ({ + ...prevState, + ...payload, + calledCounter: prevState.calledCounter + 1, + }) + }, + effects: () => ({ + foo() { + this.setState({ count: 1 }); + } + }) +} +``` + +###### 模型联动 + +您可以通过声明 effects 函数的第一个参数 `dispatch` 来调用其他模型的方法: + +```js +import { createStore } from '@ice/store'; + const user = { state: { foo: [], }, effects: (dispatch) => ({ - like(payload, rootState) => { - this.foo(payload); // call user's actions - dispatch.todos.foo(payload); // call actions of another model + like(payload, rootState) { + this.doSomething(payload); // 调用 user 内的其他 effect 或 reducer + // 另一种调用方式:dispatch.user.doSomething(payload); + dispatch.todos.foo(payload); // 调用其他模型的 effect 或 reducer }, + doSomething(payload) { + // ... + this.foo(payload); + } }), reducers: { foo(prevState, payload) { @@ -145,8 +206,14 @@ const user = { }, } }; + +const todos = { /* ... */ }; + +const store = createStore({ user, todos }); ``` +参考 [docs/recipes](./recipes.zh-CN.md#模型联动) 了解更多。 + #### options - `disableImmer` (布尔值, 可选, 默认值=false) @@ -198,7 +265,7 @@ const models = { const store = createStore(models); const { Provider } = store; -const initialState = { +const initialStates = { todo: { title: 'Foo', done: true, @@ -210,7 +277,7 @@ const initialState = { }; function App() { return ( - + ); diff --git a/docs/recipes.md b/docs/recipes.md index adb17b6c..5a3aa8a9 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -23,7 +23,7 @@ export default { effects: () => ({ async refresh() { const data = await fetch('/user'); - this.update(data); + this.setState(data); }, }), }; @@ -34,7 +34,7 @@ export default { effects: (dispatch) => ({ async refresh() { const data = await fetch('/tasks'); - this.update(data); + this.setState(data); }, async add(task) { await fetch('/tasks/add', task); @@ -240,21 +240,21 @@ const store = createStore(models, { - X: No - +: Extra -| feature | constate | zustand | react-tracked | icestore | -| :--------| :-------- | :-------- | :-------- | :-------- | -| Framework | React | React | React | React | -| Simplicity | ★★★★ | ★★★ | ★★★ | ★★★★ | -| Less boilerplate | ★★ | ★★★ | ★★★ | ★★★★ | -| Configurable | ★★★ | ★★★ | ★★★ | ★★★★★ | -| Shareable State | O | O | O | O | -| Reusable State | O | O | O | O | -| Interactive State | + | + | + | O | -| Class Component | + | + | + | O | -| Function Component | O | O | O | O | -| Async Status | X | X | X | O | -| SSR | O | X | O | O | -| Persist | X | X | X | O | -| Lazy load models | + | + | + | O | -| Centralization | X | X | X | O | -| Middleware or Plug-in | X | O | X | O | -| Devtools | X | O | X | O | +| feature | redux | constate | zustand | react-tracked | icestore | +| :--------| :--------| :-------- | :-------- | :-------- | :-------- | +| Framework | Any | React | React | React | React | +| Simplicity | ★★ | ★★★★ | ★★★ | ★★★ | ★★★★ | +| Less boilerplate | ★ | ★★ | ★★★ | ★★★ | ★★★★ | +| Configurable | ★★ | ★★★ | ★★★ | ★★★ | ★★★★★ | +| Shareable State | O | O | O | O | O | +| Reusable State | O | O | O | O | O | +| Interactive State | + | + | + | + | O | +| Class Component | O | + | + | + | O | +| Function Component | O | O | O | O | O | +| Async Status | + | X | X | X | O | +| SSR | O | O | X | O | O | +| Persist | + | X | X | X | O | +| Lazy load models | + | + | + | + | O | +| Centralization | O | X | X | X | O | +| Middleware or Plug-in | O | X | O | X | O | +| Devtools | O | X | O | X | O | diff --git a/docs/recipes.zh-CN.md b/docs/recipes.zh-CN.md index f844d166..161c8fb3 100644 --- a/docs/recipes.zh-CN.md +++ b/docs/recipes.zh-CN.md @@ -23,7 +23,7 @@ export default { effects: () => ({ async refresh() { const data = await fetch('/user'); - this.update(data); + this.setState(data); }, }), }; @@ -34,7 +34,7 @@ export default { effects: (dispatch) => ({ async refresh() { const data = await fetch('/tasks'); - this.update(data); + this.setState(data); }, async add(task) { await fetch('/tasks/add', task); @@ -237,21 +237,21 @@ const store = createStore(models, { - X: 不支持 - +: 需要额外地进行能力扩展 -| 功能/库 | constate | zustand | react-tracked | icestore | -| :--------| :-------- | :-------- | :-------- | :-------- | -| 框架 | React | React | React | React | -| 简单性 | ★★★★ | ★★★ | ★★★ | ★★★★ | -| 更少的模板代码 | ★★ | ★★★ | ★★★ | ★★★★ | -| 可配置性 | ★★★ | ★★★ | ★★★ | ★★★★★ | -| 共享状态 | O | O | O | O | -| 复用状态 | O | O | O | O | -| 状态联动 | + | + | + | O | -| Class 组件支持 | + | + | + | O | -| Function 组件支持 | O | O | O | O | -| 异步更新的状态 | X | X | X | O | -| SSR | O | X | O | O | -| 持久化 | X | X | X | + | -| 懒加载模型 | + | + | + | O | -| 中心化 | X | X | X | O | -| 中间件或插件机制 | X | O | X | O | -| 开发者工具 | X | O | X | O | +| 功能/库 | redux | constate | zustand | react-tracked | icestore | +| :--------| :--------| :-------- | :-------- | :-------- | :-------- | +| 框架 | Any | React | React | React | React | +| 简单性 | ★★ | ★★★★ | ★★★ | ★★★ | ★★★★ | +| 更少的模板代码 | ★ | ★★ | ★★★ | ★★★ | ★★★★ | +| 可配置性 | ★★ | ★★★ | ★★★ | ★★★ | ★★★★★ | +| 共享状态 | O | O | O | O | O | +| 复用状态 | O | O | O | O | O | +| 状态联动 | + | + | + | + | O | +| Class 组件支持 | O | + | + | + | O | +| Function 组件支持 | O | O | O | O | O | +| 异步更新的状态 | + | X | X | X | O | +| SSR | O | O | X | O | O | +| 持久化 | + | X | X | X | + | +| 懒加载模型 | + | + | + | + | O | +| 中心化 | + | X | X | X | O | +| 中间件或插件机制 | O | X | O | X | O | +| 开发者工具 | O | X | O | X | O | diff --git a/docs/upgrade-guidelines.md b/docs/upgrade-guidelines.md index 24c60515..5033b20f 100644 --- a/docs/upgrade-guidelines.md +++ b/docs/upgrade-guidelines.md @@ -10,46 +10,6 @@ English | [简体中文](./upgrade-guidelines.zh-CN.md) From 1.2.0 to 1.3.0 is fully compatible, but we recommend that you use the new API in incremental code. We will remove the deprecated API in future versions. -### initialState - -#### 1.2.0 - -```jsx -import store from './store'; -const { Provider } = store; - -const initialStates = { - foo: { - }, -}; -function App() { - return ( - - - - ); -} -``` - -#### 1.3.0 - -```js -import store from './store'; -const { Provider } = store; - -const initialState = { - foo: { - }, -}; -function App() { - return ( - - - - ); -} -``` - ### Define Model Effects #### 1.2.0 @@ -327,7 +287,7 @@ const todos = { name: 'angular', }, ]; - this.update({ + this.setState({ dataSource, }); diff --git a/docs/upgrade-guidelines.zh-CN.md b/docs/upgrade-guidelines.zh-CN.md index d2edd7ff..37ab9e5d 100644 --- a/docs/upgrade-guidelines.zh-CN.md +++ b/docs/upgrade-guidelines.zh-CN.md @@ -11,46 +11,6 @@ title: Upgrade Guidelines 我们会在未来的版本中删除标记为「已过期」的 API。 -### initialState - -#### 1.2.0 - -```jsx -import store from './store'; -const { Provider } = store; - -const initialStates = { - foo: { - }, -}; -function App() { - return ( - - - - ); -} -``` - -#### 1.3.0 - -```js -import store from './store'; -const { Provider } = store; - -const initialState = { - foo: { - }, -}; -function App() { - return ( - - - - ); -} -``` - ### Define Model Effects #### 1.2.0 @@ -335,7 +295,7 @@ const todos = { name: 'angular', }, ]; - this.update({ + this.setState({ dataSource, }); diff --git a/examples/counter/src/index.tsx b/examples/counter/src/index.tsx index 1545f7b4..63830f49 100644 --- a/examples/counter/src/index.tsx +++ b/examples/counter/src/index.tsx @@ -11,7 +11,7 @@ const counter = { increment:(prevState) => prevState + 1, decrement:(prevState) => prevState - 1, }, - effects: (dispatch) => ({ + effects: () => ({ async decrementAsync() { await delay(1000); this.decrement(); diff --git a/examples/todos/src/index.tsx b/examples/todos/src/index.tsx index 0c4bbd98..c5787702 100644 --- a/examples/todos/src/index.tsx +++ b/examples/todos/src/index.tsx @@ -6,7 +6,7 @@ import TodoAdd from './components/TodoAdd'; import User from './components/User'; import Car from './components/Car'; -const initialState = { +const initialStates = { user: { dataSource: { name: 'Tom', @@ -16,11 +16,11 @@ const initialState = { }, }; -const { Provider } = store;; +const { Provider } = store; function App() { return ( - + diff --git a/examples/todos/src/models/index.ts b/examples/todos/src/models/index.ts index 10f41052..086ca814 100644 --- a/examples/todos/src/models/index.ts +++ b/examples/todos/src/models/index.ts @@ -3,13 +3,13 @@ import todos from './todos'; import user from './user'; import car from './car'; -const rootModel: RootModel = { todos, user, car }; +const rootModels: RootModels = { todos, user, car }; // add interface to avoid recursive type checking -export interface RootModel extends Models { +export interface RootModels extends Models { todos: typeof todos; user: typeof user; car: typeof car; } -export default rootModel; +export default rootModels; diff --git a/examples/todos/src/models/todos.ts b/examples/todos/src/models/todos.ts index 335a9e69..8ae3289e 100644 --- a/examples/todos/src/models/todos.ts +++ b/examples/todos/src/models/todos.ts @@ -31,12 +31,12 @@ const model = { }, }, effects: (dispatch: RootDispatch) => ({ + // this will run after "add" reducer finished add(todo: Todo) { dispatch.user.setTodos(store.getModelState('todos').dataSource.length); }, async refresh() { - await delay(2000); - + await delay(2000); // wait for data to load const dataSource: Todo[] = [ { name: 'react', @@ -49,7 +49,10 @@ const model = { name: 'angular', }, ]; - this.update({ + + // pass the result to a local reducer + // setState is a built-in reducer + this.setState({ dataSource, }); diff --git a/examples/todos/src/models/user.ts b/examples/todos/src/models/user.ts index 3ebf3ecd..c85d662c 100644 --- a/examples/todos/src/models/user.ts +++ b/examples/todos/src/models/user.ts @@ -25,7 +25,7 @@ const model = { async login() { await delay(1000); - this.update({ + this.setState({ dataSource: { name: 'Alvin', }, diff --git a/examples/todos/src/store.ts b/examples/todos/src/store.ts index 14d159fb..2ff44999 100644 --- a/examples/todos/src/store.ts +++ b/examples/todos/src/store.ts @@ -5,6 +5,7 @@ import models from './models'; const store = createStore(models); export default store; +export type Models = typeof models; export type Store = typeof store; -export type RootDispatch = IcestoreDispatch; -export type iRootState = IcestoreRootState; +export type RootDispatch = IcestoreDispatch; +export type iRootState = IcestoreRootState; diff --git a/package.json b/package.json index 7505c524..47f30b6a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ice/store", - "version": "1.3.1", + "version": "1.3.2", "description": "Simple and friendly state for React", "main": "lib/index.js", "files": [ diff --git a/src/plugins/modelApis.tsx b/src/plugins/modelApis.tsx index 5c05cd8d..b7ba237c 100644 --- a/src/plugins/modelApis.tsx +++ b/src/plugins/modelApis.tsx @@ -20,11 +20,18 @@ export default (): T.Plugin => { return [state, dispatchers]; } function useModelState(name: string) { - return store.useSelector(state => state[name]); + const selector = store.useSelector(state => state[name]); + if (selector) { + return selector; + } + throw new Error(`Not found model by namespace: ${name}.`); } function useModelDispatchers(name: string) { const dispatch = store.useDispatch(); - return dispatch[name]; + if (dispatch[name]) { + return dispatch[name]; + } + throw new Error(`Not found model by namespace: ${name}.`); } /** @@ -107,4 +114,3 @@ export default (): T.Plugin => { }, }; }; - diff --git a/src/plugins/provider.tsx b/src/plugins/provider.tsx index 0ae50451..d62108f5 100644 --- a/src/plugins/provider.tsx +++ b/src/plugins/provider.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Provider as ReduxProvider } from 'react-redux'; import * as T from '../types'; -import warning from '../utils/warning'; import actionTypes from '../actionTypes'; const { SET_STATE } = actionTypes; @@ -13,17 +12,13 @@ interface ProviderConfig { export default ({ context }: ProviderConfig): T.Plugin => { return { onStoreCreated(store: any) { - const Provider = function(props: { children; initialStates?; initialState? }) { - const { children, initialStates, initialState } = props; - const states = initialState || initialStates; - if (states) { - if (initialStates) { - warning('`initialStates` API has been detected, please use `initialState` instead. \n\n\n Visit https://github.com/ice-lab/icestore/blob/master/docs/upgrade-guidelines.md#initialstate to learn about how to upgrade.'); - } - Object.keys(states).forEach(name => { - const state = states[name]; - if (state && store.dispatch[name][SET_STATE]) { - store.dispatch[name][SET_STATE](state); + const Provider = function(props: { children; initialStates? }) { + const { children, initialStates } = props; + if (initialStates) { + Object.keys(initialStates).forEach(name => { + const initialState = initialStates[name]; + if (initialState && store.dispatch[name][SET_STATE]) { + store.dispatch[name][SET_STATE](initialState); } }); } diff --git a/src/redux.ts b/src/redux.ts index b6f94bed..a935da8e 100644 --- a/src/redux.ts +++ b/src/redux.ts @@ -23,8 +23,8 @@ export default function({ }) { const combineReducers = redux.combineReducers || Redux.combineReducers; const createStore: Redux.StoreCreator = redux.createStore || Redux.createStore; - const initialState: any = - typeof redux.initialState !== 'undefined' ? redux.initialState : {}; + const initialStates: any = + typeof redux.initialStates !== 'undefined' ? redux.initialStates : {}; // Allows passing in of reducer functions, rather than models. // While not recommended, @@ -98,7 +98,7 @@ export default function({ middlewares, ); - this.store = createStore(rootReducer, initialState, enhancers); + this.store = createStore(rootReducer, initialStates, enhancers); return this; } diff --git a/src/types.ts b/src/types.ts index a61a4cf8..4901af26 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,22 +1,22 @@ import * as Redux from 'redux'; -export type Optionalize = Omit; +type Optionalize = Omit; -export type PropType = Obj[Prop]; +type PropType = Obj[Prop]; -export interface EffectState { +interface EffectState { isLoading: boolean; error: Error; } -export type EffectsState = { +type EffectsState = { [K in keyof Effects]: EffectState; } -export type EffectsLoading = { +type EffectsLoading = { [K in keyof Effects]: boolean; } -export type EffectsError = { +type EffectsError = { [K in keyof Effects]: { error: Error; value: number; @@ -31,7 +31,7 @@ export type IcestoreRootState = ExtractIcestoreStateFromModels M > -export type ExtractIModelDispatcherAsyncFromEffect< +type ExtractIModelDispatcherAsyncFromEffect< E > = E extends () => Promise ? IcestoreDispatcherAsync @@ -41,7 +41,7 @@ export type ExtractIModelDispatcherAsyncFromEffect< ? IcestoreDispatcherAsync : IcestoreDispatcherAsync -export type ExtractIModelDispatchersFromEffectsObject< +type ExtractIModelDispatchersFromEffectsObject< effects extends ModelEffects > = { [effectKey in keyof effects]: ExtractIModelDispatcherAsyncFromEffect< @@ -60,7 +60,7 @@ export type ExtractIModelDispatchersFromEffects< : effects extends ConfigEffects ? OldModelEffects : {}; -export type ExtractIModelDispatcherFromReducer = R extends () => any +type ExtractIModelDispatcherFromReducer = R extends () => any ? IcestoreDispatcher : R extends (state: infer S) => infer S ? IcestoreDispatcher @@ -70,13 +70,17 @@ export type ExtractIModelDispatcherFromReducer = R extends () => any ? IcestoreDispatcher : IcestoreDispatcher -export type ExtractIModelDispatchersFromReducersObject< +interface DefaultIModelDispatchersFromReducersObject { + setState: IcestoreDispatcher; +} + +type ExtractIModelDispatchersFromReducersObject< reducers extends ModelReducers > = { [reducerKey in keyof reducers]: ExtractIModelDispatcherFromReducer< reducers[reducerKey] - > -} + >; +} & DefaultIModelDispatchersFromReducersObject; export type ExtractIModelDispatchersFromReducers< reducers extends ModelConfig['reducers'] @@ -112,7 +116,7 @@ export type ExtractIcestoreDispatchersFromModels = { [modelKey in keyof M]: ExtractIModelDispatchersFromModelConfig } -export type IcestoreDispatcher

= ([P] extends [void] +type IcestoreDispatcher

= ([P] extends [void] ? ((...args: any[]) => Action) : [M] extends [void] ? ((payload: P) => Action) @@ -120,7 +124,7 @@ export type IcestoreDispatcher

= ([P] extends [void] ((action: Action) => Redux.Dispatch>) & ((action: Action) => Redux.Dispatch>) -export type IcestoreDispatcherAsync

= ([P] extends [void] +type IcestoreDispatcherAsync

= ([P] extends [void] ? ((...args: any[]) => Promise) : [M] extends [void] ? ((payload: P) => Promise) @@ -150,7 +154,7 @@ export interface Icestore< subscribe(listener: () => void): Redux.Unsubscribe; } -export interface EffectsErrorPluginAPI { +interface EffectsErrorPluginAPI { useModelEffectsError(name: K): ExtractIModelEffectsErrorFromModelConfig; withModelEffectsError< K extends keyof M, @@ -160,7 +164,7 @@ export interface EffectsErrorPluginAPI { (props: Optionalize) => React.ReactElement; } -export interface EffectsLoadingPluginAPI { +interface EffectsLoadingPluginAPI { useModelEffectsLoading(name: K): ExtractIModelEffectsLoadingFromModelConfig; withModelEffectsLoading< K extends keyof M, @@ -170,11 +174,11 @@ export interface EffectsLoadingPluginAPI { (props: Optionalize) => React.ReactElement; } -export interface UseModelEffectsState { +interface UseModelEffectsState { (name: K): ExtractIModelEffectsStateFromModelConfig; } -export interface WithModelEffectsState { +interface WithModelEffectsState { < K extends keyof M, F extends (effectsState: ExtractIModelEffectsStateFromModelConfig) => Record @@ -183,7 +187,7 @@ export interface WithModelEffectsState { (props: Optionalize) => React.ReactElement; } -export interface EffectsStatePluginAPI { +interface EffectsStatePluginAPI { useModelEffectsState: UseModelEffectsState; /** @@ -198,11 +202,11 @@ export interface EffectsStatePluginAPI { withModelActionsState: WithModelEffectsState; } -export interface UseModelDispatchers { +interface UseModelDispatchers { (name: K): ExtractIModelDispatchersFromModelConfig; } -export interface WithModelDispatchers { +interface WithModelDispatchers { < K extends keyof M, F extends (model: ExtractIModelDispatchersFromModelConfig) => Record @@ -211,7 +215,7 @@ export interface WithModelDispatchers { (props: Optionalize) => React.ReactElement; } -export interface ModelPluginAPI { +interface ModelPluginAPI { useModel(name: K): ExtractIModelFromModelConfig; useModelState(name: K): ExtractIModelStateFromModelConfig; useModelDispatchers: UseModelDispatchers; @@ -237,17 +241,12 @@ export interface ModelPluginAPI { withModelActions: WithModelDispatchers; } -export interface ProviderProps { +interface ProviderProps { children: any; - initialState?: any; - - /** - * @deprecated use `initialState` instead. - */ initialStates?: any; } -export interface ProviderPluginAPI { +interface ProviderPluginAPI { Provider: (props: ProviderProps) => JSX.Element; } @@ -267,21 +266,11 @@ export interface Action

{ meta?: M; } -export type EnhancedReducer = ( - state: S, - payload: P, - meta: M -) => S - -export interface EnhancedReducers { - [key: string]: EnhancedReducer; -} - export interface ModelReducers { [key: string]: (state: S, payload: any, meta?: any) => S; } -interface ModelEffects { +export interface ModelEffects { [key: string]: ( this: { [key: string]: (payload?: any, meta?: any) => Action }, payload: any, @@ -351,7 +340,7 @@ export interface DevtoolOptions { } export interface InitConfigRedux { - initialState?: S; + initialStates?: S; reducers?: ModelReducers; enhancers?: Redux.StoreEnhancer[]; middlewares?: Middleware[]; @@ -396,7 +385,7 @@ export interface Middleware< } export interface ConfigRedux { - initialState?: any; + initialStates?: any; reducers: ModelReducers; enhancers: Redux.StoreEnhancer[]; middlewares: Middleware[]; @@ -427,7 +416,7 @@ export type ConfigEffect = (state: S, payload?: any, actions?: any, glo */ export type ConfigReducer = (state: S, payload?: any,) => S; /** - * @deprecated + * @deprecated use `ModelEffects` instead */ export interface ConfigEffects { [name: string]: ConfigEffect; diff --git a/src/utils/appendReducers.ts b/src/utils/appendReducers.ts index 3e8b0479..553f2991 100644 --- a/src/utils/appendReducers.ts +++ b/src/utils/appendReducers.ts @@ -9,8 +9,8 @@ export default function(originModels: any) { if (!model.reducers) { model.reducers = {}; } - if (!model.reducers.update) { - model.reducers.update = (state, payload) => ({ + if (!model.reducers.setState) { + model.reducers.setState = (state, payload) => ({ ...state, ...payload, }); diff --git a/src/utils/mergeConfig.ts b/src/utils/mergeConfig.ts index 7f63cb1e..17aaa894 100644 --- a/src/utils/mergeConfig.ts +++ b/src/utils/mergeConfig.ts @@ -75,9 +75,9 @@ export default (initConfig: T.InitConfig & { name: string }): T.Config => { // redux if (plugin.config.redux) { - config.redux.initialState = merge( - config.redux.initialState, - plugin.config.redux.initialState, + config.redux.initialStates = merge( + config.redux.initialStates, + plugin.config.redux.initialStates, ); config.redux.reducers = merge( config.redux.reducers,