Use code to talk about the differences between our current mainstream front-end programming

original
03/03 15:04
Reading number 182

Write in front

Maybe different from most front-end developers, we use Vue3 and TypeScript But we may be back antique In the programming mode of Object oriented (OOP) , or ran a lap, compared with the current JavaScript Python PHP Go C# Wait, still love Java We do not deny that the current mainstream is Functional Programming (FP) More and more developers like to use it abstractly on the front end object-oriented programming This article only expresses our own preferences and does not impose any opinions.

>Many people ask why we do this on Vue. Here are some reasons: Cost of recruitment Current situation of Chongqing, an N-line Internet city Corporate decisions wait.

How do we use object-oriented

We also introduced a large number of object-oriented shadows in the front end, including some Data interaction entity Encapsulation of similar APIs Etc.)

>What Service/Entity looks like when Java writes the backend?

  • Encapsulation of data entities

As the main data object of front end and back end interaction, data entity bears the important steps of data interaction between front end and back end, data interaction between components, etc.

Like back-end programming, we will first constrain the data structure according to the specified specifications. Here we do not use some of the currently popular ways to encapsulate: interface , type Wait, but use class To encapsulate. Here we only talk about the purpose of such encapsulation:

  • Fixed field specification data entity

All database interaction entities will contain fields such as ID, so let's first define BaseEntity To perform data constraints:

 class BaseEntity { id!: number createTime!: number } class UserEntity extends BaseEntity { //No need to write public fields declared in the base class name!: string }

In the above way, we can write fewer public fields, and when data needs to be transferred in some public components, we can limit the type to BaseEntity Class, so that the public field of the incoming data can be retrieved from the component, for example, it can be retrieved directly ID , you can also automatically format and display the creation time in a friendly way.

>Of course, interface can also be used here to define. The same implementation of inheritance avoids writing the same field. Why do we still use class? Please continue to read

  • Data entity with unfixed specification

It is inevitable that some back-end developers do not like to use the same public fields, such as the following data interaction methods:

 { "user_id": 123, "user_name": "admin" }
 { "role_id": 122, "Role_name": "Administrator" }

The above habitual data interaction mode is not suitable for use interface Inherited, but we still use it with decorators class To achieve this requirement:

We made a statement BaseEntity and UserEntity , but we have added some decorators to configure some information about data conversion:)

 class BaseEntity { id!: number createTime!: number } //Indicates that all user fields need to start with user_ @Prefix("user_") class UserEntity extends BaseEntity { name!: string idcard!: string } //Indicates that all role fields need to start with role_ @Prefix("role_") class UserEntity extends BaseEntity { name!: string }

In this way, we have completed some configurations, but it may not be enough, such as some fields There is really no prefix , or we don't want to use non-standard back-end naming at all, for example, the back-end has shortened the phone pnum When the phone number?

 @Prefix("user_") class UserEntity extends BaseEntity { name!: string //The ID number field does not need a prefix, which is idcard @IgnorePrefix() idcard!: string @IgnorePrefix()  @Alias ("pnum")//Use alias to replace the attribute name of the back end phone!: string }

OK, so we are happy to complete some configuration about the field name, but we still need some methods to deal with it. So we declare a BaseModel As a superclass, let BaseEntity Inherit it, so that all entities have these transformation methods. If you are a normal data model without ID, you can also directly inherit BaseModel

 //Read some configurations of the decorator and provide some conversion methods class BaseModel { //See the open source project code provided at the end of the article for specific conversion methods toJson() { //Method for converting the current object into a normal JSON object } fromJson(json: Record<string, any>) { //Convert the JSON provided by the back end to the class object we need } } class BaseEntity extends BaseModel { //No more repetition }

Next, we can complete some data conversion, and then we can easily deal with the following issues regardless of the field names on the back end:

 Const json={}//JSON retrieved from the back end Const user=new UserEntity(). fromJson (json)//Of course, you can also directly provide some static methods: //For example, const user=UserEntity.fromJson (json), const userList=UserEntity.fromJsonArray (jsonArray) Console. log (user. id)//Get the ID declared by ourselves directly instead of the user_id following the backend

I don't care how you change my field name. Even if the backend interface user_id Changed to userid I don't need to search and change one by one in my code: I just need to UserEntity The configured decorator is changed to @Prefix("user") , if the other party needs to change to userId , I can also write another decorator, @Hump() , and then BaseModel Judge whether to mark the hump decorator during the conversion to select whether to automatically hump the field name.

:) Is it interesting?

  • More abnormal data conversion requirements

