一个简单的数据模型包装器,可定义属性和方法,使用 Active Record 风格。
ar-front
是一个为前端项目准备的数据模型包装器,当然不经过改造亦可用于后端。一个数据模型即是一个类,数据和行为共同封装在一个类里(曰 Active Record 模式)。例如,包装一个 User
模型后,会得到如下的调用体验:
// 列表页:获取所有用户,返回一个 User 类的实例数组
const users = await User.list()
// 展示页:获取一个用户详情
const user = await User.find(1)
console.log(user.name)
console.log(user.age)
// 新建页:创建一个 User 实例,设置属性,然后保存
const user = new User()
user.name = 'Jim'
user.age = 18
await user.save()
// 更新页:设置属性,然后保存
const user = await User.find(1)
user.name = 'Jim'
user.age = 18
await user.save()
其他更详细的用法参见文档。
有关 ar-front
的开发理念,可阅读 docs/开发理念.md.
由于当前正在开发中,直接通过 github 仓库获取到最新的稳定开发版:
# 使用 yarn
$ yarn add https://github.com/yetrun/ar-front.git
# 使用 npm
$ npm install --save https://github.com/yetrun/ar-front.git
假设后端提供操作用户的 Restful API.
const axios = require('axios')
const { Model } = require('ar-front')
// 定义模型类
const User = Model.extend({
// 定义属性
attributes: {
name: { type: String },
age: { type: Number }
},
// 定义行为
actions: {
async save () {
if (this.id) {
const { data } = await axios.put(`/users/${this.id}`, this.attributes)
this.attributes = data
} else {
const { data } = await axios.post('/users', this.attributes)
this.attributes = data
}
}
},
static: {
async list () {
const { data } = await axios.get('/users')
return data.map(dataItem => new User(dataItem))
},
async find (id) {
const { data } = await axios.get(`/users/${id}`)
return new User(data)
}
}
})
// 返回用户列表
const users = await User.list()
// 创建用户
const user = new User()
user.name = 'James'
user.age = 18
await user.save()
// 更新用户
const user = User.find(1)
user.name = 'James Dean'
user.age += 1
await user.save()
ar-front
最大的优点是直接在对象上设置和返回属性,并能够自动进行类型转换和设置默认值。
现有个简单的模型类定义:
const User = Model.extend({
attributes: {
name: { type: String },
age: { type: Number, default () { return 18 } }
}
})
直接在对象层面访问和设置属性如下:
const user = new User()
// 初始化即有默认值
console.log(user.name) // null
console.log(user.age) // 18
// 可通过属性直接在对象层面设置和获取
user.name = 'Jim'
console.log(user.name) // 'Jim'
// 自动进行类型转换
user.age = '18'
console.log(user.age) // 18
console.log(typeof user.age) // 'number'
const { Model } = require('ar-front')
Model.extend({
name: '...',
attributes: { /*...*/ },
computed: { /*...*/ },
actions: { /*...*/ },
static: { /*...*/ },
config: { /*...*/ }
})
使用 name
定义模型类的名称。
示例:
const User = Model.extend({
name: 'User'
})
console.log(User.name) // 'User'
使用 attributes
块定义计算属性,可定义属性的类型和默认值。
- 类型:支持以下五种类型的定义。除非是
Object
,否则当设置属性时,自动进行类型转换。- Number
- String
- Boolean
- Date
- Array
- Object
- 默认值:可设置常量值或函数,函数绑定的
this
是模型实例。
示例:
const User = Model.extend({
attributes: {
name: { type: String },
age: { type: Number, default: 18 },
registeredAt: { type: Date, default () { return new Date() }}
}
})
使用 computed
块定义计算属性。
示例:
const User = Model.extend({
attributes: {
firstName: { type: String },
lastName: { type: String }
},
computed: {
fullName: {
get () {
return this.firstName + ' ' + this.lastName
},
set (fullName) {
const [firstName, lastName] = fullName.split(' ')
this.firstName = firstName
this.lastName = lastName
}
}
}
})
let user = new User({ firstName: 'James', lastName: 'Dean' })
console.log(user.fullName === 'James Dean')
user = new User()
user.fullName = 'James Dean'
console.log(user.firstName === 'James')
console.log(user.lastName === 'Dean')
使用 actions
块定义实例方法。
示例:
const User = Model.extend({
actions: {
async save () {
// ...
},
getFullName () {
// ...
}
}
})
user = new User()
await user.save()
user.getFullName()
使用 static
块定义静态方法。
示例:
const User = Model.extend({
static: {
async find () {
// ...
},
current () {
// ...
}
}
})
await User.find(1)
User.current()
使用 config
块定义配置。
示例:
Model.extend({
config: {
dynamicAttributes: true,
defineAttributesIn: 'object',
setter: function () { /* ... */ },
deleter: function () { /* ... */ }
}
})
返回:模型类。
选项解释:
-
dynamicAttangributes
:是否支持动态属性,默认为false
. 默认情况下,this.attributes
只能返回和设置在attributes
块中定义的属性:const User = Model.extend({ attributes: { name: {}, age: {} } }) const user = new User({ name: 'Jim', foo: 'foo' }) console.log('foo' in user) // false user.attributes = { name: 'James', bar: 'bar' } console.log('bar' in user) // false
而一旦设置
dynamicAttributes
为true
,则this.attributes
会接受额外的属性。将上面的 Model 定义修改为:const User = Model.extend({ attributes: { name: {}, age: {} }, config: { dynamicAttributes: true } })
则相应的行为将会改变:
const user = new User({ name: 'Jim', foo: 'foo' }) console.log('foo' in user) // true user.attributes = { name: 'James', bar: 'bar' } console.log('foo' in user) // false console.log('bar' in user) // true
设置
dynamicAttributes
为true
可以简化定义,例如最简单的设置 Model 的方式可以为:const User = Model.extend({ config: { dynamicAttributes: true } })
但它的侵入性很大,除非特殊情况,还是建议使用明确声明
attributes
的方式。 -
defineAttributesIn
:可选值有prototype
和object
,默认值是prototype
. 它选择将属性的getter
和setter
设置在对象上还是原型上。假设我们设置 Model 为:const User = Model.extend({ attributes: { name: {}, age: {} } }) const user = new User({ name: 'Jim', age: 18 })
如果设置
defineAttributesIn
为prototype
,则属性设置在原型上:Object.keys(user) // [] Object.keys(Object.getPrototypeOf(user)) // ['name', 'age']
反之,如果
defineAttributesIn
为object
,则属性设置在对象上:Object.keys(user) // ['name', 'age'] Object.keys(Object.getPrototypeOf(user)) // []
-
setter
:自定义设置属性的方式,默认值是类似于下面行为的函数:function (key, value) { this[key] = value }
-
deleter
:自定义删除属性的方式,默认值是类似于下面行为的函数:function (key) { delete this[key] }
使用 mixin
部分配置混入,示例:
const restfulActions = {
actions: {
update () { /*...*/ },
destroy () { /*...*/ }
},
static: {
list () { /*...*/ },
find (id) { /*...*/ },
create (attrs) { /*...*/ }
}
}
Model.extend({
attributes: {
name: {},
age: {}
},
mixin: [
restfulActions,
// add other mixins
]
})
返回一个定制新的默认配置的 Model 类。如果你对默认的 Model 配置不满意,可以调用此方法生成一个新的 Model 类。
例如下面新生成的 Model 类默认支持动态属性:
const NewModel = Model.config({
dynamicAttributes: true
})
// 新的 Model 类也支持 extend 方法
const User = NewModel.extend()
// 可直接使用动态属性
const user = new User({ name: 'Jim', age: 18 })
console.log(user.name) // 'Jim'
console.log(user.age) // 18
定制 action 是通过 attributes
的 setter 和 getter 来实现的。我们还是以下面的模型类定义为例:
const User = Model.extend({
attributes: {
name: { type: String },
age: { type: Integer }
}
})
首先,可通过构造函数设置属性:
const user = new User({ name: 'Jim', age: 18 )
亦可通过 attributes
设置器:
const user = new User()
user.attributes = { name: 'Jim', age: 18 }
attributes
的返回器将返回属性的键值对:
user.attributes // { name: 'Jim', age: 18 }
一般是在定义 action 时使用 attributes
的 setter 和 getter,例如一个创建动作,代码实现是:
async create () {
const { data } = await axios.post('/users', this.attributes )
this.attributes = data
}
而不是用下面的代码,这样失去了使用 ar-front
库的意义了:
async create () {
const { data } = await axios.post('/users', { name: this.name, age: this.age } )
this.name = data.name
this.age = data.age
}
attributes
的设置会完全覆盖所有属性(它不是更新)。如果你有下面的模型实例:
user = new User({ name: 'Jim' })
下面的 attributes
覆盖会将 name
设置为 null
:
user.attributes = { age: 18 }
user.name // null
user.age // 18
在实例对象上调用 $model
方法可返回模型类。
示例:
const User = Model.extend({
name: 'User'
})
const user = new User()
console.log(user.$model === User) // true
console.log(user.$model.name) // 'User'
不使用 Model.extend
,可通过继承 Model
类来创建新的模型类。
import { Model } from 'ar-front'
class User extends Model {}
属性定义放在 defineAttributes
静态方法当中
import { Model } from 'ar-front'
class User extends Model {
static defineAttributes () {
this.attr('id', { type: String })
this.attr('name', { type: String })
this.attr('age', { type: Number })
}
}
由于模型本身是一个 JavaScript 类语法,可直接在类中定义方法:
import { Model } from 'ar-front'
class User extends Model {
// 定义静态方法
static list () {
// ...
}
// 定义实例方法
save () {
// ...
}
// 定义计算属性
get fullName () {
// ...
}
set fullName () {
// ...
}
}
可对模型类进行配置:
class User extends Model {
static defaultConfig = {
dynamicAttributes: true,
defineAttributesIn: 'object',
// 未定义 setter 和 deleter,则会自动继承默认定义
}
}
Model.extend
不是创建继承类,而是一个拥有 Model 的实例方法的全新的类。
class UserOne extends Model {}
const UserTwo = Model.extend({
name: 'UserTwo'
})
new UserOne() instanceof Model // true
new UserTwo() instanceof Model // false
我很喜欢 Vue 库,所以当然希望它在 Vue 中能够实现双向绑定。Vue 2.x 曾说过,绑定的对象需要是 Plain Object 的,但其实并不完全是,ar-front
库可以很好地在 Vue 中使用,只不过需要结合一些特别的配置。
想要在 Vue 2.x 中使用,只需要生成一个新的 Model 类。如下生成一个新的 Model 类并保存在 model.js
文件内:
// model.js
import Vue from 'vue'
import { Model } from 'ar-front'
export default Model.config({
defineAttributesIn: 'object',
setter (key, value) {
Vue.set(this, key, value)
},
deleter (key) {
Vue.delete(this, key)
}
})
定制模型类时引入 model.js
即可:
// user.js
import Model from './model'
export default Model.extend({
name: 'User',
attributes: {
// ...
}
})
Apache-2.0