Skip to content

Latest commit

 

History

History
139 lines (104 loc) · 5.68 KB

开发理念.md

File metadata and controls

139 lines (104 loc) · 5.68 KB

开发理念

经过几个月的摸索和实践,终于确定了 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,可参考文档说明。