Created
July 14, 2017 09:47
-
-
Save otakustay/e916dbef301c28ea0d10335d96cc0166 to your computer and use it in GitHub Desktop.
Store normalization with backend API connected
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * @file 管理Store Nomalization的相关逻辑 | |
| * @author zhanglili | |
| */ | |
| import {reduce} from 'lodash'; | |
| const UPDATE_ENTITY_TABLE = 'UPDATE_ENTITY_TABLE'; | |
| /** | |
| * 创建一个用于在请求结束后更新Normalized Store中的实体数据的高阶函数 | |
| * | |
| * 当传递一个`store`后,会返回一个`withTableUpdate`函数,这个函数的用法如下: | |
| * | |
| * ```javascript | |
| * const apiWithTableUpdate = withTableUpdate(entitySetName, selectEntities)(api); | |
| * const response = await apiWithTableUpdate(args); | |
| * ``` | |
| * | |
| * 当一个用于获取后端数据的API函数被这个高阶函数包装后,会在响应返回时额外做以下行为: | |
| * | |
| * 1. 调用`selectEntities`函数,将`response`传入并得到一个对象,该对象是一系列需要更新的实体,以实体的索引属性(比如id)为键 | |
| * 2. 派发一个类型为`UPDATE_ENTITY_TABLE`的Action,并通过`payload`提供`entitySetName`和`entity`属性 | |
| * | |
| * 当reducer包含了对这个Action的处理时,会有以下逻辑: | |
| * | |
| * 1. 根据`entitySetName`从`state`中获取到对应的表 | |
| * 2. 将当前从响应中获取的实体一一合并到表中 | |
| * | |
| * 通过这种方式,可以将后端的接口与Normalized Store中的实体信息建立关联,保持所有实体更新在实体表中,其它地方通过id的方式引用保持信息同步 | |
| * | |
| * 如果一个响应同时返回多个实体,也同样可以调用多次`withTableUpdate`来进行包装: | |
| * | |
| * ```javascript | |
| * import {property, keyBy} from 'lodash'; | |
| * | |
| * const withUsersTableUpdate = withTableUpdate('usersByName', res => keyBy(res.users, 'username')); | |
| * const withCommitsTableUpdate = withTableUpdate('commitsById', res => keyBy(res.commits, 'id')); | |
| * const apiWithTableUpdate = withUsersTableUpdate(withCommitsTableUpdate(api)); | |
| * ``` | |
| * | |
| * @param {Object} store Redux store | |
| */ | |
| export const createTableUpdater = ({dispatch}) => (entitySetName, selectEntities) => { | |
| const dispatchTableUpdate = responseData => { | |
| const entities = selectEntities(responseData); | |
| dispatch({type: UPDATE_ENTITY_TABLE, payload: {entitySetName, entities}}); | |
| return responseData; | |
| }; | |
| return fetchFunction => (...args) => fetchFunction(...args).then(dispatchTableUpdate); | |
| }; | |
| /** | |
| * 与`createTableUpdater`合作使用的reducer函数,具体参考上面的注释说明 | |
| * | |
| * @param {Object} [state] 当前的状态 | |
| * @param {Object} [action] 收到的Action | |
| */ | |
| export const reducer = (state = {}, action) => { | |
| if (action.type !== UPDATE_ENTITY_TABLE) { | |
| return state; | |
| } | |
| const {payload: {entitySetName, entities}} = action; | |
| const entitySet = state[entitySetName] || {}; | |
| const mergedEntitySet = reduce( | |
| entities, | |
| (result, value, key) => { | |
| // 因为当前的系统前后端接口并不一定会返回一个完整的实体, | |
| // 如果之前有一个比较完整的实体已经在`state`中,后来又来了一个不完整的实体,直接覆盖会导致字段丢失, | |
| // 因此针对每个实体,使用的是浅合并的策略,保证第一级的字段是不会丢的 | |
| const currentEntity = result[key]; | |
| return { | |
| ...result, | |
| [key]: {...currentEntity, ...value} | |
| }; | |
| }, | |
| entitySet | |
| ); | |
| const newState = { | |
| ...state, | |
| [entitySetName]: mergedEntitySet | |
| }; | |
| return newState; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment