JianghuJS-Complex Logic Development
120021. Business Component Encapsulation
Business component encapsulation is a best practice in front-end development, referring to the process of encapsulating the code, styles, and logic of specific business functionalities or modules into independent components for reuse and maintenance throughout the project. The main goal of business component encapsulation is to improve code maintainability and reusability, reduce coupling, promote team collaboration, and accelerate the development process.
Why encapsulate business components
To achieve complex functionalities and interactions on a page. By breaking a page into multiple components, each responsible for its own part, the code can become more modular, easier to maintain, and extend.
For example, in an e-commerce website, a product page may contain multiple parts such as product images, product names, product descriptions, prices, and reviews, each of which can be implemented using a component. This way, each component is responsible for its own functionality and state, and the components are independent of each other, allowing for separate development, testing, and maintenance, as well as reuse across different pages. Additionally, components can interact and share data through inter-component communication (props, events), enriching and making the page's functionality more flexible.
Therefore, using multiple components has the following advantages:
Maintainability:
Business component encapsulation centralizes the code for related functionalities into a single component, making it easier to maintain and modify. If the business logic changes or needs optimization, only the internal code of the component needs to be modified without affecting the entire application.Reusability:
By encapsulating business components, the same functionality can be easily reused in different parts of the project or in different projects. This reduces code redundancy and improves development efficiency.Reduced Coupling:
Encapsulating business components decouples business logic from other components, reducing dependencies between components. This means that modifying one component does not affect others, enhancing code flexibility.Improved Team Collaboration:
Through the encapsulation of business components, team members can more easily understand and use components written by each other. This helps improve team collaboration efficiency, especially in large projects.Accelerated Development Process:
Encapsulating business components can enhance development efficiency, as developers can directly use already encapsulated components when developing new functionalities, rather than writing similar code from scratch.
How to encapsulate business components
Abstract business logic:
Abstract the business logic within the business component into reusable functions or methods. Ensure that the component focuses on a single responsibility and does not involve excessive business logic.Extract configurable options:
Extract configurable options (such as styles, parameters, callback functions) from the component into the component's properties or configuration options for customization when using the component.Good interface design:
Design clear, simple, and easy-to-use interfaces to ensure that the usage of the component is intuitive, reducing the learning curve for users.Consider state management:
If the component needs to maintain some state internally, consider using state management tools (like Redux, Vuex, etc.) to manage the state, ensuring consistency and traceability.Documentation and examples:
Provide detailed documentation and examples for the business component, including how to use it, configurable options, event triggers, etc., so that other developers can easily use the component.Testing:
Write unit tests to ensure the correctness of the component's functionality and logic. Testing is an important means to ensure the stability of the component.Version management:
Use version control tools (like Git) to manage the versioning of business components, ensuring consistency in usage across different projects or teams.
Example code (Vue.js component):
<template>
<div>
<button @click="handleClick">Click Me</button>
</div>
</template>
<script>
export default {
methods: {
handleClick() {
// Business logic for handling click events
console.log('Button clicked!');
}
}
}
</script>
<style scoped>
/* Component styles */
</style>The above example is a simple Vue.js component that encapsulates the business logic for a button click, enabling component reuse. In practical applications, more flexible and rich encapsulations can be made based on the complexity of the business.
2. Parent-Child Component Parameter Passing
Different ways to pass parameters
In Vue.js, there are several ways for a parent component to pass data to a child component, including the following:
Props
Props (properties) passing: This is the most common method. When a parent component needs to pass data to a child component, it can use props. The parent component declares props on the child component and uses v-bind or shorthand:, passing data as properties to the child component. The child component receives the props data and uses it internally.$emit
$emit event passing: When a child component needs to pass data to a parent component, it can use event passing. The child component can trigger a custom event using$emitand carry data to notify the parent component. The parent component listens for the event using@eventNameon the child component and retrieves the passed data in the corresponding event handler.$refs
$refs property: When a parent component needs to actively call methods of the child component or access properties of the child component, it can use$refs. By using the ref attribute on the child component to name it, the parent component can access the child component instance throughthis.$refs.childRef. This allows direct calls to the child component's methods or access to its properties without communicating through props or events.
Use Case & Logic Explanation
Here is a simple example of parameter passing between parent and child components in jianghusjs:
<!-- Parent 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 %}<!-- Child component 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>In this example, the parent component contains a title and a child component <child-component>.
Properties passed to the child component:
The datamessagefrom the parent component is passed to the child component as a property through:message="message".Child component receives properties:
The child component declares this property through thepropsoption.Child component change event:
The<p>tag in the child component displays the value of themessageproperty, and clicking the<button>tag triggers thechangeMessage()method, which sends anupdateevent to the parent component with a new message'New Message'usingthis.$emit('update', 'New Message'). The parent component listens for theupdateevent with@update="updateMessage"and executes theupdateMessage(newMessage)method to save the new message to themessageproperty.
By using the props option to achieve data passing between parent and child components, components can be decoupled, independent, and easier to maintain and extend. At the same time, the event mechanism allows for more flexible and dynamic interactions between components.
3. jianghuKnex
Relationship between jianghuKnex and knex
knex is a SQL query builder for Node.js that simplifies interaction between developers and common databases such as PostgreSQL, MySQL, SQLite, and MSSQL.
jianghuKnex is an encapsulation built on top of Knex, adding data history features. This includes retaining historical data during database operations, tracking data change history, and more.
Data history records:
This includes recording the history of each data change, including insert, update, and delete operations. This is very useful for tracking data changes and auditing.Versioned data:
Based on Knex queries, it provides additional syntax or methods to query specific versions of data or the state of data at a certain point in time.Timestamps:
Automatically adds timestamps to each table to record the creation and modification times of each data entry, making it easier to track the timeline of data.Data snapshots:
Provides the ability to generate snapshots or historical versions of data for comparison or restoration when needed.
Common methods of jianghuKnex
When using jianghuKnex in your code, you can follow the basic format below:
// Select all data records
await this.app.jianghuKnex('${tableName}').select();
// Insert a data record
await this.app.jianghuKnex('${tableName}').jhInsert({id: 1, data: 'abc'});
// Update the data record with id 1
await this.app.jianghuKnex('${tableName}').where({id: 1}).jhUpdate({data: '123'}); Next, let's take a student management system as an example to demonstrate various SQL data operation syntax:
- Example data table name: 'student_basic'
- Some data fields in the table:
- id: Record number
- studentId: Student number
- name: Student name
- gender: Gender
- dateOfBirth: Date of birth
- classId: Class number
- level: GradeReading data
- Read all data
await this.app.jianghuKnex('student_basic').select();- Read the first data record
await this.app.jianghuKnex('student_basic').first();- Read only certain fields 【needs testing】
// Read the first data record
await this.app.jianghuKnex('student_basic').column('studentId', 'name', 'classId').first();
// Read all data
await this.app.jianghuKnex('student_basic').column('studentId', 'name', 'classId').select();Setting various Where conditions
In jianghuKnex, the where condition is not mandatory and can be added as needed based on the query.
General query condition: where
const { jianghuKnex } = this.app
// Query students named "Zhang Sanfeng" with class number "2021-01 Class-02"
await jianghuKnex('student_basic').where({ name: 'Zhang Sanfeng', classId: '2021-01 Class-02' }).select();
// Fuzzy query for students whose names start with "Zhang"
await jianghuKnex('student_basic').where("name", "like", "Zhang%").select();And/Or queries: andWhere, orWhere
const { jianghuKnex } = this.app
// Query students named "Zhang Sanfeng" and whose height is greater than 180
await jianghuKnex("student_basic")
.where({"name": "Zhang Sanfeng"})
.andWhere("bodyHeight", ">", 180).select()
// Query students named "Zhang Sanfeng" or whose height is greater than 180
await jianghuKnex("student_basic")
.where({"name": "Zhang Sanfeng"})
.orWhere("bodyHeight", ">", 180).select()Specify multiple values in query conditions: whereIn
// Find all students named "Zhang Sanfeng" or "Zhang Wuji"
await this.app.jianghuKnex('student_basic').whereIn("name", ["Zhang Sanfeng", "Zhang Wuji"]).select();Custom SQL queries (not recommended, prone to SQL injection)
await this.app.jianghuKnex('student_basic').whereRaw('id = ? or bodyHeight > ?', [187, 180]).select();Setting Order By, Limit, etc. conditions
In jianghuKnex, we can also set the starting position, number of records, and sorting rules for the query. These rules are not mandatory and can be filled in as needed.
Set query starting position offset
- For example, to query starting from the 100th position:
await this.app.jianghuKnex('student_basic').offset(100).select();Set query limit
- For example, to query the first 10 records:
await this.app.jianghuKnex('student_basic').limit(10).select();Set sorting rules
- For example, to sort by age in descending order:
await this.app.jianghuKnex('student_basic').orderBy('age', 'desc').select();- You can also set multiple sorting rules. For example, to sort by class in ascending order and then by age in descending order:
await this.app.jianghuKnex('student_basic').orderBy([
'classId', { column: 'age', order: 'desc' }
]).select();Insert data
- Insert a data record:
await this.app.jianghuKnex('student_basic', ctx).jhInsert({
studentId: 'G00100',
name: 'Little Shrimp',
gender: 'Male',
dateOfBirth: '2010-01-01',
classId: '2022-01 Class-01',
level: 1
});- To insert multiple records, use an array:
await this.app.jianghuKnex('student_basic', ctx).jhInsert([{
studentId: 'G00003',
name: 'Little Shrimp',
gender: 'Male',
dateOfBirth: '2010-01-01',
classId: '2022-01 Class-02',
level: 1
}, {
studentId: 'G00101',
name: 'Big Shrimp',
gender: 'Male',
dateOfBirth: '2010-01-02',
classId: '2022-01 Class-02',
level: 1
}]);Update data
- Update the name of the student with student number "G00101":
await this.app.jianghuKnex('student_basic', ctx).where({studentId: 'G00101'}).jhUpdate({
name: 'Zhang Big Shrimp'
});You can set where conditions to update multiple records.
Delete data
- Delete the record of the student with student number "G00101":
await this.app.jianghuKnex('student_basic', ctx).where({studentId: 'G00101'}).jhDelete();You can set where conditions to delete multiple records.
Directly execute SQL statements
jianghuKnex also supports executing SQL statements directly. The
rawcommand can be used to execute valid SQL statements.
Note!! We strongly advise developers against concatenating SQL statements, as this can easily lead to SQL injection!!
- Usage example: Query data for the student with student number "G00101":
const studentId = 'G00101';
await this.app.jianghuKnex.raw(`SELECT * FROM student_basic WHERE studentId = '${studentId}'`);Important Notes: Pass ctx when using jhInsert/Update/Delete
When jianghuKnex performs insertions, modifications, or deletions, it can store the historical records of the data in the
_record_historytable. However, it is important to note that you need to pass the environment variablectxas a parameter to thejianghuKnex()function to record which user performed the operation.
- In a service, a complete function would be written as follows:
// 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: 'Zhang San'});
return result;
}
}
module.exports = DemoService;- jianghuKnex Transaction Handling
MySQL transactions are a set of SQL statements that either all succeed or all fail. In MySQL, transactions are implemented using the keywords BEGIN, COMMIT, and ROLLBACK. If all SQL statements execute successfully, COMMIT is used to submit the transaction; otherwise, ROLLBACK is used to roll back the transaction.
await jianghuKnex.transaction(async trx => {
await trx('student').insert({ name: 'xxx1' });
await trx('student').insert({ name: 'xxx2' });
});4. beforeHook
"beforeHook" typically refers to a hook that is executed before performing a certain operation or executing a certain function. This mechanism is common in software development, especially when using frameworks or libraries, to allow for the execution of custom logic before a certain operation.
Why have a beforeHook
Preprocessing logic:
Before executing a certain operation, it may be necessary to perform some preprocessing logic, such as validating input, preparing data, checking permissions, etc. "beforeHook" provides an opportunity to execute this logic before the actual operation.Code organization:
Using "beforeHook" can centralize related preprocessing logic in one place, making the code clearer and easier to maintain. This helps improve code readability and maintainability.Extensibility:
"beforeHook" allows developers to easily add, remove, or modify preprocessing logic, enhancing the code's extensibility. This is very important for adapting to future changes in requirements.
Usage of beforeHook
In jianghujs, beforeHook is implemented by calling a specified function in app/service. Configuration information is stored in the "resourcehook" field of the "_resource" table in the following format:
{
"after": [],
"before": [
{
"service": "servicename",
"serviceFunction": "function"
}
]
}5. afterHook
Why have an afterHook
Post-processing logic:
After executing a certain operation, it may be necessary to perform some post-processing logic, such as logging, triggering other asynchronous tasks, updating caches, etc. afterHook provides an opportunity to execute this logic after the actual operation.Code organization:
Similar to beforeHook, using afterHook can centralize post-processing logic in one place, making the code clearer and easier to maintain. This helps improve code readability and maintainability.Exception handling:
After an operation is successfully completed, it may be necessary to handle some additional exceptional situations or edge cases. afterHook can be used to execute such exception handling logic.Triggering other operations:
After completing a certain operation, it may be necessary to trigger other related operations. afterHook provides an appropriate time to execute these operations.
Usage of afterHook
In jianghujs, afterHook is implemented by calling a specified function in app/service. Configuration information is stored in the "resourcehook" field of the "_resource" table in the following format:
{
"after": [
{
"service": "servicename",
"serviceFunction": "function"
}
],
"before": []
}