作为位于波兰克拉科夫的软件公司 Railwaymen 的后端开发人员,我的一些任务依赖于模型来操作和定制从数据库检索的数据。 当我想提高我在前端框架方面的技能时,我选择了 Vue,我认为最好有一种类似的方法来在存储中对数据进行建模。 我从我在NPM上找到的一些库开始,但它们提供的功能比我需要的更多。
所以我决定构建自己的解决方案,令我非常惊讶的是,基本代码不到 15 行并且非常灵活。我在我开发的一个名为 Evally 的开源应用程序中实现了此解决方案 - 一个 Web 应用程序,可帮助企业跟踪员工的绩效评估和职业发展。 它会提醒经理或人力资源代表有关员工即将进行的评估,并收集评估其绩效所需的所有数据,以最公平的方式进行评估。
模型和列表
您唯一需要做的是创建一个类并在 Lodash JavaScript 库中使用 defaultsDeep 函数
_.defaultsDeep(object, [sources])
参数
object (Object)
: 目标对象[sources] (...Object)
: 源对象
返回值
(Object)
: 返回对象
此辅助函数: Lodash 文档
“递归地将源对象自身的和继承的可枚举字符串键属性分配给目标对象,对于所有解析为未定义的目标属性。源对象从左到右应用。设置属性后,将忽略同一属性的其他值。”
例如
_.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } })
// => { 'a': { 'b': 2, 'c': 3 } }
就是这样! 要尝试一下,请创建一个名为 base.js 的文件,并从 Lodash 包中导入 defaultsDeep 函数
// base.js
import defaultsDeep from "lodash/defaultsDeep";
接下来,创建并导出 Model 类,其中构造函数将使用 Lodash 辅助函数将值分配给所有传递的属性,并使用默认值初始化未收到的属性
// base.js
// ...
export class Model {
constructor(attributes = {}) {
defaultsDeep(this, attributes, this.defaults);
}
}
现在,创建您的第一个真正的模型 Employee,其中包含 firstName、lastName、position 和 hiredAt 的属性,其中“position”将“Programmer”定义为默认值
// employee.js
import { Model } from "./base.js";
export class Employee extends Model {
get defaults() {
return {
firstName: "",
lastName: "",
position: "Programmer",
hiredAt: ""
};
}
}
接下来,开始创建员工
// app.js
import { Employee } from "./employee.js";
const programmer = new Employee({
firstName: "Will",
lastName: "Smith"
});
// => Employee {
// firstName: "Will",
// lastName: "Smith",
// position: "Programmer",
// hiredAt: "",
// constructor: Object
// }
const techLeader = new Employee({
firstName: "Charles",
lastName: "Bartowski",
position: "Tech Leader"
});
// => Employee {
// firstName: "Charles",
// lastName: "Bartowski",
// position: "Tech Leader",
// hiredAt: "",
// constructor: Object
// }
您有两名员工,第一名员工的职位是从默认值分配的。以下是如何定义多名员工
// base.js
// ...
export class List {
constructor(items = []) {
this.models = items.map(item => new this.model(item));
}
}
// employee.js
import { Model, List } from "./base.js";
// …
export class EmployeesList extends List {
get model() {
return Employee;
}
}
List 类构造函数将收到的项目数组映射到所需模型的数组。唯一的要求是提供正确的模型类名称
// app.js
import { Employee, EmployeesList } from "./employee.js";
// …
const employees = new EmployeesList([
{
firstName: "Will",
lastName: "Smith"
},
{
firstName: "Charles",
lastName: "Bartowski",
position: "Tech Leader"
}
]);
// => EmployeesList {models: Array[2], constructor: Object}
// models: Array[2]
// 0: Employee
// firstName: "Will"
// lastName: "Smith"
// position: "Programmer"
// hiredAt: ""
// <constructor>: "Employee"
// 1: Employee
// firstName: "Charles"
// lastName: "Bartowski"
// position: "Tech Leader"
// hiredAt: ""
// <constructor>: "Employee"
// <constructor>: "EmployeesList"
使用此方法的方式
这个简单的解决方案允许您将数据结构保存在一个地方,并避免代码重复。DRY 原则非常棒! 您还可以根据需要自定义您的模型,例如在以下示例中。
自定义 Getter
您是否需要一个属性依赖于其他属性? 没问题; 您可以通过改进您的 Employee 模型来实现
// employee.js
import { Model } from "./base.js";
export class Employee extends Model {
get defaults() {
return {
firstName: "",
lastName: "",
position: "Programmer",
hiredAt: ""
};
}
get fullName() {
return [this.firstName, this.lastName].join(' ')
}
}
// app.js
import { Employee, EmployeesList } from "./employee.js";
// …
console.log(techLeader.fullName);
// => Charles Bartowski
现在您不必重复代码来做一些像显示员工全名这样简单的事情。
日期格式化
模型是为给定属性定义其他格式的好地方。 最好的例子是日期
// employee.js
import { Model } from "./base.js";
import moment from 'moment';
export class Employee extends Model {
get defaults() {
return {
firstName: "",
lastName: "",
position: "Programmer",
hiredAt: ""
};
}
get formattedHiredDate() {
if (!this.hiredAt) return "---";
return moment(this.hiredAt).format('MMMM DD, YYYY');
}
}
// app.js
import { Employee, EmployeesList } from "./employee.js";
// …
techLeader.hiredAt = "2020-05-01";
console.log(techLeader.formattedHiredDate);
// => May 01, 2020
与日期相关的另一个案例(我在开发 Evally 应用程序时发现的)是能够使用不同的日期格式。 这是一个使用 datepicker 的示例
- 从数据库获取的所有员工的 hiredAt 日期都采用以下格式
年-月-日,例如 2020-05-01 - 您需要以更友好的格式显示 hiredAt 日期
月 日,年,例如 2020 年 5 月 1 日 - datepicker 使用以下格式
日-月-年,例如 01-05-2020
使用以下代码解决此问题
// employee.js
import { Model } from "./base.js";
import moment from 'moment';
export class Employee extends Model {
// …
get formattedHiredDate() {
if (!this.hiredAt) return "---";
return moment(this.hiredAt).format('MMMM DD, YYYY');
}
get hiredDate() {
return (
this.hiredAt
? moment(this.hiredAt).format('DD-MM-YYYY')
: ''
);
}
set hiredDate(date) {
const mDate = moment(date, 'DD-MM-YYYY');
this.hiredAt = (
mDate.isValid()
? mDate.format('YYYY-MM-DD')
: ''
);
}
}
这会将 getter 和 setter 函数添加到处理 datepicker 的功能。
// Get date from server
techLeader.hiredAt = '2020-05-01';
console.log(techLeader.formattedHiredDate);
// => May 01, 2020
// Datepicker gets date
console.log(techLeader.hiredDate);
// => 01-05-2020
// Datepicker sets new date
techLeader.hiredDate = '15-06-2020';
// Display new date
console.log(techLeader.formattedHiredDate);
// => June 15, 2020
这使得管理多个日期格式非常简单。
模型相关信息的存储
模型类的另一个用途是存储与模型相关的常规信息,例如路由的路径
// employee.js
import { Model } from "./base.js";
import moment from 'moment';
export class Employee extends Model {
// …
static get routes() {
return {
employeesPath: '/api/v1/employees',
employeePath: id => `/api/v1/employees/${id}`
}
}
}
// Path for POST requests
console.log(Employee.routes.employeesPath)
// Path for GET request
console.log(Employee.routes.employeePath(1))
自定义模型列表
不要忘记 List 类,您可以根据需要进行自定义
// employee.js
import { Model, List } from "./base.js";
// …
export class EmployeesList extends List {
get model() {
return Employee;
}
findByFirstName(val) {
return this.models.find(item => item.firstName === val);
}
filterByPosition(val) {
return this.models.filter(item => item.position === val);
}
}
console.log(employees.findByFirstName('Will'))
// => Employee {
// firstName: "Will",
// lastName: "Smith",
// position: "Programmer",
// hiredAt: "",
// constructor: Object
// }
console.log(employees.filterByPosition('Tech Leader'))
// => [Employee]
// 0: Employee
// firstName: "Charles"
// lastName: "Bartowski"
// position: "Tech Leader"
// hiredAt: ""
// <constructor>: "Employee"
总结
这种用于 JavaScript 中数据建模的简单结构应该可以节省您一些开发时间。 您可以根据需要随时添加新功能,以使您的代码更简洁且更易于维护。 所有这些代码都可以在我的 CodeSandbox 中找到,所以请尝试一下,并在下面的评论中告诉我它的效果如何。
1 条评论