preface

On September 18, 2020, the official version of VUE2 was released. A few days ago, I read the whole document and felt deeply that it could solve some pain points in my project, so I decided to reconstruct the previous vue2 open source project.

This article documents the process of refactoring the VUe2 project and invites interested developers to read it.

Environment set up

We were going to use Vite + vue3 + VueRouter + vuex + typescript to build the project, but after a bit of work, we found that Vite only supports VUE, and some libraries around Vue are not supported yet, so we can’t use them in the project.

In the end, we decided to use Vue Cli 4.5 for the build.

Although Vite is not working properly in the project yet, I did a bit of a toss and recorded the process and some errors during the toss.

Build projects using Vite

The package management tool used in this document is YARN. After you upgrade yarn to the latest version, you can create vite projects.

Initialize the project

Next, let’s look at the specific steps.

  • Open terminal, go to your project directory, and run the following command:yarn crete vite-app vite-projectThis command is used to create avite-projectThe project.

  • Once created, you should have the files shown below.

  • Enter the created project and run the following command:yarn install, the command will installpackage.jsonThe dependency declared in.

  • We use theIDEOpen the project you just created, the overall project is shown below, vite official provided us with a simple demo.

  • Open thepackage.jsonView startup commands run commands in terminal:yarn run devOr clickideTo launch the project.

  • Done, browser accesshttp://localhost:3000/, as shown below.

Integrate Vue peripheral libraries

We replace the Vue CLI-initialized project file with the Vite initialized project, modify the dependencies in package.json, and then reinstall the dependencies.

The specific process is as follows:

  • Replace the file and the replaced project directory is shown below.

  • frompackage.jsonExtract the dependency we need, extract the file after the next.
{
  "name": "vite-project"."version": "0.1.0 from"."scripts": {
    "dev": "vite"."build": "vite build"
  },
  "dependencies": {
    "core-js": "^ 3.6.5." "."vue": "^ 3.0.0-0"."vue-class-component": "^ 8.0.0-0"."vue-router": "^ 4.0.0-0"."vuex": "^ 4.0.0-0"
  },
  "devDependencies": {
    "vite": 1 "" ^ 1.0.0 - rc.."@typescript-eslint/eslint-plugin": "^ 2.33.0"."@typescript-eslint/parser": "^ 2.33.0"."@vue/compiler-sfc": "^ 3.0.0-0"."@vue/eslint-config-prettier": "^ 6.0.0"."@vue/eslint-config-typescript": "^ 5.0.2"."eslint": "^ 6.7.2." "."eslint-plugin-prettier": "^ 3.1.3"."eslint-plugin-vue": "^ 7.0.0-0"."node-sass": "^ 4.12.0"."prettier": "^ 1.19.1"."sass-loader": "^ 8.0.2." "."typescript": "~ 3.9.3." "
  },
  "license": "MIT"
}

Copy the code

  • Start the project, no error, mouth crazy up.

  • After the browser is accessed, the blank page is openedconsoleAfter the discoverymain.js 404

Difficult to findmain.jsAnd that I havemain.tsTry changing the suffix. Change the suffix tojsAfter the file is not error 404, but there is a new error.

Vite service 500 and @alias are not recognized, so I open the CONSOLE of the IDE and see the error, probably SCSS fault, Vite does not support SCSS yet.

SCSS is not supported, alias is not recognized, and I searched online but failed to find a solution. All these basic things cannot be supported by Vite, so it cannot be used in the project, so I gave up.

All that said, Vite still has a lot to go, and when it matures in the community, it can be applied to projects.

Build the project using the Vue Cli

Since Vite is not suitable, we continue to use WebPack, and here we choose to use Vue CLI 4.5 to create the project.

Initialize the project

  • Enter the project directory on the terminal and run the following command:vue create chat-system-vue3This command is used to create a file namedchat-system-vue3The project.

  • After creation, the following information is displayed.

  • withIDEOpen the project. Open itpackage.jsonFile, view the project startup command or click the compiler’s run button.

  • OK, you are done. Open the browser and access the Intranet address of the terminal.

Resolve error reporting

In the viewCLIOpen demo created by defaultmain.jsThe file was foundApp.vueThe file reported a type error. The specific type cannot be derived.

