bump 2.2.8
San 框架的官方应用状态管理套件,其理念是类似 flux 的单向流。
虽然应用状态管理已经是流行的理念,我们依然推荐您在开始之前,先看看 为什么要进行应用状态管理,可能会有些思考和启发。
提示:使用 san-store 需要同时使用 san-update 2.x 创建状态变更器,san-store 将使用此变更器更新 store 中的应用状态。
NPM:
$ npm i --save san-store san-update
import {store, connect} from 'san-store'; import {builder} from 'san-update'; store.addAction('changeUserName', function (name) { return builder().set('user.name', name); }); let UserNameEditor = connect({ name: 'user.name' })(san.defineComponent({ submit() { store.dispatch('changeUserName', this.data.get('name')); } }));
从例子开始和模仿比死啃枯燥的文档要更人性化。 Todos项目 和 RealWorld App 展示了如何在项目里使用 san-store 进行状态管理。 多store的Todos项目 展示了如何再项目中使用 多 store connect 及 composition api 的用法。
本文档描述了 san-store 的基本使用场景。想了解 san-store 都提供了什么,可以参阅 API文档
一个应用具有唯一的应用状态源,在一个地方管理整个应用的所有状态,是一个比较共识的方式。所以 san-store 提供了默认的 Store 实例。绝大多数时候,应用开发者不需要手工创建自己的 Store 实例,只需要 import 默认的 store。
import {store} from 'san-store'; // 通过 getState 方法,获取 store 中的状态数据。 console.log(store.getState('user.name'));
store 并没有提供修改状态数据的方法,修改状态数据只能通过 dispatch action 来做到,具体细节请参考 Action 章节。通过 addAction 方法可以添加 action。
store.addAction('changeUserName', name => builder().set('user.name', name));
当同一系统中有不同团队开发自己的业务模块,各团队之间没有状态共享,可以考虑分别建立 Store 实例进行开发。通过 new Store 创建自己的 Store 实例。创建时可以传入初始化数据和声明 actions。
import {Store} from 'san-store'; import {builder} from 'san-update'; let myStore = new Store({ initData: { user: { name: 'your name' } }, actions: { changeUserName(name) { return builder().set('user.name', name); } } });
本节最后,还是要强调下,应用开发应当遵循 一个应用具有唯一的应用状态源。说白了就是 要按常理出牌。
通过 addAction 方法可以为 Store 添加 Action。 Action 是 san-store 最重要的组成部分之一,它:
如果你使用了 san-store,Action 应该是你业务组件的唯一出口:用户操作事件等需要改变应用状态时,都应该 dispatch Action。
Action 接收一个 payload,返回一个 san-update 的 builder 对象。store 使用 builder 对象生成状态变更函数,并执行它,使 store 内部的状态得到更新。当然,如果当前 action 不期望对 store 的状态进行更新,可以不返回 builder 对象。
import {builder} from 'san-update'; store.addAction('changeUserName', function (name) { return builder().set('user.name', name); }); // 通过名称 dispatch store.dispatch('changeUserName', 'erik');
san-update 是一个 Immutable 的更新对象库,其提供了一些更新函数(如set、push等),通过 newObj = set(oldObj, 'x', 1) 的使用形式让对象更新 Immutable。builder 是 san-update 提供的一个很好用的功能,通过 builder 你可以预定义一系列的数据更新操作,然后通过 builder.build 方法可以获得一个更新函数。san-store 就是利用这个功能,使用 action 返回的 builder 生成对象更新函数,再调用它进行 store 内部状态更新。
newObj = set(oldObj, 'x', 1)
builder.build
san-update 的 builder 支持预定义所有 san-update 支持的数据操作,通过 san-update文档:可用指令 可以查看所有操作类型。常用的有:
使用前请阅读 san-update文档:使用builder构建更新函数 进行详细了解。
Action 的第二个参数是一个对象,其中的 getState 方法可以用于获得当前 store 中的应用状态。这个方法是 this 无关的。
import {builder} from 'san-update'; store.addAction('initCount', function (count, {getState}) { if (getState('count') == null) { return builder().set('count', count); } }); store.dispatch('initCount', 10);
如果我们的更新操作仅依赖于当前数据状态项的值,也可以使用 san-update 提供的 apply 方法。
import {builder} from 'san-update'; store.addAction('initCount', function (count) { // apply 意思是:在原有的值上应用新的值 return builder().apply('count', oldValue => { return oldValue == null ? count : oldValue; }); }); store.dispatch('initCount', 10);
同步的 Action 返回一个 builder,并立即更新数据状态,但我们经常会遇到异步的场景,常见的比如请求数据、返回并更新应用状态。Action 在设计上作为 业务组件的唯一出口, 对异步支持的方式如下:
下面是一个简单的例子: 一个列表请求的行为,此时要显示 loading,在请求返回时更新应用状态中的列表项,同时隐藏 loading。
import {builder} from 'san-update'; store.addAction('fetchList', function (page, {getState, dispatch}) { dispatch('showLoading'); dispatch('updateCurrentPage', page); return requestList(page).then(list => { if (getState('currentPage') === page) { dispatch('updateList', list); dispatch('hideLoading'); } }); }); store.addAction('showLoading', function () { return builder().set('loading', true); }); store.addAction('hideLoading', function () { return builder().set('loading', false); }); store.addAction('updateCurrentPage', function (page) { return builder().set('currentPage', page); }); store.addAction('updateList', function (list) { return builder().set('list', list); }); // 这里模拟一下,意思意思 function requestList(page) { return new Promise(resolve => { setTimeout(() => { let pageList = [1, 2, 3]; resolve(pageList); }, 500); }); }
例子中有下面几个要点:
store.addAction('fetchList', function (page, {getState, dispatch}) { dispatch('showLoading'); dispatch('updateCurrentPage', page); return requestList(page).then(list => { if (getState('currentPage') === page) { dispatch('hideLoading'); // 如果异步 Action 支持在 promise 中返回 builder 并更新状态 // 这里的代码就可能导致问题。因为 promise.then 不是马上运行的 // 这里的 currentPage 不代表 builder 运行时的 currentPage // currentPage 可能被另外一个 dispatch fetchList 改掉 // 所以这里应该 dispatch 一个同步的 Action 让应用状态即时完成变更 dispatch('updateList', list); // good // return builder().set('list', list); // warning } }); });
异步 Action 在 dispatch 时将返回 Promise 对象,以便于 Action 完成后的逻辑控制。
store.addAction('addArticle', function (article) { return axios.post(url, article); }); store.dispatch('addArticle', {}).then(() => { // redirect to view page });
connect 方法支持对 默认store实例 和 San 组件进行连接,步骤和 redux 类似:
import {store, connect} from 'san-store'; const connector = connect( {name: 'user.name'}, {change: 'changeUserName'} ); const UserNameEditor = san.defineComponent({/*...*/}); const NewUserNameEditor = connector(UserNameEditor);
通常,我们只需要对当前声明的组件进行 connect。此时可以合并成一句
const UserNameEditor = san.defineComponent({/*...*/}); const NewUserNameEditor = connect( {name: 'user.name'}, {change: 'changeUserName'} )(UserNameEditor);
当实际业务中需要多个 Store 实例时,可以自行创建 Store 实例。connect 方法支持连接到指定的 Store 实例。
import {Store, connect} from 'san-store'; // 自行创建 store 实例 const myStore = new Store({ initData: { name:'erik' }, actions:{ changeUserName(name) { return builder().set('user.name', name); } } }); const UserNameEditor = san.defineComponent({/*...*/}); // connect 时提供自创建的 storeA const NewUserNameEditor = connect( myStore, {name: 'user.name'}, {change: 'changeUserName'} )(UserNameEditor);
mapStates 参数指定了要把哪些状态注入到组件,key 是要注入到组件的数据项名称,value 是 store 中状态项的名称。
mapStates
import {store, connect} from 'san-store'; const NewUserNameEditor = connect( {name: 'user.name'} )(san.defineComponent({ // connect 后,name 数据项由 store 提供 template: '<div title="{{name}}">......</div>' }));
使用 store 时,我们可以通过调用 store.dispatch(actionName, payload) 方法更新应用状态。但如果在组件中也这么做,就会有些问题:
store.dispatch(actionName, payload)
通过指定 mapActions 可以在组件的 actions 成员上生成 dispatch action 的快捷方法,让组件可以更便捷的 dispatch action。mapActions 的 key 是要映射到组件 actions 成员上的方法名,value 是 action 的名称。
mapActions
import {store, connect} from 'san-store'; const UserNameEditor = connect( {name: 'user.name'}, {change: 'changeUserName'} )(san.defineComponent({ submit() { // 通过 mapActions,可以把 dispatch action 简化成组件自身的方法调用 // store.dispatch('changeUserName', this.data.get('name')); this.actions.change(this.data.get('name')); } }));
当实际业务中真的需要多个 Store 实例时,可以通过 connect 自行创建 connector,连接 Store 实例和 San 组件。步骤如下:
import {Store, connect} from 'san-store'; // 创建模块A中的store实例 const storeA = new Store({ initData: { name:'erik' }, actions:{ changeUserName(name) { return builder().set('user.name', name); } } }); const storeB = new Store({ initData: { todos: [] }, actions:{ changeTodos(payload) { return builder().set('todos', payload); } } }); const Container = san.defineComponent({/*...*/}); // 调用手动创建的connectA方法进行storeA和组件连接 const NewContainer = connect( storeA, {name: 'user.name'}, {change: 'changeUserName'} ).connect( storeB, {todos: 'todos'}, ['changeTodos'] )(Container);
use api 提供了对 san-composition 形式组件的 store 支持:
useState
useAction
import san from 'san'; import {defineComponent, template, method} from 'san-composition'; import {useState} from 'san-store/use'; import {Store} from 'san-store'; // 创建模块A中的store实例 const myStore = new Store({ initData: { name:'erik' }, actions:{ changeUserName(name) { return builder().set('user.name', name); } } }); export default defineComponent(context => { template(` <div>{{name}} <input value="{=newName=}"><button on-click="change">change</button> </div> `); const name = useState(myStore, 'user.name', 'name'); const newName = data('newName', ''); let changeUserName = useAction(myStore, 'changeUserName'); method({ change: () => { changeUserName(newName.get()); } }); }, san);
版权所有:中国计算机学会技术支持:开源发展技术委员会 京ICP备13000930号-9 京公网安备 11010802032778号
san-store
San 框架的官方应用状态管理套件,其理念是类似 flux 的单向流。
虽然应用状态管理已经是流行的理念,我们依然推荐您在开始之前,先看看 为什么要进行应用状态管理,可能会有些思考和启发。
下载
NPM:
使用
从例子开始和模仿比死啃枯燥的文档要更人性化。 Todos项目 和 RealWorld App 展示了如何在项目里使用 san-store 进行状态管理。 多store的Todos项目 展示了如何再项目中使用 多 store connect 及 composition api 的用法。
本文档描述了 san-store 的基本使用场景。想了解 san-store 都提供了什么,可以参阅 API文档
单Store与多Store
一个应用具有唯一的应用状态源,在一个地方管理整个应用的所有状态,是一个比较共识的方式。所以 san-store 提供了默认的 Store 实例。绝大多数时候,应用开发者不需要手工创建自己的 Store 实例,只需要 import 默认的 store。
store 并没有提供修改状态数据的方法,修改状态数据只能通过 dispatch action 来做到,具体细节请参考 Action 章节。通过 addAction 方法可以添加 action。
当同一系统中有不同团队开发自己的业务模块,各团队之间没有状态共享,可以考虑分别建立 Store 实例进行开发。通过 new Store 创建自己的 Store 实例。创建时可以传入初始化数据和声明 actions。
本节最后,还是要强调下,应用开发应当遵循 一个应用具有唯一的应用状态源。说白了就是 要按常理出牌。
Action
通过 addAction 方法可以为 Store 添加 Action。 Action 是 san-store 最重要的组成部分之一,它:
如果你使用了 san-store,Action 应该是你业务组件的唯一出口:用户操作事件等需要改变应用状态时,都应该 dispatch Action。
变更应用状态
Action 接收一个 payload,返回一个 san-update 的 builder 对象。store 使用 builder 对象生成状态变更函数,并执行它,使 store 内部的状态得到更新。当然,如果当前 action 不期望对 store 的状态进行更新,可以不返回 builder 对象。
san-update 是一个 Immutable 的更新对象库,其提供了一些更新函数(如set、push等),通过
newObj = set(oldObj, 'x', 1)的使用形式让对象更新 Immutable。builder 是 san-update 提供的一个很好用的功能,通过 builder 你可以预定义一系列的数据更新操作,然后通过builder.build方法可以获得一个更新函数。san-store 就是利用这个功能,使用 action 返回的 builder 生成对象更新函数,再调用它进行 store 内部状态更新。san-update 的 builder 支持预定义所有 san-update 支持的数据操作,通过 san-update文档:可用指令 可以查看所有操作类型。常用的有:
使用前请阅读 san-update文档:使用builder构建更新函数 进行详细了解。
获取当前应用状态
Action 的第二个参数是一个对象,其中的 getState 方法可以用于获得当前 store 中的应用状态。这个方法是 this 无关的。
如果我们的更新操作仅依赖于当前数据状态项的值,也可以使用 san-update 提供的 apply 方法。
异步过程
同步的 Action 返回一个 builder,并立即更新数据状态,但我们经常会遇到异步的场景,常见的比如请求数据、返回并更新应用状态。Action 在设计上作为 业务组件的唯一出口, 对异步支持的方式如下:
下面是一个简单的例子: 一个列表请求的行为,此时要显示 loading,在请求返回时更新应用状态中的列表项,同时隐藏 loading。
例子中有下面几个要点:
异步 Action 在 dispatch 时将返回 Promise 对象,以便于 Action 完成后的逻辑控制。
组件的connect
connect到默认store
connect 方法支持对 默认store实例 和 San 组件进行连接,步骤和 redux 类似:
通常,我们只需要对当前声明的组件进行 connect。此时可以合并成一句
connect到自己创建的store
当实际业务中需要多个 Store 实例时,可以自行创建 Store 实例。connect 方法支持连接到指定的 Store 实例。
映射store状态与组件data
mapStates参数指定了要把哪些状态注入到组件,key 是要注入到组件的数据项名称,value 是 store 中状态项的名称。组件上可直接调用的 dispatch action 方法
使用 store 时,我们可以通过调用
store.dispatch(actionName, payload)方法更新应用状态。但如果在组件中也这么做,就会有些问题:通过指定
mapActions可以在组件的 actions 成员上生成 dispatch action 的快捷方法,让组件可以更便捷的 dispatch action。mapActions的 key 是要映射到组件 actions 成员上的方法名,value 是 action 的名称。connect 多个 store
当实际业务中真的需要多个 Store 实例时,可以通过 connect 自行创建 connector,连接 Store 实例和 San 组件。步骤如下:
componsition api
use api 提供了对 san-composition 形式组件的 store 支持:
useState定义数据。useAction定义方法。