As mentioned above, we can automatically process some field names, and we can also process some field attribute types:

  • Conversion of Boolean, Number and String
  • If there is no value, you need to give a default value
  • If it is an array or other attached objects, such as users with roles
  • It is an enumeration value, and enumeration dictionary is required
  • Wait, wait, wait

>Please refer to our article on data conversion: Based on decorators - This is how I handle data conversion of TypeScript projects

  • Encapsulation of similar APIs

In daily development, we usually encounter interfaces with the same structure and request method, the same interface naming method, the same parameters and return values:)

Generally speaking, the request address of the interface may be different. We can declare an abstract class and require the subclass to pass in the address itself:

So we try to use a AbstractBaseService Class to perform some processing based on object-oriented inheritance:)

 abstract class AbstractBaseService { abstract apiUrl: string add() { request(this.apiUrl + "/add") } delete() { }//Delete //Wait, wait, wait }

Then our other subclasses can directly inherit the service and implement the following apiUrl This attribute (Java: direct abstract attribute)

 class UserService extends AbstractBaseService { apiUrl = "user" }

UserService Is it great to have the add, delete, modify and query methods in all parent classes? Of course, the generic type is added here to constrain the data type:

 abstract class AbstractBaseService<e extends baseentity> { abstract apiUrl: string add(entity: E) { request(this.apiUrl + "/add", entity.toJson()) } delete(entity: E) { request(this.apiUrl + "/delete", entity.toJson()) } } //Subclass passes in the corresponding generic constraint class UserService extends AbstractBaseService<userentity> { apiUrl = "user" }

Then, the encapsulation here not only realizes the reuse of parent class methods, but also the connection port request blocks the type:

 const user = new UserEntity() user.id = 1 New UserService(). add (user)//No error is reported normally const role = new RoleEntity() role.id = 1 New UserService(). add (role)//The rolling sub type does not match

This completes the encapsulation of the public part, and also adds some type constraints. If these general operations and dynamically bound data are unified into a hook, wouldn't it be nice? Like this:)

 //ClassConstructor is the wrapper class we encapsulate export function useAdd<e extends baseentity>(ServiceClass: ClassConstructor<e>, EntityClass: ClassConstructor<e>) { const formData = ref(new EntityClass()) const service = ref(new ServiceClass()) const isLoading = ref(false) const onAdd = () =&gt;  { isLoading.value = true try{ service.add(formData.value); }catch (e){ Alert ("Failed to add") }finally { isLoading.value = false } } return { formData, onAdd } }

The called view is simpler:)

 <template> <form> <input type="text" v-model="formData.name"> <button @click="onAdd"></button> </form> </template> <script setup lang="ts"> const {formData,onAdd} = useAdd(UserService,UserEntity) </script>

Is it a lot better to write like this?

Summary

The code in this article may not have been verified, but it was written directly in markdown when writing the article. If there is any error, please point out in the comment area.

Here we return to the topic mentioned in the previous article. We have not only used object orientation, but also used some functional hooks.

There is no need to choose between the two, adults, why not both?

That's all

We've been using vue3 TypeScript ElementPlus Java SpringBoot JPA And other technology stacks to realize a set of object-oriented front and rear unified development whole stack projects. Welcome to pay attention to:

front end: https://github.com/HammCn/AirPower4T

Back end: https://github.com/HammCn/AirPower4J </e></e></e></userentity></e></string,>

Expand to read the full text
Loading
Click to lead the topic 📣 Post and join the discussion 🔥
Reward
zero comment
zero Collection
zero fabulous
 Back to top
Top