JianghuJS-复杂逻辑开发
120021、业务组件封装
业务组件封装是前端开发中的一种最佳实践,它指的是将特定业务功能或模块的代码、样式和逻辑封装成独立的组件,以便在整个项目中复用和维护。业务组件封装的主要目的是提高代码的可维护性、可复用性,降低耦合度,促进团队协作和加速开发过程。
为什么要封装业务组件
为了实现页面的复杂功能和交互。将一个页面拆分成多个组件,每个组件负责自己的部分,可以使代码更加模块化、易于维护和扩展。
例如,在一个电商网站中,一个产品页面可能包含产品图片、产品名称、产品描述、价格、评价等多个部分,每个部分都可以使用一个组件来实现。这样,每个组件负责自己的功能和状态,组件之间相互独立,可以单独进行开发、测试和维护,也可以在不同的页面中重复使用。此外,组件之间可以通过组件间通信(props、events)来实现交互和数据共享,使页面的功能更加丰富和灵活。
因此,使用多个组件有以下优点:
可维护性:
业务组件封装使得相关功能的代码集中到一个组件中,便于维护和修改。如果业务逻辑发生变化或者需要优化,只需修改组件内部的代码而不影响整个应用。可复用性:
通过封装业务组件,可以轻松地在项目的不同部分或者不同项目中复用相同的功能。这降低了代码冗余,提高了开发效率。降低耦合度:
封装业务组件可以将业务逻辑与其他组件解耦,减少组件之间的依赖性。这使得修改一个组件不会影响到其他组件,提高了代码的灵活性。提高团队协作:
通过业务组件的封装,团队成员可以更容易地理解和使用彼此编写的组件。这有助于提高团队协作效率,特别是在大型项目中。加速开发过程:
封装业务组件可以提高开发效率,因为在新功能的开发中,开发者可以直接使用已经封装好的组件,而不是从头开始编写相似的代码。
如何封装业务组件
抽象业务逻辑:
将业务组件内的业务逻辑抽象成可复用的函数或方法。确保组件关注单一职责,不涉及过多的业务逻辑。提取可配置项:
将组件中的可配置项(例如样式、参数、回调函数)提取为组件的属性或配置项,以便在使用组件时进行定制。良好的接口设计:
设计清晰、简单、易于使用的接口,确保组件的使用方式直观,减少使用者的学习成本。考虑状态管理:
如果组件内部需要维护一些状态,考虑使用状态管理工具(如Redux、Vuex等)来管理状态,以确保状态的一致性和可追踪性。文档和示例:
为业务组件提供详细的文档和示例,包括如何使用、可配置项、事件触发等信息,以便其他开发者能够轻松地使用该组件。测试:
编写单元测试以确保组件的功能和逻辑的正确性。测试是确保组件稳定性的重要手段。版本管理:
使用版本控制工具(如Git)对业务组件进行版本管理,确保在不同项目或团队中的使用一致性。
示例代码(Vue.js 组件):
<template>
<div>
<button @click="handleClick">点击我</button>
</div>
</template>
<script>
export default {
methods: {
handleClick() {
// 处理点击事件的业务逻辑
console.log('按钮被点击了!');
}
}
}
</script>
<style scoped>
/* 组件样式 */
</style>
上述示例是一个简单的Vue.js组件,通过封装按钮点击的业务逻辑,实现了组件的复用。在实际应用中,可以根据业务的复杂性进行更灵活和丰富的封装。
2、父子组件传参
不同的传参方式
在 Vue.js 中,父组件向子组件传递数据可以使用不同的方式,包括以下几种:
Props
Props(属性)传递: 是最常见的一种方式,当父组件需要将数据传递给子组件时,可以使用属性传递。父组件通过在子组件上声明 props,使用v-bind或简写:
,将数据以属性的形式传递给子组件。子组件接收 props 数据并在内部使用。$emit
$emit 事件传递: 当子组件需要将数据传递给父组件时,可以使用事件传递。子组件可以通过$emit
触发自定义事件并携带数据来通知父组件。父组件通过在子组件上使用@eventName
监听该事件,并在相应的事件处理程序中获取传递的数据。$refs
$refs 属性: 当父组件需要主动调用子组件的方法或访问子组件的属性时,可以使用$refs
。在子组件上使用 ref 属性为其命名,然后在父组件中通过this.$refs.childRef
来访问子组件实例。可以直接调用子组件的方法或访问其属性,而无需通过props或事件进行通信。
用例 & 逻辑说明
下面是一个简单的 jianghusjs
父子组件传递参数的示例:
<!-- 父page页面 -->
{% extends 'template/jhTemplateV4.html'%}
{% block vueTemplate %}
<script type="text/html" id="app-template">
<div>
<h1>{{ title }}</h1>
<child-component :message="message" @update="updateMessage"></child-component>
</div>
</script>
<div id="app"></div>
{% endblock %}
{% block vueScript %}
<script>
{% include 'component/childComponent.html' %}
new Vue({
el: '#app',
template: '#app-template',
vuetify: new Vuetify(),
data() {
return {
title: 'Parent Component',
message: 'Hello, Child Component!'
}
},
methods: {
updateMessage(newMessage) {
this.message = newMessage;
}
}
});
</script>
{% endblock %}
<!-- 子组件 component/childComponent.html -->
<template id="child-component">
<div>
<p>{{ message }}</p>
<button @click="changeMessage">Change Message</button>
</div>
</template>
<script>
Vue.component("ChildComponent", {
template: "#child-component",
vuetify: new Vuetify(),
props: {
message: String
},
methods: {
changeMessage() {
this.$emit('update', 'New Message');
}
}
});
</script>
在这个例子中,父组件包含一个标题和一个子组件<child-component>
,
属性传递给子组件:
通过:message="message"
将父组件中的数据message
作为属性传递给子组件。子组件接收属性:
子组件中通过props
选项声明接收这个属性。子组件变更事件:
子组件中的<p>
标签显示message
属性的值,<button>
标签点击后会触发changeMessage()
方法,通过this.$emit('update', 'New Message')
语句向父组件发送update
事件,并传递一个新的消息'New Message'
。父组件中的@update="updateMessage"
监听到update
事件,并执行updateMessage(newMessage)
方法,将新消息保存到message
**属性中。
通过props选项实现父子组件之间的数据传递,可以使组件之间解耦、独立、易于维护和扩展。同时,通过事件机制实现组件之间的交互,可以使组件之间更加灵活和动态。
3、jianghuKnex
jianghuKnex 与 knex 的关系
knex 是一个 Node.js 的 SQL 查询构建器,可简化开发者与 PostgreSQL、MySQL、SQLite 和 MSSQL 等常见数据库交互。
jianghuKnex是在 Knex 的基础上进行的封装,添加了数据历史特性。包括在数据库操作中保留历史数据、追踪数据变更历史等功能。
数据历史记录:
包括记录每次数据变更的历史,包括插入、更新和删除操作。这对于跟踪数据变更并进行审计非常有用。版本化数据:
在 Knex 查询的基础上,提供了一些额外的语法或方法,以便查询特定版本的数据,或者查询某个时间点的数据状态。时间戳:
为每个表格自动添加时间戳,记录每次数据的创建和修改时间,用于更方便地追踪数据的时间线。数据快照:
提供生成数据的快照或历史版本,以便在需要时进行比较或还原。
- jianghuKnex 常用方法
在你的代码中使用jianghuKnex时,你可以按照以下的基本格式:
// 选择全部数据记录
await this.app.jianghuKnex('${表的名字}').select();
// 增加一条数据记录
await this.app.jianghuKnex('${表的名字}').jhInsert({id: 1, data: 'abc'});
// 更新id为1的数据记录
await this.app.jianghuKnex('${表的名字}').where({id: 1}).jhUpdate({data: '123'});
接下来,我们以一个学生管理系统为例,示范各种SQL数据操作的写法:
- 示例数据表名:'student_basic'
- 数据表部分数据字段:
- id:记录序号
- studentId:学生编号
- name:学生姓名
- gender:性别
- dateOfBirth:出生日期
- classId:班级编号
- level:年级
读取数据
- 读全部数据
await this.app.jianghuKnex('student_basic').select();
- 读取第一条数据
await this.app.jianghuKnex('student_basic').first();
- 仅读取某些字段 【需要试验】
// 读取第一条数据
await this.app.jianghuKnex('student_basic').column('studentId', 'name', 'classId').first();
// 读取全部数据
await this.app.jianghuKnex('student_basic').column('studentId', 'name', 'classId').select();
设置各种Where条件
在jianghuKnex中,where条件并非必要,根据查询的需要增加即可。
通用查询条件:where
const { jianghuKnex } = this.app
// 查询名叫“张三丰”、班级编号是“2021-01级-02班”的学生
await jianghuKnex('student_basic').where({ name: ‘张三丰’, classId: ‘2021-01级-02班’}).select();
// 模糊查询名为“张”开头的学生
await jianghuKnex('student_basic').where("name", "like", "张%").select();
和与或查询:andWhere、orWhere
const { jianghuKnex } = this.app
// 查询名叫“张三丰”并且身高大于180
await jianghuKnex("student_basic")
.where({"name": "张三丰"})
.andWhere("bodyHeight", ">", 180).select()
// 查询名叫“张三丰”或身高大于180
await jianghuKnex("student_basic")
.where({"name": "张三丰"})
.orWhere("bodyHeight", ">", 180).select()
在查询条件中规定多个值:whereIn
// 查找所有名字是“张三丰”或者“张无忌”的学生
await this.app.jianghuKnex('student_basic').whereIn("name", ["张三丰", "张无忌"]).select();
自定义sql查询(不建议使用,容易sql注入)
await this.app.jianghuKnex('student_basic').whereRaw('id = ? or bodyHeight > ?', [187, 180]).select();
设置Order By、Limit等条件
在jianghuKnex中,我们还可以对查询设置起始位置、条数以及排序规则。这些规则不是必须的,根据需要选填即可。
设置查询起始位置 offset
- 例如,从第100位开始查询:
await this.app.jianghuKnex('student_basic').offset(100).select();
设置查询条数 limit
- 例如,查询前10条数据:
await this.app.jianghuKnex('student_basic').limit(10).select();
设置排序规则
- 例如,按照年龄倒序排列:
await this.app.jianghuKnex('student_basic').orderBy('age', 'desc').select();
- 也可以设置多个排序规则。例如,按照班级正序排列,再按年龄倒序排列:
await this.app.jianghuKnex('student_basic').orderBy([
'classId', { column: 'age', order: 'desc' }
]).select();
插入数据 Insert
- 插入一条数据:
await this.app.jianghuKnex('student_basic', ctx).jhInsert({
studentId: 'G00100',
name: '小虾米',
gender: '男',
dateOfBirth:'2010-01-01',
classId:'2022-01级-01班'
level:1
});
- 插入多条数据,需要使用数组:
await this.app.jianghuKnex('student_basic', ctx).jhInsert([{
studentId: 'G00003',
name: '小虾米',
gender: 'male',
dateOfBirth:'2010-01-01',
classId:'2022-01级-02班'
level:1
}],[{
studentId: 'G00101',
name: '大虾米',
gender: 'male',
dateOfBirth:'2010-01-02',
classId:'2022-01级-02班'
level:1
}]);
修改数据 Update
- 修改学生编号为“G00101”的学生的姓名:
await this.app.jianghuKnex('student_basic',ctx).where({studentId: 'G00101'}).jhUpdate({
name: '张大虾'
});
可以通过设置where条件来修改多条数据。
删除数据 Delete
- 删除学生编号为“G00101”的数据:
await this.app.jianghuKnex('student_basic',ctx).where({studentId: 'G00101'}).jhDelete();
可以通过设置where条件来删除多条数据。
直接执行 sql 语句
jianghuKnex也支持直接执行sql语句。使用
raw
命令可以执行合法的sql语句。
注意!!我们极其不建议开发者拼接sql语句,这样很容易引起sql注入!!
- 用法示例:查询学生编号为“G00101”的数据:
const studentId = 'G00101';
await this.app.jianghuKnex.raw(`SELECT * FROM student_basic WHERE studentId = '${studentId}'`);
注意事项:使用jhInsert/Update/Delete要传入ctx
jianghuKnex在对数据进行插入、修改或删除时,可以将数据的历史记录,存入
_record_history
表中。但需要注意的是,你需要把环境变量ctx
作为一个参数传入jianghuKnex()
函数中,这样才能记录是哪个用户执行了这个操作。
- 在service中,一个完整的函数的写法是:
// app/service/demo.js
class DemoService extends Service {
async demoSelectFunction() {
const { ctx, app } = this;
const { jianghuKnex } = app;
const result = await jianghuKnex('sample-table').select();
return result;
}
async demoInsertFunction() {
const { ctx, app } = this;
const { jianghuKnex } = app;
const result = await jianghuKnex('sample-table', ctx).insert({id: 100, name: '张三'});
return result;
}
}
module.exports = DemoService;
- jianghuKnex 事务处理
MySQL事务是一组SQL语句的执行,全部成功或全部失败。在MySQL中,使用BEGIN、COMMIT和ROLLBACK三个关键字来实现事务。如果所有SQL语句都执行成功,使用COMMIT提交事务,否则使用ROLLBACK回滚事务。
await jianghuKnex.transaction(async trx => {
await trx('student').insert({ name: 'xxx1' });
await trx('student').insert({ name: 'xxx2' });
});
4、beforeHook
"beforeHook" 通常指的是在进行某个操作或执行某个函数之前执行的钩子(hook)。这种机制常见于软件开发中,尤其是在使用框架或库的情况下,以允许在某个操作之前执行一些自定义的逻辑。
为什么要有 beforeHook
预处理逻辑:
在执行某个操作之前,可能需要进行一些预处理逻辑,例如验证输入、准备数据、检查权限等。"beforeHook" 提供了一个机会在实际操作之前执行这些逻辑。代码组织:
使用 "beforeHook" 可以将相关的预处理逻辑集中到一个地方,使代码更加清晰和易于维护。这有助于提高代码的可读性和可维护性。可扩展性:
"beforeHook" 允许开发者轻松地添加、移除或修改预处理逻辑,从而增强了代码的可扩展性。这对于应对未来需求的变化非常重要。
beforeHook 用法
jianghujs中的 beforehook 是通过在 app/service 中调用指定函数来实现的。配置信息存储在 "_resource" 表的 "resourcehook" 字段中,其格式如下:
{
"after": [],
"before": [
{
"service": "servicename",
"serviceFunction": "function"
}
]
}
5、afterHook
为什么要有 afterHook
后处理逻辑:
在执行某个操作之后,可能需要进行一些后处理逻辑,例如记录日志、触发其他异步任务、更新缓存等。afterHook 提供了一个机会在实际操作之后执行这些逻辑。代码组织:
类似于 beforeHook,使用 afterHook 可以将后处理逻辑集中到一个地方,使代码更加清晰和易于维护。这有助于提高代码的可读性和可维护性。异常处理:
在操作成功完成后,可能需要处理一些额外的异常情况或边缘情况。afterHook 可以用于执行这样的异常处理逻辑。触发其他操作:
在某个操作完成后,可能需要触发其他相关的操作。afterHook 提供了一个适当的时机来执行这些操作。
afterHook 用法
jianghujs中的 afterHook 是通过在 app/service 中调用指定函数来实现的。配置信息存储在 "_resource" 表的 "resourcehook" 字段中,其格式如下:
{
"after": [
{
"service": "servicename",
"serviceFunction": "function"
}
],
"before": []
}