--- id: api title: API --- `createStore` 是 @ice/store 的 API 主要入口。创建后的 Store 将提供一些 Hooks 和 API 用于访问和操作数据。 `createModel` 是一个类型工具方法,无任何副作用。用它来包裹你的 model 对象,在 effects 中使用 `this` 时可获得完整的类型提示。 ## createModel `createModel(modelConfig)` 该方法用于包裹 model 对象,以获得更好的类型提示。 ```ts import { createModel } from '@ice/store'; type IState = { count: number, }; const state: IState = { count: 0, }; const counter = createModel({ state, reducers: { increment(state: IState, payload: number) { return state.count + payload; }, decrement(state: IState, payload: number) { return state.count - payload; }, }, effects: () => ({ async asyncDecrement(payload: number) { this.decrement(payload); }, async anotherEffect(payload: number) { this.asyncDecrement(payload); }, }), }); const models = { counter, } ``` ## createStore `createStore(models, options)` 该方法用于创建 Store。 ```js import { createStore } from '@ice/store'; const { // 主要的 API Provider, useModel, getModel, withModel, // 辅助的 API useModelDispatchers, useModelEffectsState, withModelDispatchers, withModelEffectsState, getModelState, getModelDispatchers, } = createStore(models); ``` ### 参数 #### models `createStore({ [string]: modelConfig });` ```js import { createStore, createModel } from '@ice/store' const count = createModel({ state: 0, }); createStore({ count }); ``` ##### state `state: any`: 必填 该 model 的初始 state。 ```js import { createModel } from '@ice/store'; interface State { loading: boolean; } const model = createModel({ state: { loading: false } as State, }); ``` ##### reducers `reducers: { [string]: (state, payload) => any }` 一个改变该模型状态的函数集合。这些方法以模型的上一次 state 和一个 payload 作为入参,在方法中使用可变的方式来更新状态。 这些方法应该是仅依赖于 state 和 payload 参数来计算下一个 state 的纯函数。对于有副作用的函数,请使用 effects。 一个简单的示例: ```ts import { createModel } from '@ice/store'; interface State { title: string; done: boolean; } const todos = createModel({ state: [ { title: 'Learn typescript', done: true, }, ] as State[], reducers: { foo(state) { state.push({ title: 'Tweet about it' }); // 直接更新了数组 state[1].done = true; }, }, }); ``` icestore 内部是通过调用 [immer](https://github.com/immerjs/immer) 来实现可变状态的。 Immer 只支持对普通对象和数组的变化检测,所以像字符串或数字这样的类型需要返回一个新值。 例如: ```js import { createModel } from '@ice/store'; const count = createModel({ state: 0, reducers: { add(state) { state += 1; return state; }, }, }); ``` 参考 [docs/recipes](./recipes.md#可变状态的说明) 了解更多。 reducer 的第二个参数即是调用时传递的参数: ```js import { createModel } from '@ice/store'; const todos = createModel({ state: [ { title: 'Learn typescript', done: true, }, ], reducers: { // 正确用法 add(state, todo) { state.push(todo); }, // 错误用法 add(state, title, done) { state.push({ title, done }); }, }, }); // 使用时: function Component() { const { add } = store.useModelDispatchers('todos'); function handleClick () { add({ title: 'Learn React', done: false }); // 正确用法 add('Learn React', false); // 错误用法 } } ``` ##### effects `effects: (dispatch) => ({ [string]: (payload, rootState) => void })` 一个可以处理该模型副作用的函数集合。这些方法以 payload 和 rootState 作为入参,适用于进行异步调用、[模型联动](recipes.md#模型联动)等场景。在 effects 内部,通过调用 `this.reducerFoo` 来更新模型状态: ```js import { createModel } from '@ice/store'; const counter = createModel({ state: 0, reducers: { decrement:(prevState) => prevState - 1, }, effects: () => ({ async asyncDecrement() { await delay(1000); // 进行一些异步操作 this.decrement(); // 调用模型 reducers 内的方法来更新状态 }, }), }); ``` > 注意:如果您正在使用 TypeScript ,并且配置了编译选项 `noImplicitThis: ture`,则会遇到类似 "Property 'setState' does not exist on type" 的编译错误。您可以[参考qna中的用法](qna.md)使用 `createModel` 来包裹你的 model,或者使用下面示例中的 `dispatch.model.reducer` 来避免此错误。 ###### 同名处理 如果 reducers 和 effects 中的方法重名,则会在先执行 reducer.foo 后再执行 effects.foo: ```js import { createModel } from '@ice/store'; const model = createModel({ 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 import { createModel } from '@ice/store'; const model = createModel({ 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, createModel } from '@ice/store'; const user = createModel({ state: { foo: [], }, effects: (dispatch) => ({ 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) { return { ...prevState, }; }, } }); const todos = { /* ... */ }; const store = createStore({ user, todos }); ``` 参考 [docs/recipes](./recipes.md#模型联动) 了解更多。 #### options - `disableImmer` (布尔值, 可选, 默认值=false) 如果您将其设置为true,那么 immer 将被禁用,这意味着您不能再在 reducers 中直接改变状态,而是必须返回新的状态。 - `disableError` (布尔值, 可选, 默认值=false) 如果将此设置为true,则 “UseModelEffectsError” 和 “WithModelEffectsError” 将不可用。仅当您非常关注性能或故意抛出错误时才启用该选项。 - `disableLoading` (布尔值, 可选, 默认值=false) 如果将此设置为true,则“useModelEffectsLoading”和“withModelEffectsLoading”将不可用。 ### 返回值 #### Provider `Provider(props: { children })` 将 store 和 React 应用进行绑定,因此可以在组件中使用 store 提供的 hooks。 ```jsx import React from 'react'; import ReactDOM from 'react-dom'; import { createStore } from '@ice/store'; const { Provider } = createStore(models); ReactDOM.render( , rootEl ); ``` 允许您声明初始状态(可以用在诸如服务端渲染等场景)。 ```jsx import { createStore, createModel } from '@ice/store'; const models = { todo: createModel({ state: {} }), user: createModel({ state: {}, }), }; const store = createStore(models); const { Provider } = store; const initialStates = { todo: { title: 'Foo', done: true, }, user: { name: 'Alvin', age: 18, }, }; function App() { return ( ); } ``` #### useModel `useModel(name: string): [ state, dispatchers ]` 通过该 hooks 使用模型,返回模型的状态和调度器。 ```jsx import { createModel } from '@ice/store'; const counter = createModel({ state: { value: 0, }, reducers: { add: (state, payload) => { state.value = state.value + payload; }, }, }); const { useModel } = createStore({ counter }); function FunctionComponent() { const [ state, dispatchers ] = useModel('counter'); state.value; // 0 dispatchers.add(1); // state.value === 1 } ``` #### getModel `getModel(name: string): [ state, dispatchers ]` 通过 API 获取到最新的模型,在闭包中将非常有用。 ```js import { useCallback } from 'react'; import store from '@/store'; function FunctionComponent() { const memoizedCallback = useCallback( () => { const [state] = store.getModel('foo'); doSomething(a, b, state); }, [a, b], ); } ``` #### withModel `withModel(name: string, mapModelToProps?: (model: [state, dispatchers]) => Object = (model) => ({ [name]: model }) ): (React.Component) => React.Component` 使用该 API 将模型绑定到 Class 组件上。 ```jsx import { ExtractIModelFromModelConfig } from '@ice/store'; import todosModel from '@/models/todos'; import store from '@/store'; interface Props { todos: ExtractIModelFromModelConfig; // `withModel` automatically adds the name of the model as the property } class TodoList extends Component { render() { const { counter } = this.props; const [ state, dispatchers ] = counter; state.value; // 0 dispatchers.add(1); } } export default withModel('counter')(TodoList); ``` 可以使用 `mapModelToProps` 设置 props 的字段名: ```tsx import { ExtractIModelFromModelConfig } from '@ice/store'; import todosModel from '@/models/todos'; import store from '@/store'; const { withModel } = store; interface Props { title: string; customKey: ExtractIModelFromModelConfig; } class TodoList extends Component { render() { const { title, customKey } = this.props; const [ state, dispatchers ] = customKey; state.field; // get state dispatchers.add({ /* ... */}); // run action } } export default withModel( 'todos', // mapModelToProps: (model: [state, dispatchers]) => Object = (model) => ({ [modelName]: model }) ) (model) => ({ customKey: model, }) )(TodoList); ``` #### useModelState `useModelState(name: string): state` 通过该 hooks 使用模型的状态并订阅其更新。 ```js function FunctionComponent() { const state = useModelState('counter'); console.log(state.value); } ``` #### useModelDispatchers `useModelDispatchers(name: string): dispatchers` 通过该 hooks 使用模型的调度器,通过调度器更新模型。 ```js function FunctionComponent() { const dispatchers = useModelDispatchers('counter'); dispatchers.add(1); } ``` #### useModelEffectsLoading `useModelEffectsLoading(name: string): { [actionName: string]: boolean }` 通过该 hooks 来获取模型副作用的调用状态。 ```js function FunctionComponent() { const dispatchers = useModelDispatchers('counter'); const effectsLoading = useModelEffectsLoading('counter'); useEffect(() => { dispatchers.fetch(); }, []); effectsLoading.fetch; // boolean } ``` #### useModelEffectsError `useModelEffectsError(name: string): { [actionName: string]: { error: Error; value: boolean;}}` 通过该 hooks 来获取模型副作用的调用结果是否有错误。 ```js function FunctionComponent() { const dispatchers = useModelDispatchers('counter'); const effectsError = useModelEffectsError('counter'); useEffect(() => { dispatchers.fetch(); }, []); effectsError.fetch.error; // Error } ``` #### withModelDispatchers `withModelDispatchers(name: string, mapModelDispatchersToProps?: (dispatchers) => Object = (dispatchers) => ({ [name]: dispatchers }) ): (React.Component) => React.Component` ```tsx import { ExtractIModelDispatchersFromModelConfig } from '@ice/store'; import todosModel from '@/models/todos'; import store from '@/store'; const { withModelDispatchers } = store; interface Props { todosDispatchers: ExtractIModelDispatchersFromModelConfig; // `withModelDispatchers` automatically adds `${modelName}Dispatchers` as the property } class TodoList extends Component { render() { const { todosDispatchers } = this.props; todosDispatchers.add({ /* ... */}); // run action } } export default withModelDispatchers('todos')(TodoList); ``` 你可以使用 `mapModelDispatchersToProps` 来设置 props 的字段名,用法同 `mapModelToProps`。 #### withModelEffectsLoading `withModelEffectsLoading(name: string, mapModelEffectsLoadingToProps?: (effectsLoading) => Object = (effectsLoading) => ({ [name]: effectsLoading }) ): (React.Component) => React.Component` ```tsx import { ExtractIModelEffectsLoadingFromModelConfig } from '@ice/store'; import todosModel from '@/models/todos'; import store from '@/store'; const { withModelEffectsLoading } = store; interface Props { todosEffectsLoading: ExtractIModelEffectsLoadingFromModelConfig; // `todosEffectsLoading` automatically adds `${modelName}EffectsLoading` as the property } class TodoList extends Component { render() { const { todosEffectsLoading } = this.props; todosEffectsLoading.add; } } export default withModelEffectsLoading('todos')(TodoList); ``` 可以使用 `mapModelEffectsLoadingToProps` 参数来设置 props 的字段名,方式与 `mapModelToProps` 一致。 #### withModelEffectsError `withModelEffectsError(name: string, mapModelEffectsErrorToProps?: (effectsError) => Object = (effectsError) => ({ [name]: effectsError }) ): (React.Component) => React.Component` ```tsx import { ExtractIModelEffectsErrorFromModelConfig } from '@ice/store'; import todosModel from '@/models/todos'; import store from '@/store'; const { withModelEffectsError } = store; interface Props { todosEffectsError: ExtractIModelEffectsErrorFromModelConfig; // `todosEffectsError` automatically adds `${modelName}EffectsError` as the property } class TodoList extends Component { render() { const { todosEffectsError } = this.props; todosEffectsError.add; } } export default withModelEffectsError('todos')(TodoList); ``` 可以使用 `mapModelEffectsErrorToProps` 来设置 props 的字段名,方式与 `mapModelToProps` 一致。 #### getModelState `getModelState(name: string): state` 通过该 API 获取模型的最新状态。 ```js import { useCallback } from 'react'; import store from '@/store'; function FunctionComponent() { const memoizedCallback = useCallback( () => { const state = store.getModelState('foo'); something(a, state); }, [a, b], ); } ``` #### getModelDispatchers `getModelDispatchers(name: string): dispatchers` 通过该 API 来获取模型的调度器。 ```js import { useCallback } from 'react'; import store from '@/store'; function FunctionComponent() { const memoizedCallback = useCallback( () => { const dispatchers = store.getModelDispatchers('foo'); dispatchers.foo(a, b); }, [a, b], ); } ``` ## withModel `withModel(model, mapModelToProps?, options?)(ReactFunctionComponent)` 该方法用于在组件中快速使用 Model。 ```js import { withModel } from '@ice/store'; import model from './model'; function Todos({ model }) { const { useState, useDispatchers, useEffectsState, getState, getDispatchers, } = model; const [ state, dispatchers ] = useValue(); } export default withModel(model)(Todos); ``` ### 参数 #### modelConfig 与 createStore 方法中的 modelConfig 一致。 #### mapModelToProps `mapModelToProps = (model) => ({ model })` 使用该函数来自定义映射到组件中的值,使用示例: ```js import { withModel } from '@ice/store'; import model from './model'; function Todos({ todo }) { const [ state, dispatchers ] = todo.useValue(); } export default withModel(model, function(model) { return { todo: model }; })(Todos); ``` #### options 与 createStore 方法中的 options 一致。 ### 返回值 - useValue - useState - useDispathers - useEffectsState - getValue - getState - getDispatchers - withValue - withDispatchers - withModelEffectsState 其用法参考 createStore 的返回值。