近年来数据管理一直是前端比较活跃的领域,优秀的技术方案层出不穷,flux、redux、mobx、dva、rematch以及vuex等。可以对这些技术按使用时的复杂度进行一个大致的排列:
flux > redux ≈ mobx > dva ≈ rematch ≈ vuex
对于技术要大范围的被推广使用,一方面是要很好地解决实际问题;另一方面是要降低技术的使用门槛。
从flux开始,到redux、mobx,再到dva、rematch、vuex,使用的复杂度在降低,受欢迎程度在提高。数据管理变得越来越易用。
到dva、rematch、vuex这里,它们都做出了相同地改进,将数据和数据处理放在一个模块里面,将之前分散的数据和数据处理聚集了起来。似乎都意识到数据管理需要有秩序,有秩序才好维护。
其实后端在这方面已经很成熟了,那就是数据库。而前端由于自身发展原因,数据管理这块近些年才比较受重视,缺少数据库这种高度抽象的数据模型。程序所需要的数据模型,虽然因领域在实现上会有差异,但组成都是一样的:数据结构、数据操作和数据约束。
按照这个组成结构,在前端领域也能建立起相应的数据模型。理论上这个数据模型可以将前端目前所有的数据管理方案都包含进来,作为这个数据模型的某一种实现。
等等,为什么要建这么一个数据模型,难道又是造轮子吗?我也找不到很确切的理由,现有的方案都能解决问题,可能只是想实践自己的想法。下面直接讲对这个数据模型的实现(这个只是数据模型的一种实现,对于前端数据模型的实现应该还有其他的,权当抛转引玉)。
数据操作
为什么先是数据操作?因为这个最接近我们实际的需求,由这个可以引出另外两个。
前端数据操作莫非就是写和读,即输入和输出。
这里输入和输出都采用纯函数的形式:
输入:data => fn(data) => model => ...
输出:... => model => referToState(fn) => data
图形展示更为直观:
模型和state的结构是一一对应的.
获取数据源state中的p3节点
referToState(model.p1.p3)
复制代码
更新数据源state中的p3节点
model.p1.p3(data)
复制代码
红色节点代表被更新的节点
数据结构
后端数据库有很多模型,有关系型、层次模型以及网状模型。这里借鉴网状模型,在前端采用树状结构。
输入:data => fn(data) => model => ...
输出:... => model => referToState(fn) => data
model是一个普通对象,数据的输入、输出就是通过model对象来实现。model并不是数据源,而是数据源的抽象结构。数据源可能由其他某种实现来生成,比如由redux、mobx或者其他的数据管理技术实现。
数据约束
在日常开发中,后端数据库表的概念就是一个基本的数据结构,里面包含了很多数据的约束条件。前端的数据模型也需要一个基本的数据结构,来承载数据以及表达数据约束关系。这里也采用普通对象来表达这个数据结构。
还需要对这个数据结构中的原子数据进行约束,这里提供一个定义原子数据的函数:
原子数据: gluer(updater, initialState) => fn(data)
基本的数据结构: { name:gluer((data, state)=>`${data}·Stark`,'Tony')}
model由原子数据和基本的数据结构组成
gluer会产生(注意并不是返回,而是由返回的函数再生成)一个函数fn,这个fn会作为该原子数据输入和读取的凭证。
输入:data => fn(data) => model => ...
输出:... => model => referToState(fn) => data
原子数据具有:updater和initialState两个属性
updater负责处理输入该节点的数据,并将返回值传递出去,传递出去的值会按一定规则更新到对应的数据源中去;
initialState则表明原子数据具体的数据类型和初始值,由js缺少强类型约束,这里更多是当做一种约定。(可以用ts来做强制约束,实际上在声明文件中也是这么做了)
数据完整性和一致性
数据的完整性和一致性对于数据模型是必须保证的。上面三点里面并没有提到具体如何保证,因为这部分可能有很多实现。**这也是这一套设计中最灵活的部分。可以接入redux、mobx或者其他的技术方案。**只要能保证完整性和一致性都可以。 这里笔者写了一个接入redux的实现,可以简略地一瞥。(2019年5月21日更新:不接入第三方技术方案,自实现数据完整性和一致性,femo)
基本数据结构model:
import { gluer } from 'glue-redux';
const country = gluer('中国');
const app = {
country,
};
export default app;
复制代码
数据源抽象store:
import app from 'model/app';
const store = { app };
export default store;
复制代码
与redux结合
import { createStore, combineReducer } from 'redux';
import { destruct } from 'glue-redux';
import store form './store';
const reduxStore = createStore(() => ({}));
const { reducers, referToState } = destruct(reduxStore)(store);
reduxStore.replaceReducers(combineReducer(reducers));
console.log(referToState(store));
// { app: { country: '中国' } }
console.log(referToState(store.app));
// { country: '中国' }
console.log(referToState(store.app.country));
// "中国"
store.app.country('俄罗斯');
console.log(referToState(store.app.country));
// "俄罗斯"
console.log(referToState(store.app));
// { country: '俄罗斯' }
console.log(referToState(store));
// { app: { country: '俄罗斯' } }
复制代码
更多细节请查看这里glue-redux,谢谢阅读:-D
关联文章:
Redux: 不要再使用action type了