At first, I was confused, remembering that the Vue documentation says that enabling TypeScript requires TypeScript to correctly infer the types in Vue component options that need to be useddefineComponent.

The app. vue file code is as follows:

<template>
  <div id="nav">
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link>
  </div>
  <router-view />
</template>

<style lang="scss">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#nav {
  padding: 30px;

  a {
    font-weight: bold;
    color: #2c3e50;

    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
</style>

Copy the code

After looking at the code, we found that the CLI-generated code did not contain the code described in the documentation, so we added it and exported it.

import { defineComponent } from "vue";
const Component = defineComponent({
  // Type inference is enabled
});
export default Component;
Copy the code

After adding the above code, our code does not report errors.

According to the defineComponent website, we could write the logic code in the defineComponent package, but when I looked at the Demo Home component provided by CIL, he wrote it as follows.

export default class Home extends Vue {}
Copy the code

There is a file named shims-vue.d.ts in the SRC directory of the project, which declares the return types of all vUE files, so we can write as described above. The declaration file code is as follows.

declare module "*.vue" {
  import { defineComponent } from "vue";
  const component: ReturnType<typeof defineComponent>;
  export default component;
}

Copy the code

This looks more typescript-friendly, but it only supports some attributes. Again, the logic of our component can be written inside the class, so apply the changes we just made in the app.vue file, as shown below.

<script lang="ts">
import { Vue } from "vue-class-component";
export default class App extends Vue {}
</script>
Copy the code

The class notation supports the following attributes:

Configure the IDE

This section only applies to WebStorm, skip this section if the editor is different.

We integrated ESLint and Prettier into the project, which webStorm doesn’t have by default, so we had to do it ourselves.

  • Open the WebStorm configuration menu as shown below

  • Search for ESLint and configure it as shown in the following figure. When configured, click APPLY and OK.

  • Search for prettier and configure it as shown in the following figure. After the configuration, click APPLY and OK.

After configuring the above content, there is still a problem. There is no prompt when using vue commands such as v-if v-for on the component. This is because webstorm did not send the node_modules package correctly.

After the preceding operations are performed, the wait time depends on the CPU performance, and the computer will become hot. These are normal phenomena

After success, we found that the editor already recognized the V-instruction and gave the corresponding prompt.

Project Directory comparison

Following the above steps to create a vue3 project, we will then compare the directory of the vue2 project that needs to be refactored with the directory created above.

  • The directory for the vue2.0 project is shown below

  • The directory for the VUe3.0 project is shown below

On closer inspection, there is no major difference in the directory, just the addition of typescript configuration files and auxiliary files to use TS within the project.

Project to reconstruct

Next, we will step by step migrate the files of the vue2 project to the vue3 project and modify the inappropriate areas to make them suitable for Vue3.0.

Adaptive Routing Configuration

We start from the routing configuration file, open the router/index.ts file of vue3 project, and find an error as follows.

The error message is that the type has not been derived. I looked at the following route and blindly guessed that it needed to be returned by a function. So I tried it.

  {
    path: "/".name: "Home".component: () = > Home
  }
Copy the code

The overall routing configuration file code is as follows:

import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
import Home from ".. /views/Home.vue";

const routes: Array<RouteRecordRaw> = [
  {
    path: "/".name: "Home".component: () = > Home
  },
  {
    path: "/about".name: "About".// route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () = >
      import(/* webpackChunkName: "about" */ ".. /views/About.vue")}];const router = createRouter({
  history: createWebHashHistory(),
  routes
});

export default router;

Copy the code

Let’s take a look at the routing configuration in the VUe2 project. I’ve copied some of the code for simplicity, as shown below.

import Vue from 'vue'
import VueRouter from 'vue-router'
import MsgList from '.. /views/msg-list'
import Login from ".. /views/login"
import MainBody from '.. /components/main-body'
Vue.use(VueRouter);

const routes = [
    {
        path: '/'.redirect: '/contents/message/message'}, {name: 'contents'.path: '/contents/:thisStatus'.// Redirect to nested routines by
        redirect: '/contents/:thisStatus/:thisStatus/'.components: {
            mainArea: MainBody
        },
        props: {
            mainArea: true
        },
        children: [{path: 'message'.components: {
                    msgList: MsgList
                }
            }
        ],
    },
    {
        name: 'login'.path: "/login".components: {
            login:Login
        }
    }
];

const router = new VueRouter({
    // mode: 'history',
    routes,
});

export default router

Copy the code

After observation, their differences are as follows:

  • Vue.use(VueRouter)This notation is removed
  • new VueRouter({})I’m going to write it this waycreateRouter({})
  • Hash mode and history mode are declared instead of the originalmodeOption changed tocreateWebHashHistory()andcreateWebHistory()It’s more semantic
  • Ts type annotation for route declarationArray<RouteRecordRaw>

Now that we know the difference, we can adapt and migrate the route. Migrate the completed route configuration file: router/index.ts

There is a pit where a lazy route must return a function when loaded. For example: Component: () => import(“.. / views/MSG – list. Vue “). Or I’ll give you a yellow warning.

Adapted to Vuex configuration

Let’s take a look at the differences in vuex usage between the two versions, as shown below in the VUex configuration for VUE3.

import { createStore } from "vuex";

export default createStore({
  state: {},
  mutations: {},
  actions: {},
  modules: {}});Copy the code

Let’s take a look at the VUex configuration in the VUe2 project. For brevity, I’ve only listed the general code.

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

export default new Vuex.Store({
  state: {},mutations: {},actions: {},modules: {}})Copy the code

After comparison, we found differences as follows:

  • According to the need to importimport { createStore } from "vuex"Removes the entire previous importimport Vuex from 'vuex'
  • removedVue.use(Vuex)The writing of
  • Discard the previous one when exportingnew Vuex.StoreI’ve changed the way I wrote itcreateStoreWriting.

With these differences in mind, we can adapt and migrate the code to the completed vuex configuration file: Store /index.ts

If you need to mount something on a vue prototype, you can’t use the old prototype mount method, you need to use the new method config.globalProperties, see the official documentation for details.

My project uses a websocket plugin that needs to mount methods on the Vue prototype in Vuex. Here’s how I did it.

  • Export the createApp method in main.ts.

    import { createApp } from "vue";
    
    const app = createApp(App);
    
    export default app;
    
    Copy the code
  • Import main.ts in store/index.ts and call method mount.

      mutations: {
        // Open the connection
        SOCKET_ONOPEN(state, event) {
          main.config.globalProperties.$socket = event.currentTarget;
          state.socket.isConnected = true;
          // When the connection is successful, the heartbeat message is periodically sent to avoid being disconnected from the server
          state.socket.heartBeatTimer = setInterval(() = > {
            const message = "Heartbeat message";
            state.socket.isConnected &&
              main.config.globalProperties.$socket.sendObj({
                code: 200.msg: message }); }, state.socket.heartBeatInterval); }}Copy the code

Adapter axios

Here’s how AXIos differs when packaged as a plug-in:

  • exposedinstallI’m going to go from the originalPlugin.installChange toinstall
  • Added the type declaration for TS
  • Object.definePropertiesI’ve abandoned it and now I’m using itapp.config.globalPropertiesCan be mounted

The code for adaptation is as follows:

import { App } from "vue";
import axiosObj, { AxiosInstance, AxiosRequestConfig } from "axios";
import store from ".. /store/index";

const defaultConfig = {
  // baseURL is omitted here. Considering that the project may be developed by many people and the domain name may be different, the domain name is configured in base.js separately by pulling out the API

  // Request timeout
  timeout: 60 * 1000.// Whether credentials are required for cross-domain requests
  // withCredentials: true, // Check cross-site Access-Control
  heards: {
    get: {
      "Content-Type": "application/x-www-form-urlencoded; charset=utf-8"
      // Use the universal request header as the base configuration. When a special request header is needed, it is passed in as a parameter, overwriting the underlying configuration
    },
    post: {
      "Content-Type": "application/json; charset=utf-8"
      // Use the universal request header as the base configuration. When a special request header is needed, it is passed in as a parameter, overwriting the underlying configuration}}};/** * Request failure after the unified processing, of course, there are more status code judgment, according to your business needs to expand *@param Status Indicates the status code * of the failed request@param MSG Error message */
const errorHandle = (status: number, msg: string) = > {
  // Determine the status code
  switch (status) {
    // 401: The login page is displayed because the token is not logged in or expired
    case 401:
      // Jump to the login page
      break;
    // 403 Access denied
    case 403:
      

      break;
    // 404 request does not exist
    case 404:
      // The resource does not exist
      break;
    default:
      console.log(msg); }};export default {
  // Expose the installation method
  install(app: App, config: AxiosRequestConfig = defaultConfig) {
    let _axios: AxiosInstance;

    // Create an instance
    _axios = axiosObj.create(config);
    // Request interceptor
    _axios.interceptors.request.use(
      function(config) {
        // Get the token from vuex
        const token = store.state.token;
        // If the token exists, add it to the request header
        token && (config.headers.token = token);
        return config;
      },
      function(error) {
        // Do something with request error
        error.data = {};
        error.data.msg = "Server exception";
        return Promise.reject(error); });// Response interceptor
    _axios.interceptors.response.use(
      function(response) {
        // Clear the tokens in the local storage. If you need to refresh the tokens, exchange the old tokens with the server and add the new tokens to the VUEX
        if (response.data.code === 401) {
          localStorage.removeItem("token");
          // Page refresh
          parent.location.reload();
        }
        // Return only data in response
        return response.data;
      },
      function(error) {
        if (error) {
          // The request has been sent, but not in 2xx
          errorHandle(error.status, error.data.msg);
          return Promise.reject(error);
        } else {
          / / broken network
          return Promise.reject(error); }});// Mount AXIos to the global properties of vueapp.config.globalProperties.$axios = _axios; }};Copy the code

Then use it in main.js and you can use it in your code via this.$axios.xx.

However, mounting AXIos to VUE above is unnecessary, because I have removed the API from each individual API file by importing the configuration file we wrapped axios, and then encapsulating the interface with the imported AXIOS instance. (PS: because I didn’t notice this before, I foolishly packaged it into a plug-in 😂)

So, instead of wrapping it as a plug-in, it is a configuration wrapper for AXIos. We put it in the config directory and modify the code to config/axios.ts.

Finally, mount the API to the global properties in main.ts.

import { createApp } from "vue";
import api from "./api/index";
const app = createApp(App);
app.config.globalProperties.$api = api;
Copy the code

This.$api.xx can then be used in business code to call our thrown interface module by module.

Shims-vue.d. ts type declaration file

Shims-vue. D. ts is a Typescript declaration file. When TS is enabled in the project, some files are wrapped by us and their types are complicated, so WE need to declare them manually.

For example, $API, which we mounted on the prototype above, exports a class file. In this case, the type is more complex, ts cannot deduce its type, and we will report an error when using it.

To resolve this error, we need to declare the API type in shims-vue.d.ts

// Declare the global attribute type
declare module "@vue/runtime-core" {
  interface ComponentCustomProperties<T> {
    $api: T; }}Copy the code

Note: in the shims-vue.d.ts file, when there are more than one type declaration, the package that needs to be imported within the component cannot be carried out inside it. It needs to be written in the outermost layer, otherwise an error will be reported.

Adaptive entry file

With typescript enabled, the entry file changes from main.js to main.ts. The file is written differently than before:

  • Initialize the mount vue from the originalnew Vue(App)I changed it to import on demandcreateApp(App)
  • When using plug-ins, also from the originalVue.use()Changed to,createApp(App).use()

There is no ts type declaration file, so when importing ts can’t deduce its type, I have to use // @ts-ignore to make TS ignore it.

Full entry file address: main.ts

Adapter component

After the infrastructure is complete, we will adapt the components. Let’s first try to move all the components of 2.x project to see if we can start directly.

The result is predictable, it doesn’t work. Because I used 2. X plugins, vue3.0 has some changes in the way plugins are packaged. A total of two plug-ins v-Viewer and VUe-native Websocket are referenced in my project. The plug-in V-Viewer has no solution, and it uses too much 2.x syntax in its low-level use, so I choose to give up this plug-in. Vue — native websocket this plug-in is to use vue. Prototype. Xx of writing was abandoned, with a new writing vue. Config. GlobalProperties. Xx can replace it.

After the replacement is complete, recompile and then start the project as shown below. The error is resolved and the project is successfully started.

As you can see in the image above, the console has a yellow warning because our component’s code still uses the syntax of VUe2.x and we need to reorganize the methods in the component to fit vue3.0.

Note: After the component script tag declares lang=”ts”, the component must be defined using the defineComponent global method as stated in the Vue official documentation.

Component optimization

Next, let’s refactor from the login.vue component to see what optimizations have been made.

  1. createtypeFolder, folder createdComponentDataType.tsTo specify the type used in the component.
  2. createenumFolder to place the enumerations used in the component.

For example, we need to specify the type of each attribute of the object returned by data. Therefore, we create a type named loginDataType in ComponentDataType. The code is as follows.

export type loginDataType<T> = {
  loginUndo: T; // Disable the login icon
  loginBtnNormal: T; // Button icon for login
  loginBtnHover: T; // The login icon with the mouse hovering
  loginBtnDown: T; // The login icon when the mouse is pressed
  userName: string; / / user name
  password: string; / / password
  confirmPassword: string; // Confirm login password during registration
  isLoginStatus: number; Login status: 0. Not logged in 1. Logging in 2
  loginStatusEnum: Object; // Enumeration of login status
  isDefaultAvatar: boolean; // Profile picture is the default profile picture
  avatarSrc: T; // Avatar address
  loadText: string; // Load the text of the layer
};
Copy the code

Once the type has been declared, it can be used in the component as follows:

import { loginDataType } from "@/type/ComponentDataType";
export default defineComponent({
  data<T>(): loginDataType<T> {
    return {
      loginUndo: require(".. /assets/img/login/[email protected]"),
      loginBtnNormal: require(".. /assets/img/login/[email protected]"),
      loginBtnHover: require(".. /assets/img/login/[email protected]"),
      loginBtnDown: require(".. /assets/img/login/[email protected]"),
      userName: "".password: "".confirmPassword: "".isLoginStatus: 0.loginStatusEnum: loginStatusEnum,
      isDefaultAvatar: true.avatarSrc: require(".. /assets/img/login/[email protected]"),
      loadText: "Up in"}; }})Copy the code

Complete address of the above code:

  • type/ComponentDataType.ts

  • login.vue

For example, isLoginStatus in the data above has three states, and we need to do different things according to these three states. If we directly use numbers to represent the three states and directly assign numbers, it will be a very painful thing in the later maintenance. If it is defined as enum, its state can be seen at a glance from its semantics.

We created in the enum folder ComponentEnum. Ts file, all components used in the enumeration will defined in this file, then create loginStatusEnum within the components, the code is as follows:

export enum loginStatusEnum {
  NOT_LOGGED_IN = 0./ / not logged in
  LOGGING_IN = 1./ / login
  REGISTERED = 2 / / register
}
Copy the code

Once declared, we can use it in the component as follows:

import { loginStatusEnum } from "@/enum/ComponentEnum";

export default defineComponent({
  methods: {
    stateSwitching: function(status) {
      case Conditions of "1":
      	this.isLoginStatus = loginStatusEnum.LOGGING_IN;
      	break;
      case "Conditions of 2":
      	this.isLoginStatus = loginStatusEnum.NOT_LOGGED_IN;
      	break; }}})Copy the code

Complete address of the above code:

  • enum/ComponentEnum.ts

  • login.vue

This points to the

In the process of adapting the component, the this inside the method is not well recognized, so we have to use a very stupid method to solve the problem.

As follows:

const _img = new Image();
_img.src = base64;
_img.onload = function() {
    const _canvas = document.createElement("canvas");
    const w = this.width / scale;
    const h = this.height / scale;
    _canvas.setAttribute("width", w + "");
    _canvas.setAttribute("height", h + "");
    _canvas.getContext("2d")? .drawImage(this.0.0, w, h);
    const base64 = _canvas.toDataURL("image/jpeg");
}
Copy the code

This inside the onload method should point to _img, but ts doesn’t think so, as shown below.

This object does not contain the width property. The solution is to change this to _img. Problem solved.

Dom object type definition

When manipulating DOM objects, ts cannot infer the specific type, as shown below:

sendMessage: function(event: KeyboardEvent) {
      if (event.key === "Enter") {
        // Prevents edit boxes from generating div events by default
        event.preventDefault();
        let msgText = "";
        // Get all the children of the input box
        const allNodes = event.target.childNodes;
        for (const item of allNodes) {
          // Determine if the current element is an IMG element
          if (item.nodeName === "IMG") {
            if (item.alt === "") {
              / / is pictures
              let base64Img = item.src;
              // Remove the base64 image prefix
              base64Img = base64Img.replace(/^data:image\/\w+; base64,/."");
              // Random file name
              const fileName = new Date().getTime() + "chatImg" + ".jpeg";
              // Convert base64 to file
              const imgFile = this.convertBase64UrlToImgFile(
                base64Img,
                fileName,
                "image/jpeg"
              );
            }
          }
        }
      }
}
Copy the code

The message box contains the image and text. To process the image separately, we need to get all the nodes from the target. The type of childNodes is NodeList. Each element is of Node type. If the nodeName attribute of the current traversal element is IMG, it is an image. We obtain its Alt attribute and then obtain its SRC attribute.

However, ts will report that the Alt and SRC attributes do not exist as follows:

At this point, we need to assert item as HTMLImageElement.

Complex type definition

In the process of adapting components, we encountered a complex data type definition. The data is as follows:

 data(){
    return {
      friendsList: [{groupName: "我".totalPeople: 2.onlineUsers: 2.friendsData: [{username: "The Amazing Programmer.".avatarSrc:
                "https://www.kaisir.cn/uploads/1ece3749801d4d45933ba8b31403c685touxiang.jpeg".signature: "Today's efforts are for the future.".onlineStatus: true.userId: "c04618bab36146e3a9d3b411e7f9eb8f"
            },
            {
              username: "admin".avatarSrc:
                "https://www.kaisir.cn/uploads/40ba319f75964c25a7370e3909d347c5admin.jpg".signature: "".onlineStatus: true.userId: "32ee06c8380e479b9cd4097e170a6193"}]}, {groupName: "My friend.".totalPeople: 0.onlineUsers: 0.friendsData: []}, {groupName: "My family".totalPeople: 0.onlineUsers: 0.friendsData: []}, {groupName: "My colleague.".totalPeople: 0.onlineUsers: 0.friendsData: []}]}; },Copy the code

That’s how I defined it at the beginning.

Nested together, thought it was okay, put in code, error length mismatch, so write knowledge to define the type of the first object.

After some help, they said it should be written separately and not nested like this. The correct way to write it is as follows:

  • Type separate definition

    // Contact panel Data property definition
    export type contactListDataType<V> = {
      friendsList: Array<V>;
    };
    
    // Contact list type definition
    export type friendsListType<V> = {
      groupName: string; // Group name
      totalPeople: number; / / the total number
      onlineUsers: number; // Number of people online
      friendsData: Array<V>; // List of friends
    };
    
    // Contact type definition
    export type friendsDataType = {
      username: string; / / nickname
      avatarSrc: string; // Avatar address
      signature: string; // Signature
      onlineStatus: boolean; // Online status
      userId: string; / / user id
    };
    
    Copy the code
  • Use in components

    import {
      contactListDataType,
      friendsListType,
      friendsDataType
    } from "@/type/ComponentDataType";
    
    data(): contactListDataType<friendsListType<friendsDataType>> {
        return {
          friendsList: [{groupName: "我".totalPeople: 2.onlineUsers: 2.friendsData: [{username: "The Amazing Programmer.".avatarSrc:
                    "https://www.kaisir.cn/uploads/1ece3749801d4d45933ba8b31403c685touxiang.jpeg".signature: "Today's efforts are for the future.".onlineStatus: true.userId: "c04618bab36146e3a9d3b411e7f9eb8f"
                },
                {
                  username: "admin".avatarSrc:
                    "https://www.kaisir.cn/uploads/40ba319f75964c25a7370e3909d347c5admin.jpg".signature: "".onlineStatus: true.userId: "32ee06c8380e479b9cd4097e170a6193"}]}, {groupName: "My friend.".totalPeople: 0.onlineUsers: 0.friendsData: []}, {groupName: "My family".totalPeople: 0.onlineUsers: 0.friendsData: []}, {groupName: "My colleague.".totalPeople: 0.onlineUsers: 0.friendsData: []}]}; }Copy the code

Understanding the use of typescript generics, experience ++😄

The tag attribute is removed

When we use router-link, it will render as a tag by default. If we want to render it as another custom tag, we can modify it by using the tag attribute, as shown below:

<router-link :to="{ name: 'list' }" tag="div">
Copy the code

However, in the new version of Vue-Router, the event and tag attributes are removed, so we can’t use them this way. Of course, the official documentation also gives the solution to use V-solt as an alternative. In the above code, we want to render it as a div, using v-solt as follows:

<router-link :to="{ name: 'list' }" custom v-slot="{ navigate }">
    <div
      @click="navigate"
      @keypress.enter="navigate"
      role="link"
    >
  </div>
</router-link>
Copy the code

For more information on this topic, go to the manager-of-event -and-tag-props-in-router-link

Component cannot exlink file

When I put the page as a component into the import declaration, VUe3 does not support linking the logical code outside, as shown below, via SRC.

<script lang="ts" src=".. /assets/ts/message-display.ts"></script>Copy the code

Reference in the component.

<template>
   <message-display message-status="0" list-id="1892144211" />
</template>

<script>
import messageDisplay from "@/components/message-display.vue";
export default defineComponent({
  name: "msg-list",
  components: {
    messageDisplay
  },
})
</script>
Copy the code

Then, he reported an error, the type cannot be inferred.

I tried many methods, but finally found that the SRC external chain could not pass the problem, so I wrote the code in the TS file in the Vue template to report the error.

Assertions must be made using AS

The imgContent variable may have multiple types. Ts cannot infer the specific type, so we need to make our own assertion to specify the type. I used Angle brackets, but it failed. Webstorm probably doesn’t fit vue3 very well, his error is very strange, as shown below

At first, I was confused when I saw this error. A friend told me to use the exclusion method and comment the code closest to it to see if it would return an error. Then I found the root of the problem, which was the type assertion.

The problem was solved, but I couldn’t figure out why it was necessary to use AS, because Angle brackets were the same as his, so I checked the official documents.

As stated in the official documentation, only the AS syntax can be used when JSX is enabled. Probably vue3’s template syntax is JSX-enabled by default.

Ref arrays do not automatically create arrays

In vue2, using the ref attribute in v-for populates the corresponding $refs attribute with a ref array. The following is part of the buddyList code, which loops through friendsList to put groupArrow and buddyList into the ref array.

<template> <div class="group-panel"> <div class="title-panel"> <p class="title"> friend </p> </div> <div class="row-panel" v-for="(item,index) in friendsList" :key="index"> <div class="main-content" @click="groupingStatus(index)"> <div class="icon-panel"> <img ref="groupArrow" src=".. /assets/img/list/[email protected]" Alt =" left arrow "/> </div> <div class="name-panel"> <p>{{item.groupName}}</p> </div> <div class="quantity-panel"> <p>{{item.onlineUsers}}/{{item.totalPeople}}</p> </div> </div> <! <div class="buddy-panel" ref="buddyList" style="display:none"> <div class="item-panel" v-for="(list,index) in item.friendsData" :key="index" tabindex="0"> <div class="main-panel" @click="getBuddyInfo(list.userId)"> <div <img: SRC ="list.avatarSrc" Alt =" user profile "> </div> <div class=" boot-panel "> <! - the nickname - > < div class = "name - the panel" > {{list. The username}} < / div > <! Signature -- -- -- > < div class = "signature - a panel" > [{{list. OnlineStatus? "online" : "offline"}}] {{list. The signature}} < / div > < / div > < / div > < / div > </div> </div> </div> </template>Copy the code

We can access the corresponding node through $refs, as shown below.

import lodash from 'lodash';
export default {
   name: "contact-list".methods: {// Switch the group status
        groupingStatus:function (index) {
            if(lodash.isEmpty(this.$route.params.userId)===false) {this.$router.push({name: "list"}).then();
            }
            // Get the value of transform
            let transformVal = this.$refs.groupArrow[index].style.transform;
            if(lodash.isEmpty(transformVal)===false) {// Intercepts the value of rotate
                transformVal = transformVal.substring(7.9);
                // Determine whether to expand
                if (parseInt(transformVal)===90) {this.$refs.groupArrow[index].style.transform = "rotate(0deg)";
                    this.$refs.buddyList[index].style.display = "none";
                }else{
                    this.$refs.groupArrow[index].style.transform = "rotate(90deg)";
                    this.$refs.buddyList[index].style.display = "block"; }}else{
                // The first time you click add Transform, rotate it 90 degrees
                this.$refs.groupArrow[index].style.transform = "rotate(90deg)";
                this.$refs.buddyList[index].style.display = "block"; }},// Get the list of friends
        getBuddyInfo:function (userId) {
            // Determine whether the current route params is the same as the userId of the current click item
            if(! lodash.isEqual(this.$route.params.userId,userId)){
                this.$router.push({name: "dataPanel".params: {userId: userId}}).then(); }}}}Copy the code

This is fine in VUe2, but in vue3 you get an error. The authorities think this behavior will become ambiguous and inefficient. A new syntax is used to solve this problem, and ref is used to bind a function to deal with it, as shown below.

<template> <! <img :ref="setGroupArrow" SRC =".. /assets/img/list/[email protected]" Alt =" left arrow "/> <! <div class="buddy-panel" :ref="setGroupList" style="display:none"> </div> </template> <script lang="ts"> import _ from "lodash"; import { defineComponent } from "vue"; import { contactListDataType, friendsListType, friendsDataType } from "@/type/ComponentDataType"; export default defineComponent({ name: "contact-list", data(): contactListDataType<friendsListType<friendsDataType>> { return { groupArrow: [], groupList: Dom setGroupArrow: function(el: Element) {this.grouparrow.push (el); }, // setGroupList dom setGroupList: function(el: Element) {this.grouplist.push (el); }, groupingStatus: function(index: number) {if (! _.isEmpty(this.$route.params.userId)) { this.$router.push({ name: "list" }).then(); } let transformVal = this.grouparrow [index].style.transform; if (! .isEmpty(transformVal)) {// Intercepts the value of transformVal = transformVal. Substring (7, 9); If (parseInt(transformVal) === 90) {this.grouparrow [index].style.transform = "rotate(0deg)"; this.groupList[index].style.display = "none"; } else { this.groupArrow[index].style.transform = "rotate(90deg)"; this.groupList[index].style.display = "block"; }} else {this.grouparrow [index].style. Transform = "rotate(90deg)"; this.groupList[index].style.display = "block"; }})}Copy the code

For the complete code, go to contact-list.vue

For more information about ref, go to the official documentation: ref array in V-for

Emit events need to add validation

$emit(“xx-xx-xx”). Vue3 suggests that we emit all events emitted from the component via emits. If you do not log via emits, you will see the following warning in the browser console.

Component emitted event "xx-xx-xx" but it is neither declared in the emits option nor as an "xx-xx-xx" prop.
Copy the code

The solution is as simple as adding emits to the child component that calls the parent method to validate it, as shown below. The argument is evaluated again and returns true if the condition is met, false otherwise.

export default defineComponent({
  emits: {
    // Vue3 suggests validation for all EMIT events
    "update-last-message": (val: string) = > {
      return! _.isEmpty(val); }}}Copy the code

The project address

At this point, the project is ready to start and the refactoring is complete. The next problem to solve is that vuE-native WebSocket does not work in VUE3. At first I thought it would be ok to change the way it was written in the prototype line. However, I thought it was too easy. After the change, the editor does not report errors, but it will report a lot of errors at runtime. I had no choice but to remove the part of the code that interacts with the server first.

Next, I will try to reconstruct the vuE-native Websocket plug-in to support VUE3.

Finally, put the project code address reconstructed in this paper: chat-system

Write in the last

  • If there are any errors in this article, please correct them in the comments section. If this article helped you, please like it and follow 😊

  • This article was first published in nuggets. Reprint is prohibited without permission 💌