qn-fe-core
v1.0.1
Published
前端基础库,定位是框架/界面库无关的基础工具,内容围绕状态管理方案实现,包括:
Downloads
1
Readme
Qiniu fe-core
前端基础库,定位是框架/界面库无关的基础工具,内容围绕状态管理方案实现,包括:
- Disposable 类实现及基于 mobx 的 Store 类实现
- 副作用(
fetch
/router
/storage
)与 Store 的绑定实现
fe-core 采用 Typescript 实现,对使用方没有要求,ES 项目中也可以引入 fe-core 并使用。
为什么
响应式编程(Reactive Programing)的好处
响应式编程的范式,适合实现状态随用户交互、时间变更的复杂应用逻辑。将应用的行为拆解为正交的两个部分:数据的维护逻辑与数据推导出的逻辑,便于逻辑的组织与实现。
例如,在 name
每次改变时打印出 name
的值:(这里以 MobX 提供的能力为例)
// 第一种方式,每次修改完手动调用 `console.log`,缺点:很难避免遗漏
o.name = 'foo'
console.log(o.name)
// 第二种方式,提供专门的用于修改 `name` 的方法,缺点:
// 要么不一致(有的值通过 `=` 有的值通过 `set` 方法)
// 要么繁琐(所有的值都要通过特定的 `set` 方法修改)
const o = {
setName(name) {
this.name = name
console.log(name)
}
}
o.name = 'foo'
// 基于响应式的数据,手动指定观测对象(依赖)
reaction(
() => o.name,
name => console.log(name)
)
// 基于响应式的数据,自动收集观测对象(依赖)
autorun(
() => console.log(o.name)
)
再如,根据 firstName
跟 lastName
计算得到 fullName
:
// 第一种方式,每次修改完 `firstName` 或 `lastName` 都记得重新计算 `fullName` 并赋值
// 缺点:繁琐且很难避免遗漏,有多少处对数据的修改,就需要维护多少处逻辑
o.firstName = 'foo'
o.lastName = 'bar'
o.fullName = getFullName(o.firstName, o.lastName)
// 第二种方式,提供专门的用于修改 `fistName` & `lastName` 的方法
// 缺点:有多少计算依赖,就需要实现多少处的 set 方法,需要维护 m * n 处逻辑
// (m 为 `fullName` 这样的复杂逻辑的数量,n 为逻辑依赖的数据项数)
const o = {
setFirstName(firstName) {
this.firstName = firstName
this.updateFullName()
}
setLastName(lastName) {
this.lastName = lastName
this.updateFullName()
}
updateFullName() {
this.fullName = getFullName(this.firstName, this.lastName)
}
}
// 基于响应式的数据,声明数据间的映射关系
// 需要维护 m 处逻辑,即复杂逻辑的数量
class O {
@computed get fullName() {
return getFullName(this.firstName, this.lastName)
}
}
数据驱动的好处
如果拥有了响应状态变更的能力,也就具备了将整个应用的行为都由状态变更来驱动的条件。而由状态数据来驱动应用行为,有这样的好处:
- 通过比较数据的值即可验证应用的状态与行为,可以大大简化开发时的调试与测试用例的编写
- 可以做到应用状态的保存与恢复,使一些复杂功能的实现变得简单(如开发辅助工具、用户操作状态恢复等)
为什么不使用业界主流方案或原生 API,而是通过与 Store 绑定后使用
如,为什么不使用 react-router,而使用 router-store 以及基于 router-store 实现的路由方案,原因有:
- 使用数据的变更驱动与外界的交互,如这里的路由读取、跳转等行为
- 将这部分状态(如这里的路由状态)纳入到统一的状态管理方案中,好处有
- 同类逻辑形状的一致性
- 数据流向的一致性
- 支持推导与观测,方便其他部分使用
fe-core 采用的方案
通过 Mobx 实现“透明地”(transparently)引入响应式编程范式。对比 Rx 等 FRP 框架:
避免了显式的数据封装层,读写方式与 plain Javascript 一致,使用也更简洁
舍弃了时间维度的信息,不便处理需要基于时间维度信息进行推导/响应的情况
一个简单而典型的例子是,单选组件的当前值由用户上一次操作选中值与可选列表的 default 项共同决定,取其中更新(发生更晚)者
要求将数据变更实现为 action 函数,显式地标记为应用行为,实现限制与管理
将 IO 行为(网络请求、本地存储、浏览器 URL 等)映射为不同的状态实例(Store),通过修改这些 Store 的数据实现对应副作用的触发
内容
store/disposable
Disposable
类,收集对象销毁时需要执行的步骤。数据的观测底层实现依赖于事件,因此需要在对象被销毁的时候解除对应的监听逻辑。store
基于 MobX 的状态容器 Store 类,提供
dispose
、snapshot
及resume
等能力fetch-store
基于 Store 类的 fetch 绑定,将网络请求映射为数据
react-router-store
基于 Store 类的路由绑定,将浏览器的导航行为映射为数据
storage-store
基于 Store 类的 storage(localStorage/sessionStorage)绑定,将本地存储映射为数据
依赖安装
推荐使用 yarn 安装依赖
yarn install
开发相关
目前本地开发站点(如 financial)时无需构建本库,可直接使用源代码(ts 文件);构建用于发布 npm 包前。
npm run build # 构建
npm run test # 测试,TODO
npm run lint # 代码风格检查
npm run clean # 清理构建产物