经过几个月的摸索和实践,终于确定了 ar-front
项目(以前叫 ar-front
)的开发目标:专注于前端的数据模型包装器。
所谓“数据模型包装器”,是我专门使用的词汇,读者可类比于 Data Mapper 模式或 ORM. ar-front
的目标是能够在前端项目中使用,为调用后端提供的 API 接口提供封装和属性映射。
不同于后端的 ORM 等框架,它们面向的数据源大多是关系型数据库。而关系型数据库的调用方式是非常统一的,它们拥有一套标准的 SQL 语句来执行一些标准的动作,如增、删、改、查等。而前端项目面对的数据源是后端提供的 API 接口,它们大多是不太统一和标准的,尤其是在中国这样特殊的环境下。为此,很难为 ar-front
提供一套标准的动作以适应所有的情况。所以,ar-front
做出的第一个重要的决定是:**除了属性定义外,不提供任何的动作行为。**这也意味着 ar-front
不是开箱即用的,也意味着 ar-front
就是一个纯粹的数据模型包装器。它所提供的能力仅仅是属性定义而已。
所以,在使用 ar-front
时,第一步是定义模型。我们推荐使用继承的方式来定义模型,属性定义在静态方法 defineAttributes
内:
import { Model } from 'ar-front'
export default class User extends Model {
static defineAttributes() {
this.attr('id', { type: Number })
this.attr('name', { type: String })
this.attr('age', { type: Number })
}
}
虽然没有提供任何的标准行为,但由于 User
模型是一个 JavaScript 类,我们可以自己定义行为。这里我们演示为 User
模型定义几个标准的动作,以不同的方式创建、删除、更新、查看 User
资源。
import axios from 'axios'
import { Model } from 'ar-front'
export default class User extends Model {
static defineAttributes() {
this.attr('id', { type: Number })
this.attr('name', { type: String })
this.attr('age', { type: Number })
}
static async findAll () {
const response = await axios.get('/users')
return response.data.map(dateItem => new this(dataItem))
}
static async find (id) {
const response = await axios.get(`/users/${id}`)
return new this(response.data)
}
static async create (attributes) {
const response = await axios.post('/users', attributes)
return new this(response.data)
}
async update (attributes) {
const response = await axios.put(`/users/${this.id}`, attributes)
this.attributes = response.data
}
async save () {
const response = await axios.put(`/users/${this.id}`, this.attributes)
this.attributes = response.data
}
async destroy () {
await axios.delete(`/users/${this.id}`)
}
}
这里用不同的方式定义行为,有些是静态方法,有些是实例方法,而且方法与属性定义在同一个类当中,这就是 Active Record 模式。这么做是充分提供模型动作的灵活性,例如同样是调用创建动作,我们可以用两种方式:
// 使用静态方法
const user = await User.create(attrs)
// 或使用实例方法
const user = new User(attrs)
await user.save()
调用更新动作亦是如此
// 直接调用 update 动作
await user.update(attrs)
// 或先更新 user 的属性,然后调用 save 动作
user.attributes = attrs
await user.save()
至于使用何种方式可根据实际场景调整。
ar-front
能做的事实在有限,它将决定权交给了使用者。在以上的例子里,除去 defineAttributes
静态方法、attr
静态方法、new
构造方法、attributes
读取器和设置器,你会发现它什么都不剩了。把这些魔法全去掉,它就是一个普通的 JavaScript 类:
class User {
id = null
name = null
age = null
static async findAll ()
static async find (id)
static async create (attributes)
async update (attributes)
async save ()
async destroy ()
}
但 ar-front
提供了一些额外的支持,也许开发者们能够用得上:
-
提供了类型转换。例如你可能调用
user.age = '18'
,但期望读取的时候是数字类型的18
.类型转换是在应用填写表单时一个有用的特性。不能完全保证,但有些表单控件是不支持类型的。例如原生的 HTML
<input>
控件,即使设置了type="number"
,但绑定返回的值依然是字符串,正如上面的"18"
所示。 -
user.attributes
只返回定义的类型,user.attributes =
只设置定义的类型。例如你为user
设置属性:user.attributes = { id: 1, name: 'Jim', age: 18, foo: 'foo' // 注意 foo 是多出来的字段 }
由于
foo
是未定义过的,在读取时会自动过滤该字段,调用user.attributes
查阅时只会得到:{ id: 1, name: 'Jim', age: 18 }
这在多数情况下是有用的,尤其是后端接口不假思索地返回乱七八糟的字段的时候。我们往往只需要自己关心的字段。
构造函数也借鉴了上述例子的机制,所以我们可以放心地调用如 new User(attrs)
. 至于其他特性,如可定义默认值等就不一一说明了,更详细的用法可参考文档说明。
最后一点,ar-front
对 Vue 2.x 的响应式提供了支持。我发现有些数据映射框架不支持 Vue 2.x 的响应式,还好 ar-front
提供了支持。如果要在 Vue 2.x 支持响应式,一个重要的地方是配置 defineAttributesIn
等与 "object"
. 有关如何配置 Model,可参考文档说明。