新一代前端数据mock工具Data Faker

Data Faker

注意下面的仅仅是演示案例,具体详情请参见 DataFaker 国外官网 DataFaker 国内官网

起步

介绍

DataFaker 是一个数据生成器,底层依赖 faker.js,并在之上扩展了模板语法,能够帮助你快速生成各类的数据,包括引用数据和递归数据,满足你在不同场景下各类数据的生成需求。它特别适用于以下场景:

  • 前端开发中的模拟数据
  • 单元测试和集成测试
  • API 接口原型设计
  • 数据库样本数据生成
  • 演示和教学用例

致敬 faker.js

特性

  • 无侵入:DataFakerfaker.js只做增强不做修改,你仍然可以像以前那样使用 faker.js
  • 模板化:DataFaker以模板的方式来定义数据结构,就像定义数据库表结构那样
  • 面向模型:DataFaker将模板封装为了模型,以模型为基本单元,提供了模型复用机制,让你的数据模板可在多处复用
  • 上下文机制:DataFaker采用上下文机制保持数据之间的关联性
  • 多语言:DataFaker底层依托faker.js,其同样也支持 70 多种语言环境
  • 多数据源:DataFaker 借助了 faker.js 的底层数据库,能够生成涵盖动物、书本等 26 类数据
  • 可配置:DataFaker 支持个性化配置方式

基本使用

DataFaker使用起来十分的简单,你只需要:

  1. 定义数据模型
  2. 生成数据

定义模型-defineModel

defineModel方法用于定义数据模型,它接受两个参数:

  • 模型名称
  • 数据模板
ts 复制代码
// 定义模型
const userModel = defineModel('user', {
  id: 'string.uuid',
  name: 'person.fullName',
  age: ['number.int', { min: 18, max: 30 }],
});

生成数据-fakeData

使用fakeData函数并传入数据模型就能生成模型模板对象的数据,如下所示:

ts 复制代码
// 生成数据
const data = fakeData(userModel);
console.log(data);

生成的数据如下:

json 复制代码
{
  "id": "5bdfc8e5-3b33-4560-b4ca-8b32b0150661",
  "name": "Malcolm Simonis",
  "age": 18
}

核心概念

模板语法

DataFaker通过模板来定义数据结构,就像定义一个数据库表那样,每一个数据结构就是一个schema

ts 复制代码
const addressModel = defineModel('address', {
  country: 'location.country',
  city: 'location.city',
});
const userModel = defineModel('user', {
  id: 'number.int',
  firstName: 'person.firstName',
  secondeName: 'person.lastName',
  age: ['number.int', { min: 18, max: 65 }],
  hobby: ['helpers.arrayElements', ['篮球', '足球', '乒乓球', '羽毛球']],
  email: ctx => {
    return faker.internet.email({ firstName: ctx.firstName, lastName: ctx.secondeName });
  },
  address: addressModel,
  children: {
    // 引用自身,此时必须使用模型别名'user'而不能使用userModel
    refModel: 'user',
    // 控制自引用递归深度
    deep: 3,
  },
});
const userDatas = fakeData(userModel);
console.dir(userDatas, { depth: Infinity });

模型复用

使用cloneModel函数我们能够克隆一个模型,其需要提供两个参数

  • 参数 【1】:克隆的新的模型别名
  • 参数 【2】:要克隆的模型对象

比如我们以userModel模型为原型,克隆出了一个学生模型,并将其别名命名为studentModel

ts 复制代码
// 用户模型
const userModel = defineModel('user', {
  id: 'number.int',
  firstName: 'person.firstName',
  secondName: 'person.lastName',
  age: ['number.int', { min: 18, max: 65 }],
  email: ctx => {
    return faker.internet.email({ firstName: ctx.firstName, lastName: ctx.secondName });
  },
  children: {
    refModel: 'user',
    // 控制自引用递归深度
    deep: 1,
  },
});
// 克隆学生模型
const studentModel = cloneModel('student', userModel);
const studentDatas = fakeData(studentModel);
console.dir(studentDatas, { depth: Infinity });

生成数据如下:

json 复制代码
{
  "id": 6584695714108738,
  "firstName": "Jane",
  "secondName": "Wisoky",
  "age": 21,
  "children": {
    "id": 390307445727788,
    "firstName": "Addie",
    "secondName": "Koch",
    "age": 62,
    "children": {
      "id": 6872368248204444,
      "firstName": "Alexandra",
      "secondName": "Powlowski",
      "age": 29,
      "children": null,
      "email": "Alexandra.Powlowski16@hotmail.com"
    },
    "email": "Addie_Koch@hotmail.com"
  },
  "email": "Jane_Wisoky27@gmail.com"
}

