一个简单的数据模型包装器,可定义属性和方法,使用 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