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-project
This command is used to create avite-project
The 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.json
The dependency declared in.
- We use the
IDE
Open the project you just created, the overall project is shown below, vite official provided us with a simple demo.
- Open the
package.json
View startup commands run commands in terminal:yarn run dev
Or clickide
To launch the project.
- Done, browser access
http://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.
- from
package.json
Extract 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 opened
console
After the discoverymain.js 404
Difficult to findmain.js
And that I havemain.ts
Try changing the suffix. Change the suffix tojs
After 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-vue3
This command is used to create a file namedchat-system-vue3
The project.
- After creation, the following information is displayed.
- with
IDE
Open the project. Open itpackage.json
File, 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 viewCLI
Open demo created by defaultmain.js
The file was foundApp.vue
The 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 removednew VueRouter({})
I’m going to write it this waycreateRouter({})
- Hash mode and history mode are declared instead of the original
mode
Option changed tocreateWebHashHistory()
andcreateWebHistory()
It’s more semantic - Ts type annotation for route declaration
Array<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 import
import { createStore } from "vuex"
Removes the entire previous importimport Vuex from 'vuex'
- removed
Vue.use(Vuex)
The writing of - Discard the previous one when exporting
new Vuex.Store
I’ve changed the way I wrote itcreateStore
Writing.
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:
- exposed
install
I’m going to go from the originalPlugin.install
Change toinstall
- Added the type declaration for TS
Object.defineProperties
I’ve abandoned it and now I’m using itapp.config.globalProperties
Can 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 original
new Vue(App)
I changed it to import on demandcreateApp(App)
- When using plug-ins, also from the original
Vue.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.
- create
type
Folder, folder createdComponentDataType.ts
To specify the type used in the component. - create
enum
Folder 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 💌