钩子函数

钩子函数就是在数据生成前后或数据生成中执行的函数,它能让你在数据生成前后或数据生成中操作数据项和模板,改变数据生成方式,DataFaker提供了四类钩子函数:

  • 数据生成前操作模板-beforeAllCbs
  • 数据生成后操作数据-afterAllCbs
  • 数据项生成前设置模板-beforeEachCbs
  • 数据项生成后操作数据-afterEachCbs

比如我希望为所有引用数据添加 id`属性

ts 复制代码
const addressModel = defineModel('address', {
  country: 'location.country',
  city: 'location.city',
  children: {
    refModel: 'address',
  },
});
// 用户模型
const userModel = defineModel('user', {
  firstName: 'person.firstName',
  secondName: 'person.lastName',
  age: ['number.int', { min: 18, max: 65 }],
  address: { refModel: 'address', count: 1 },
});
const userDatas = fakeData(userModel, {
  hooks: {
    afterEachCbs: ctx => {
      if (ctx.type === 'object' && ctx.value) {
        // 对所有引用类型添加id
        ctx.value['id'] = faker.string.uuid();
      }
      return ctx;
    },
  },
});
console.dir(userDatas, { depth: Infinity });

生成数据如下:

ts 复制代码
{
  firstName: 'Ernest',
  secondName: 'Ritchie',
  age: 42,
  address: {
    country: 'Sint Maarten',
    city: 'Joeborough',
    children: {
      country: 'Sudan',
      city: 'Watsicashire',
      children: null,
      id: '6b9dd2aa-26a2-4072-95af-6c63eddd6dc0'
    },
    id: '945e2165-2119-45ee-bd52-b0c0df8a73b1'
  }
}

引用数据和自引用

DataFaker支持引用数据和生成自引用递归数据

ts 复制代码
const addressModel = defineModel('address', {
  country: 'location.country',
  city: 'location.city',
  children: {
    refModel: 'address',
    count: 2,
    deep: 1,
  },
});
// 用户模型
const userModel = defineModel('user', {
  firstName: 'person.firstName',
  secondName: 'person.lastName',
  age: ['number.int', { min: 18, max: 65 }],
  address: { refModel: 'address', count: 2 },
  children: {
    refModel: 'user',
    deep: 2,
  },
});
const userDatas = fakeData(userModel);
console.dir(userDatas, { depth: Infinity });

生成数据如下:

json 复制代码
{
  "firstName": "Lydia",
  "secondName": "Adams",
  "age": 41,
  "address": [
    // 第一个address数据
    {
      "country": "Democratic Republic of the Congo",
      "city": "Elisefurt",
      // 递归1层
      "children": [
        {
          "country": "Nauru",
          "city": "Randalltown"
        },
        {
          "country": "Afghanistan",
          "city": "Commerce City"
        }
      ]
    },
    // 第二个address数据
    {
      "country": "Ecuador",
      "city": "Hamillworth",
      "children": [
        {
          "country": "Reunion",
          "city": "West Greysonland"
        },
        {
          "country": "Italy",
          "city": "Hempstead"
        }
      ]
    }
  ],
  // 第一层children
  "children": {
    "firstName": "Lisette",
    "secondName": "Gutmann",
    "age": 29,
    "address": [
      {
        "country": "Syrian Arab Republic",
        "city": "Leuschkefield",
        "children": [
          {
            "country": "Austria",
            "city": "Hammesstad"
          },
          {
            "country": "Barbados",
            "city": "West Elodyfort"
          }
        ]
      },
      {
        "country": "Colombia",
        "city": "Fort Anastasia",
        "children": [
          {
            "country": "Czechia",
            "city": "Hyattfort"
          },
          {
            "country": "Zambia",
            "city": "West Stefanieborough"
          }
        ]
      }
    ],
    // 第二层children
    "children": {
      "firstName": "Rex",
      "secondName": "Farrell",
      "age": 22,
      "address": [
        {
          "country": "El Salvador",
          "city": "Montyshire",
          "children": [
            {
              "country": "Bangladesh",
              "city": "Port Prince"
            },
            {
              "country": "Svalbard & Jan Mayen Islands",
              "city": "South Siennacester"
            }
          ]
        },
        {
          "country": "Panama",
          "city": "Monterey Park",
          "children": [
            {
              "country": "Vietnam",
              "city": "South Scotworth"
            },
            {
              "country": "Mozambique",
              "city": "Matildeside"
            }
          ]
        }
      ]
    }
  }
}

装饰器语法

基本使用

DataFaker为了更好的支持ts,引入了装饰器语法,装饰器语法本质上就是defineModel的语法糖,它设计的初衷就是为了保持现有类和模型的共通性。 比如现在项目中本来就有UserAddress两个类作为 ts 类型

ts 复制代码
class Address {
  declare city: string;
  declare children: Address[];
}
// 用户类
class User {
  declare id: string;
  declare firstName: string;
  declare secondName: string;
  declare age: number;
  declare email: string;
  declare address: Address;
  declare children: User[];
}

为了将这两个类利用起来,而不是重新使用defineModel来定义数据模型,我们可以使用装饰器语法来将现有的类型类定义为UserAddress数据模型。

  • 使用@DataModel装饰器定义数据模型,它接受一个模型别名作为参数
  • 使用@DataField装饰器来定义字段,与模板语法中定义字段是一致的

如下所示:

ts 复制代码
@DataModel('address')
class Address {
  @DataField('location.city')
  declare city: string;
  @DataField({ refModel: 'address', count: 1, deep: 1 })
  declare children: Address[];
}
@DataModel('user')
class User {
  @DataField('string.uuid')
  declare id: string;
  @DataField('person.firstName')
  declare firstName: string;
  @DataField('person.lastName')
  declare secondName: string;
  @DataField(['number.int', { min: 18, max: 65 }])
  declare age: number;
  @DataField(ctx => {
    return faker.internet.email({ firstName: ctx.firstName, lastName: ctx.secondName });
  })
  declare email: string;
  @DataField({ refModel: 'address', count: 1 })
  declare address: Address;
  @DataField({ refModel: 'user', deep: 1, count: 1 })
  declare children: User[];
}
const userDatas = fakeData('user', 2);
console.dir(userDatas, { depth: Infinity });

数据生成结果如下:

ts 复制代码
[
  {
    id: 'b8e8ade6-5f37-43d9-b512-4ba0395e5975',
    firstName: 'Cecile',
    secondName: 'MacGyver',
    age: 24,
    address: { city: 'Leviland', children: { city: 'North Georgianna' } },
    children: {
      id: 'f29ea63b-ac69-4832-9586-b82b17f2d40b',
      firstName: 'Floyd',
      secondName: 'Flatley',
      age: 57,
      address: { city: 'Lake Anissa', children: { city: 'North Beverlyshire' } },
      email: 'Floyd.Flatley@hotmail.com',
    },
    email: 'Cecile_MacGyver34@yahoo.com',
  },
  {
    id: '3647b033-470d-40f3-adf9-836df66f7eef',
    firstName: 'Evangeline',
    secondName: 'Kerluke',
    age: 23,
    address: { city: 'Raynorland', children: { city: 'West Rosetta' } },
    children: {
      id: '350c4642-761f-4b36-a6cf-5b1bcf35edcb',
      firstName: 'Aurelio',
      secondName: 'Kuvalis',
      age: 64,
      address: { city: 'Florence-Graham', children: { city: 'New Brock' } },
      email: 'Aurelio_Kuvalis61@yahoo.com',
    },
    email: 'Evangeline.Kerluke@yahoo.com',
  },
];

基于原生基础的数据模型继承

装饰器语法可以更加方便的实现模型继承,只需要像原生继承那样,无需做任何改动,如下所示,User 类从 Person 类的数据模型中继承了 emailchildren 字段:

ts 复制代码
@DataModel('person')
class Person {
  @DataField(ctx => {
    return faker.internet.email({ firstName: ctx.firstName, lastName: ctx.secondName });
  })
  declare email: string;
  @DataField({ refModel: 'user', deep: 1, count: 1 })
  declare children: User[];
}
@DataModel('user')
class User extends Person {
  @DataField('string.uuid')
  declare id: string;
  @DataField('person.firstName')
  declare firstName: string;
  @DataField('person.lastName')
  declare secondName: string;
  @DataField(['number.int', { min: 18, max: 65 }])
  declare age: number;
}
const userDatas = fakeData('user', 2);
console.dir(userDatas, { depth: Infinity });

生成数据如下:

ts 复制代码
[
  {
    children: {
      id: '01beb5dd-d2f8-4602-a4f6-4304d49b1532',
      firstName: 'Anjali',
      secondName: 'Murphy',
      age: 51,
      email: 'Anjali.Murphy@hotmail.com',
    },
    id: '041980c6-164a-4fad-81a2-65a3f9c64359',
    firstName: 'Kristy',
    secondName: 'Ledner',
    age: 62,
    email: 'Kristy_Ledner30@yahoo.com',
  },
  {
    children: {
      id: '2df47ecb-186e-4d9b-a417-4b62dd4906d0',
      firstName: 'Jody',
      secondName: 'Schmeler',
      age: 18,
      email: 'Jody_Schmeler@hotmail.com',
    },
    id: '26450cf7-f190-44dc-ab1b-6ff0faf8e74b',
    firstName: 'Nathanial',
    secondName: 'Schaden',
    age: 19,
    email: 'Nathanial.Schaden96@gmail.com',
  },
];

多种配置方式

全局配置

下面演示全局定义数据生成钩子函数

ts 复制代码
// 全局定义beforeAllCbs回调函数
DataFaker.setHooks({
  beforeAllCbs: schema => {
    console.log(schema);
    return schema;
  },
});

模板中配置

下面演示模板中定义引用类型生成数量

ts 复制代码
const userModel = defineModel('user', {
  firstName: 'person.firstName',
  secondName: 'person.lastName',
  age: ['number.int', { min: 18, max: 65 }],
  address: { refModel: 'address', count: 2 },
});

运行时配置

下面展示运行时配置引用数据生成方式

ts 复制代码
const userDatas = fakeData(userModel, {
  refRules: {
    // address引用数据生成一个,然后其address.childern自引用数据生成一个
    address: {
      [COUNT]: 1,
      children: {
        [COUNT]: 1,
      },
    },
    // 自引用递归深度为1,且只生成一个,address引用属性同上
    children: {
      [DEEP]: 1,
      [COUNT]: 1,
    },
  },
});

数据迭代器

数据迭代器旨在帮助你生成数据时能够按序的使用集合中的数据,简化你代码编写,比如有如下的场景:

需要生成 3 个用户数据,每个用户数据兴趣爱好都不同

如果通过传统faker.js来进行书写将是这样的代码:

javascript{2,6} 复制代码
let hobbyArr = ['篮球', '足球', '乒乓球'];
let index = 0;
const userModel = defineModel('user', {
  id: 'string.uuid',
  hobby: () => {
    return hobbyArr[index++];
  },
});
console.log(fakeData(userModel, 4));

生成的数据如下:

json 复制代码
[
  { "id": "d16e7a49-5e7a-40a0-97e7-68693ffa7268", "hobby": "篮球" },
  { "id": "268a6a63-5eee-4668-a166-d1b9f8bcf510", "hobby": "足球" },
  { "id": "2ed907c6-0cdf-40bd-95cf-6aaf3ebe5d1c", "hobby": "乒乓球" },
  { "id": "7d9b0df7-7fd3-401d-a7a9-59759a0948b4", "hobby": undefined }
]

可以看到你需要手动维护index变量,这并不方便而且容易与其它index变量进行混淆,所以DataFaker考虑到了这种情况,你只需要获取一个迭代器,就可以按序的使用集合中的数据。如下所示:

javascript 复制代码
let hobbyArr = ['篮球', '足球', '乒乓球'];
const iterator = IteratorFactory.getIterator(hobbyArr);
const userModel = defineModel('user', {
  id: 'string.uuid',
  hobby: () => {
    return iterator.next().value;
  },
});
console.log(fakeData(userModel, 4));

DataFaker提供了四种数据迭代器,这些迭代器可以从迭代器工厂IteratorFactory中获取,分别是:

  • 正向迭代器 :IteratorFactory.getIterator()
  • 逆向迭代器:IteratorFactory.getReverseIterator()
  • 循环正向迭代器:IteratorFactory.getLoopIterator()
  • 循环逆向迭代器:IteratorFactory.getLoopReverseIterator()
相关推荐
UIUV3 小时前
微信小程序开发学习笔记:从架构到实战
前端·javascript·前端框架
程序猿_极客3 小时前
JavaScript的Web APIs 入门到实战(day2):事件监听与交互实现,轻松实现网页交互效果(附练习巩固)
开发语言·前端·javascript·学习笔记·web apis 入门到实战
Mintopia3 小时前
🚀 一文看懂 “Next.js 全栈 + 微服务 + GraphQL” 的整体样貌
前端·javascript·全栈
Mintopia3 小时前
🧬 医疗Web场景下,AIGC的辅助诊断技术边界与伦理
前端·javascript·aigc
半桶水专家3 小时前
父子组件通信详解
开发语言·前端·javascript
Watermelo6173 小时前
从vw/h到clamp(),前端响应式设计的痛点与进化
前端·javascript·css·算法·css3·用户界面·用户体验
寻星探路3 小时前
测试开发话题10---自动化测试常用函数(2)
java·前端·python
Moment4 小时前
快到  2026  年了:为什么我们还在争论  CSS 和 Tailwind?
前端·javascript·css
梵得儿SHI4 小时前
Vue 核心语法详解:模板语法中的绑定表达式与过滤器(附 Vue3 替代方案)
前端·javascript·vue.js·插值语法·vue模板语法·绑定表达式·过滤器机制