Home Reference Source

model-adapter

NPM version Build Status Coverage Status Known Vulnerabilities changelog license

npm-image

模型适配器: 后端数据与前端数据的桥梁

专注于解决前端那些老生常谈的问题(没碰到过算你赢), 如果你遇到过以下场景, 请试用一下

初衷

Vue 或者其他视图层框架中, 如果直接使用如下插值表达式, 当嵌套对象(通常是后端返回的数据)中的某一层级为空时就会报错 TypeError: Cannot read property 'xxx' of undefined, 造成整个组件都无法渲染.

{{a.aa.aaa}}

为了解决这种问题, 让前端的视图层能够容错增强代码的健壮性, 我们可能要写出如糖葫芦一般的防御性代码, 例如这样 {{a && a.aa && a.aa.aaa}}, 要是再多嵌套几层, 简直不忍直视啊.

舒服一些的处理方式是通过 object path get 之类的库事先处理好数据, 形成前端的视图层模型, 尽量避免嵌套数据, 再到视图层中使用, 例如

// 在视图中使用: {{aaa}}
var vm = {
    aaa: _.get('a.aa.aaa')
};

核心思路

建立一个新的模型, 通过设置默认值来补齐源数据(模型)上可能缺少的对象嵌套层次. 这样我们就能够以访问源数据一致的方式来访问新模型上的数据.

例如要访问源数据上的 a.aa.aaa, 如果源数据的 anull, 那么我们直接访问肯定是会报错的.

因此我们可以准备一份默认数据, 来补齐源数据上可能缺失的数据.

新模型(target)                       源数据(source)          默认值(default)
{                                   {                       {
    a: {                        <─       a: null,       <─      a: {
        aa: {                                                       aa: {
            aaa: 'default-aaa'                                            aaa: 'default-aaa'
        }                                                           }
    },                                                          },
    b: 'source-b'               <─       b: 'source-b'          b: 'default-b',
    c: 'default-c'              <─                      <─      c: 'default-c'
}                                   }                       }

另外一种映射属性的实现思路可以参考v0.0.1版本


针对格式化数据的需求, 采取的思路为将属性改写为 setter/getter, 以输入和输出的概念来适配新模型上的属性

// setter
a.aa.aaa = 1566814067549 // 输入(input)
// getter
a.aa.aaa // 2019-08-26   // 输出(output)

示例

嵌套数据/空数据: 用默认值来补齐(重点是补齐嵌套对象)

import ModelAdapter from 'model-adapter';

// 这里示例由后端接口返回的数据
var ajaxData = {
    name: null,
    age: 18,
    extData: null
};

var model = new ModelAdapter(ajaxData, {
    name: 'Guest',
    extData: {
        country: {
            name: 'China'
        }
    }
});

console.log(model.name);                 // 'Guest'
console.log(model.age);                  // 18
console.log(model.extData.country.name); // 'China'

格式化数据: 变形

import ModelAdapter from 'model-adapter';

var ajaxData = {
    foo: {
        bar: {
            date: 1565001521464
        }
    }
};

var model = new ModelAdapter(ajaxData, null, {
    'foo.bar.date': {
        transformer: function(value, source) { // 变形器负责格式化数据
            return new Date(value).toISOString();
        }
    }
});

var restored = model.$restore();

console.log(model.foo.bar.date);    // '2019-08-05T10:38:41.464Z'
console.log(restored.foo.bar.date); // 1565001521464

数组: 在 transformer 中适配数组元素的模型

import ModelAdapter from 'model-adapter';

var ajaxData = {
    users: [{
        name: null,
        age: 18,
        extData: null
    }, {
        name: 'Shine',
        age: 19,
        extData: {
            country: {
                name: 'USA'
            }
        }
    }]
};

var model = new ModelAdapter(ajaxData, null, {
    users: {
        transformer: function(value) {
            return value.map(function(item) {
                return new ModelAdapter(item, {
                    name: 'Sun',
                    extData: {
                        country: {
                            name: 'China'
                        }
                    }
                });
            });
        }
    }
});

console.log(model.users[0].name);                 // 'Sun'
console.log(model.users[0].age);                  // 18
console.log(model.users[0].extData.country.name); // 'China'

console.log(model.users[1].name);                 // 'Shine'
console.log(model.users[1].age);                  // 19
console.log(model.users[1].extData.country.name); // 'USA'

先声明模型再设置源数据

import ModelAdapter from 'model-adapter';

// 声明模型(预先定义好 defaults 和 propertyAdapter)
var model = new ModelAdapter(null, {
    name: 'Guest',
    extData: {
        country: {
            name: 'China'
        }
    }
});

var ajaxData = {
    name: null,
    age: 18,
    extData: null
};
// 设置源数据
model.$setSource(ajaxData);

console.log(model.name);                 // 'Guest'
console.log(model.age);                  // 18
console.log(model.extData.country.name); // 'China'

声明模型类

import ModelAdapter from 'model-adapter';

// 声明模型类(预先定义好 defaults 和 propertyAdapter)
class User extends ModelAdapter {
    constructor(source) {
        super(source, {
            name: 'Guest',
            extData: {
                country: {
                    name: 'China'
                }
            }
        });
    }
}

var ajaxData = {
    name: null,
    age: 18,
    extData: null
};

// 使用模型类时, 只需要设置源数据
var user = new User(ajaxData);

console.log(user);                      // <User>
console.log(user.name);                 // 'Guest'
console.log(user.age);                  // 18
console.log(user.extData.country.name); // 'China'

与其他框架集成

建议的接入方式

例如

// service/user.js
export function getUser() {
    return axios('/user').then(function(response) {
        return new ModelAdapter(response.data, {
            name: 'Guest',
            extData: {
                country: {
                    name: 'China'
                }
            }
        });
    });
}

API 概览

参考