предисловие
18 сентября 2020 года была выпущена официальная версия vue3.Я прочитал документ в целом несколько дней назад, и я глубоко почувствовал, что он может решить некоторые болевые точки в моем проекте, поэтому я решил провести рефакторинг предыдущего проект с открытым исходным кодом vue2.
В этой статье описан процесс рефакторинга проекта vue2.Заинтересованные разработчики могут прочитать эту статью.
Строительство окружающей среды
изначально предполагалось использоватьvite + vue3 + VueRouter + vuex + typescriptВ рамках проекта, но после мучительного открытияviteНа данный момент поддерживается только vue, а некоторые библиотеки вокруг vue еще не поддерживаются, поэтому их нельзя использовать в проекте.
В итоге решил использоватьVue Cli 4.5строить.
Хоть вите в настоящее время и нельзя нормально использовать в проекте, но я его тоже один раз закинул и записал процесс закидывания и некоторые ошибки.
Создайте проект с помощью vite
Инструмент управления пакетами, используемый в этой статье,yarn, и обновите его до последней версии, чтобы нормально создать проект vite.
Инициализировать проект
Далее рассмотрим конкретные шаги.
- Откройте терминал, перейдите в каталог вашего проекта и выполните команду:
yarn crete vite-app vite-project, который используется для созданияvite-projectс проект.
- После создания вы получите файл, подобный показанному ниже.
- Войдите в созданный проект и выполните команду:
yarn install, который устанавливаетpackage.jsonЗависимости, объявленные в .
- Мы используем
IDEОткройте только что созданный проект, общий проект выглядит следующим образом, официальный сайт Vite предоставляет нам простую демонстрацию.
- Открыть
package.jsonЧтобы просмотреть команду запуска, выполните команду в терминале:yarn run devили нажмитеideЗначок запуска для запуска проекта.
- Готово, доступ через браузер
http://localhost:3000/,Следующим образом.
Интеграция периферийных библиотек Vue
мы будемVue CLIЗамените инициализированный файл проекта наviteПерейдите к инициализированному проекту, затем изменитеpackage.jsonСоответствующие зависимости в, а затем переустановите зависимости.
Конкретный процесс выглядит следующим образом:
- Замените файлы, и замененный каталог проекта показан ниже.
- от
package.jsonИзвлеките необходимые нам зависимости из извлеченных файлов.
{
"name": "vite-project",
"version": "0.1.0",
"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.0.0-rc.1",
"@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"
}
- Запустил проект, об ошибках не сообщалось, а уголки рта дико приподнялись.
- После доступа в браузере пустая страница, откройте
consoleпозже нашелmain.js 404
Прям не найтиmain.js, то ставлюmain.tsПопробуйте изменить суффикс. изменить суффикс наjsПосле этого файл не сообщает об ошибке 404, а появляется новая ошибка.
Сервис vite 500 и псевдоним @ не удалось распознать, поэтому я открыл консоль ide и увидел ошибку, в которой, вероятно, виноват scss, vite пока не поддерживает scss.
scss не поддерживается, псевдонимы не распознаются, и я не могу найти решение после поиска в Интернете.Эти самые основные вещи не могут поддерживаться vite, поэтому его нельзя использовать в проекте, поэтому я сдаться.
Вышесказанное,viteВпереди еще долгий путь, дождитесь, пока он созреет в сообществе, а потом уже применяйте в проекте.
Создайте проект с помощью Vue Cli
из-заviteне подходит, мы продолжаем использоватьwebpack, здесь мы решили использоватьVue CLI 4.5для создания проекта.
Инициализировать проект
- Войдите в каталог проекта в терминале и выполните команду:
vue create chat-system-vue3Эта команда используется для созданияchat-system-vue3с проект.
- После завершения создания, как показано ниже.
- использовать
IDEоткрытый проект, открытыйpackage.jsonфайл, просмотреть команду запуска проекта или напрямую нажать кнопку запуска компилятора.
- Хорошо, все готово, откройте браузер и получите доступ к внутреннему адресу терминала.
Решить проблему с ошибкой
browsingCLIКогда демо создано по умолчанию, откройтеmain.jsфайл найден вApp.vueТип файла неверен, и конкретный тип не может быть определен.
В начале я также был сбит с толку и вспомнил, что сказано в документации Vue: включение TypeScript должно позволять TypeScript правильно определять типы в параметрах компонента Vue, и мне нужно использоватьdefineComponent.
Код файла App.vue выглядит следующим образом:
<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>
После просмотра кода мы нашлиCLIСгенерированный код не содержит кода, описанного в документации, поэтому дополняем его и экспортируем.
import { defineComponent } from "vue";
const Component = defineComponent({
// 已启用类型推断
});
export default Component;
После добавления приведенного выше кода наш код не сообщит об ошибке.
Согласно описанию на официальном сайте, мы можемdefineComponentЛогический код компонента написан в пакете, но я видел демо, предоставленное CIL.HomeПосле того, как компонент был найден, он написал его следующим образом.
export default class Home extends Vue {}
в проектеsrcЕсть каталог с именемshims-vue.d.tsфайл, который объявляет возвращаемый тип всех файлов vue, поэтому мы можем записать его, как указано выше. Код файла декларации выглядит следующим образом.
declare module "*.vue" {
import { defineComponent } from "vue";
const component: ReturnType<typeof defineComponent>;
export default component;
}
Такой способ написания больше соответствует TypeScript, но этот способ написания поддерживает только некоторые свойства, так же и логический код нашего компонента может быть написан внутри класса.App.vueИзменения, сделанные в файле, также применяются здесь, как показано ниже.
<script lang="ts">
import { Vue } from "vue-class-component";
export default class App extends Vue {}
</script>
classАтрибуты, поддерживаемые методом записи, показаны на следующем рисунке:
Настроить IDE
Этот контент относится только кwebstormЕсли редактор пропускает этот раздел.
Мы интегрировали в проектeslintа такжеprettier,по умолчаниюwebstormЭти две вещи не включены, и нам нужно включить их вручную.
-
Откройте меню конфигурации webstorm, как показано ниже.
-
поиск
eslint, настройте, как показано на рисунке ниже, и нажмитеAPPLY,OKВот и все. -
поиск
prettier, настройте, как показано на рисунке ниже, и нажмитеAPPLY,OKВот и все.
После настройки вышеописанного возникает проблема, компоненты используемые вv-if v-forНет подсказки, когда директива VUE есть, потому что WebStorm отправил неправильно.node_modulespackage, выполните следующие действия, чтобы решить эту проблему.
После выполнения вышеуказанного время ожидания зависит отcpuВ зависимости от производительности компьютер в это время будет нагреваться. Это нормально
После успеха мы обнаружили, что редактор может быть распознан нормальноv-Даются инструкции и соответствующие подсказки.
сравнение каталогов проектов
Выполните шаги, описанные выше, чтобы создатьvue3проекта, далее нам нужно будет провести рефакторингvue2Каталог проекта сравнивается с проектом, созданным выше.
-
как показано ниже, для
vue2.0каталог проекта -
как показано ниже, для
vue3.0каталог проекта
Присмотревшись, мы обнаружили, что в каталоге нет большой разницы, просто большеtypescriptфайлы конфигурации и вспомогательные файлы при использовании ts внутри проекта.
Рефакторинг проекта
Далее давайте шаг за шагом перенесем файлы проекта vue2 в проект vue3, изменим неподходящие места и заставим их адаптироваться к vue3.0.
Настройка адаптивной маршрутизации
Начнем с файла конфигурации маршрутизации и откроем проект vue3.router/index.tsфайл, нашел ошибку, ошибка следующая.
Сообщение об ошибке в том, что тип не был выведен.Прочитав способ написания маршрута ниже, я вслепую догадался, что его нужно вернуть функцией, поэтому попробовал, и это действительно так.Правильный маршрут метод заключается в следующем.
{
path: "/",
name: "Home",
component: () => Home
}
Общий код файла конфигурации маршрутизации выглядит следующим образом:
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;
Посмотрим еще разvue2Конфигурация маршрутизации в проекте, для простоты, я выдернул часть кода, как показано ниже.
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: '/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
После наблюдения они различаются следующим образом:
-
Vue.use(VueRouter)Это написание было удалено -
new VueRouter({})написание изменено наcreateRouter({}) - Режим хеширования и режим истории объявлены исходным
modeопция изменена наcreateWebHashHistory()а такжеcreateWebHistory()более семантический - Добавлены аннотации типа ts при объявлении маршрутов
Array<RouteRecordRaw>
Зная разницу между ними, мы можем адаптировать и мигрировать маршрутизацию.Файл конфигурации маршрутизации после миграции:router/index.ts
Здесь есть небольшая яма, когда маршрут лениво загружается, он должен возвращать ему функцию. Например:
component: () => import("../views/msg-list.vue"). В противном случае сообщите о желтом предупреждении.
Адаптировать к конфигурации Vuex
Далее, давайте взглянем на две версии вvuexРазница в использовании заключается в следующемvue3Конфигурация векса.
import { createStore } from "vuex";
export default createStore({
state: {},
mutations: {},
actions: {},
modules: {}
});
Посмотрим еще разvue2Конфигурация vuex в проекте, для краткости привожу только общий код.
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
После сравнения мы обнаружили следующие отличия:
- Импорт по запросу
import { createStore } from "vuex", который удаляет весь предыдущий импортimport Vuex from 'vuex' - удаленный
Vue.use(Vuex)написание - Отбрасывать предыдущие при экспорте
new Vuex.Storeправописание изменено наcreateStoreнаписание.
Зная вышеуказанные различия, мы можем адаптировать и перенести код.Перенесенный файл конфигурации vuex:store/index.ts
Если вам нужно что-то смонтировать на прототипе vue, вы не можете использовать предыдущий метод монтирования прототипа, вам нужно использовать новый метод
config.globalProperties, пожалуйста, обратитесь к подробному использованиюофициальная документация.
Я использую в своем проекте плагин websocket.Ему нужно смонтировать метод на прототипе Vue в vuex.Мой подход следующий.
-
Буду
main.tsсерединаcreateAppэкспорт метода.import { createApp } from "vue"; const app = createApp(App); export default app; -
существует
store/index.tsимпортировать вmain.ts, а затем вызовите метод для его монтирования.mutations: { // 连接打开 SOCKET_ONOPEN(state, event) { main.config.globalProperties.$socket = event.currentTarget; state.socket.isConnected = true; // 连接成功时启动定时发送心跳消息,避免被服务器断开连接 state.socket.heartBeatTimer = setInterval(() => { const message = "心跳消息"; state.socket.isConnected && main.config.globalProperties.$socket.sendObj({ code: 200, msg: message }); }, state.socket.heartBeatInterval); } }
Адаптироваться к аксиомам
Отличия axios от предыдущих при инкапсуляции в плагины следующие:
- незащищенный
installспособ из оригиналаPlugin.installизменился наinstall - Добавлено объявление типа для ts
-
Object.definePropertiesЗаброшено, теперь используется напрямуюapp.config.globalPropertiesПросто установите его
Завершенный код выглядит следующим образом:
import { App } from "vue";
import axiosObj, { AxiosInstance, AxiosRequestConfig } from "axios";
import store from "../store/index";
const defaultConfig = {
// baseURL在此处省略配置,考虑到项目可能由多人协作完成开发,域名也各不相同,此处通过对api的抽离,域名单独配置在base.js中
// 请求超时时间
timeout: 60 * 1000,
// 跨域请求时是否需要凭证
// withCredentials: true, // Check cross-site Access-Control
heards: {
get: {
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8"
// 将普适性的请求头作为基础配置。当需要特殊请求头时,将特殊请求头作为参数传入,覆盖基础配置
},
post: {
"Content-Type": "application/json;charset=utf-8"
// 将普适性的请求头作为基础配置。当需要特殊请求头时,将特殊请求头作为参数传入,覆盖基础配置
}
}
};
/**
* 请求失败后的错误统一处理,当然还有更多状态码判断,根据自己业务需求去扩展即可
* @param status 请求失败的状态码
* @param msg 错误信息
*/
const errorHandle = (status: number, msg: string) => {
// 状态码判断
switch (status) {
// 401: 未登录状态或token过期,跳转登录页
case 401:
// 跳转登录页
break;
// 403 拒绝访问
case 403:
break;
// 404请求不存在
case 404:
// 提示资源不存在
break;
default:
console.log(msg);
}
};
export default {
// 暴露安装方法
install(app: App, config: AxiosRequestConfig = defaultConfig) {
let _axios: AxiosInstance;
// 创建实例
_axios = axiosObj.create(config);
// 请求拦截器
_axios.interceptors.request.use(
function(config) {
// 从vuex里获取token
const token = store.state.token;
// 如果token存在就在请求头里添加
token && (config.headers.token = token);
return config;
},
function(error) {
// Do something with request error
error.data = {};
error.data.msg = "服务器异常";
return Promise.reject(error);
}
);
// 响应拦截器
_axios.interceptors.response.use(
function(response) {
// 清除本地存储中的token,如果需要刷新token,在这里通过旧的token跟服务器换新token,将新的token设置的vuex中
if (response.data.code === 401) {
localStorage.removeItem("token");
// 页面刷新
parent.location.reload();
}
// 只返回response中的data数据
return response.data;
},
function(error) {
if (error) {
// 请求已发出,但不在2xx范围内
errorHandle(error.status, error.data.msg);
return Promise.reject(error);
} else {
// 断网
return Promise.reject(error);
}
}
);
// 将axios挂载到vue的全局属性中
app.config.globalProperties.$axios = _axios;
}
};
а затем положить его в main.jsuse, вы можете передать кодthis.$axios.xxприходи в употребление.
Однако вышеупомянутое монтирование axios в vue лишнее, т.к. я уже извлек api.В каждый отдельный файл api мы импортируем инкапсулированный нами файл конфигурации axios, а затем используем импортированный axios.Инкапсуляция интерфейса, выполненная экземпляр. (пс: Раньше этого не замечал, потому что было лень, поэтому тупо запаковал в плагин 😂)
Затем, если вам не нужно инкапсулировать его как плагин, то он относится к конфигурации и инкапсуляции axios. Мы поместим его в каталог config и немного изменим приведенный выше код. Адрес модифицированного кода:config/axios.ts.
Наконец вmain.tsПодключите API к глобальным свойствам в .
import { createApp } from "vue";
import api from "./api/index";
const app = createApp(App);
app.config.globalProperties.$api = api;
Затем вы можете передать его в бизнес-кодеthis.$api.xxВызываем интерфейс кидаем по модулю.
файл объявления типа shims-vue.d.ts
shims-vue.d.ts — этоTypescriptизФайл декларации, когда проект включает ts, некоторые файлы инкапсулируются нами, тип становится более сложным, и ts не может вывести свой конкретный тип, поэтому нам нужно объявить его вручную.
Например, то, что выше мы установили на прототип$api, он экспортирует файл класса, и в настоящее время этот тип более сложный,tsНевозможно вывести его тип, и мы сообщим об ошибке при его использовании.
Чтобы исправить эту ошибку, нам нужноshims-vue.d.tsзаявление вapiтип
// 声明全局属性类型
declare module "@vue/runtime-core" {
interface ComponentCustomProperties<T> {
$api: T;
}
}
Примечание: в
shims-vue.d.tsВ файле, когда есть более одного объявления типа, пакет импорта в компоненте не может быть осуществлен внутри него, и его нужно записать в самый внешний слой, иначе будет сообщено об ошибке.
Файл записи адаптации
Поскольку включеноtypescript, входной файл задаетсяmain.jsсталmain.ts, запись в файле отличается от предыдущих следующим образом:
- Изначально смонтировать vue из оригинала
new Vue(App)Изменено на запись импорта по требованиюcreateApp(App) - При использовании плагина исходный
Vue.use()изменился на,createApp(App).use()
В моем проекте есть ссылки на несколько плагинов, и мне нужно выполнить некоторые операции инициализации в файле ввода.Плагин все еще версии 2.x, и для ts нет файла объявления типа.Поэтому ts не может вывести свой тип при импорте. использовать// @ts-ignoreПусть ТС игнорирует.
Полный адрес файла записи:main.ts
Компоненты адаптера
После завершения инфраструктуры давайте адаптируем компоненты.Для начала попробуем переместить все компоненты проекта 2.x, чтобы посмотреть, можно ли их запустить напрямую.
Результат можно представить, можно не запускать. Поскольку я использовал плагин 2.x, инкапсуляция плагина в vue3.0, некоторые методы записи изменились. Всего в моем проекте упоминается 2 плагина.v-viewer,vue-native-websocket,v-viewerУ этого плагина нет решения, оно используется внизу2.xСинтаксиса слишком много, поэтому я решил отказаться от этого плагина.vue-native-websocketЭтот плагин используетсяVue.prototype.xxОрфография отбрасывается, и используется новое написаниеVue.config.globalProperties.xxПросто замените его.
После завершения замены перекомпилируйте, а затем запустите проект, как показано ниже, ошибка будет устранена, и проект запустится успешно.
Как вы можете видеть на рисунке выше, в консоли есть предупреждение желтого цвета, так как код нашего компонента все еще использует синтаксис vue2.x, нам нужно переставить методы в компоненте, чтобы адаптироватьvue3.0.
Примечание. После объявления тега скрипта компонента lang="ts" он должен следоватьОфициальная документация Vueсказал использовать
defineComponentГлобальный метод определения компонентов.
Оптимизация компонентов
Далее начинаем сlogin.vueКомпоненты начинают рефакторинг, чтобы увидеть, какие оптимизации были сделаны.
- Создайте
typeпапка, созданная внутри папкиComponentDataType.ts, где находится спецификация типа, используемая в компоненте. - Создайте
enumПапка, в которую помещаются перечисления, используемые в компоненте.
Давайте рассмотрим первый пункт, чтобы унифицированно управлять типами, используемыми в компоненте.В качестве примера возьмем компонент входа.Нам нужноdataВозвращаемый объект указывает тип каждого из его свойств, поэтому мыComponentDataType.tsсоздать файл с именемloginDataTypeтип, а его код выглядит следующим образом.
export type loginDataType<T> = {
loginUndo: T; // 禁止登录时的图标
loginBtnNormal: T; // 登录时的按钮图标
loginBtnHover: T; // 鼠标悬浮时的登录图标
loginBtnDown: T; // 鼠标按下时的登录图标
userName: string; // 用户名
password: string; // 密码
confirmPassword: string; // 注册时的确认登录密码
isLoginStatus: number; // 登录状态:0.未登录 1.登录中 2.注册
loginStatusEnum: Object; // 登录状态枚举
isDefaultAvatar: boolean; // 头像是否为默认头像
avatarSrc: T; // 头像地址
loadText: string; // 加载层的文字
};
После объявления хорошего типа, можно использовать в сборке, код выглядит следующим образом:
import { loginDataType } from "@/type/ComponentDataType";
export default defineComponent({
data<T>(): loginDataType<T> {
return {
loginUndo: require("../assets/img/login/icon-enter-undo@2x.png"),
loginBtnNormal: require("../assets/img/login/icon-enter-undo@2x.png"),
loginBtnHover: require("../assets/img/login/icon-enter-hover@2x.png"),
loginBtnDown: require("../assets/img/login/icon-enter-down@2x.png"),
userName: "",
password: "",
confirmPassword: "",
isLoginStatus: 0,
loginStatusEnum: loginStatusEnum,
isDefaultAvatar: true,
avatarSrc: require("../assets/img/login/LoginWindow_BigDefaultHeadImage@2x.png"),
loadText: "上传中"
};
}
})
Полный адрес приведенного выше кода:
Затем давайте посмотрим на второй пункт, используя enum для оптимизации условного суждения внутри компонента. Например, isLoginStatus в приведенных выше данных имеет три состояния. Нам нужно делать разные вещи в соответствии с этими тремя состояниями. Непосредственное присвоение чисел, представляющих три состояния будет очень болезненно поддерживать позже.Если вы используете enum для его определения, вы можете сразу увидеть, что это за состояние в соответствии с семантикой.
Создаем в папке enumComponentEnum.tsфайл, все перечисления, используемые в компоненте, будут определены в этом файле, а затем созданы в компонентеloginStatusEnum, код показан ниже:
export enum loginStatusEnum {
NOT_LOGGED_IN = 0, // 未登录
LOGGING_IN = 1, // 登录中
REGISTERED = 2 // 注册
}
После объявления мы можем использовать его в компоненте, код выглядит следующим образом:
import { loginStatusEnum } from "@/enum/ComponentEnum";
export default defineComponent({
methods: {
stateSwitching: function(status) {
case "条件1":
this.isLoginStatus = loginStatusEnum.LOGGING_IN;
break;
case "条件2":
this.isLoginStatus = loginStatusEnum.NOT_LOGGED_IN;
break;
}
}
})
Полный адрес приведенного выше кода:
это указывает на
В процессе адаптации компонента это внутри метода не удалось хорошо идентифицировать, поэтому для его решения был использован очень тупой метод.
Следующим образом:
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");
}
onloadвнутри методаthisдолжен указывать на_imgДа, ноtsЯ так не думаю, и ошибка заключается в следующем.
Объекты, не включенные в этоwidthатрибут, решение состоит в том, чтобы заменить его на_img,задача решена.
Определение типа объекта Dom
При манипулировании объектом dom конкретный тип не может быть выведен ts, когда уровень устарел, как показано ниже:
sendMessage: function(event: KeyboardEvent) {
if (event.key === "Enter") {
// 阻止编辑框默认生成div事件
event.preventDefault();
let msgText = "";
// 获取输入框下的所有子元素
const allNodes = event.target.childNodes;
for (const item of allNodes) {
// 判断当前元素是否为img元素
if (item.nodeName === "IMG") {
if (item.alt === "") {
// 是图片
let base64Img = item.src;
// 删除base64图片的前缀
base64Img = base64Img.replace(/^data:image\/\w+;base64,/, "");
//随机文件名
const fileName = new Date().getTime() + "chatImg" + ".jpeg";
//将base64转换成file
const imgFile = this.convertBase64UrlToImgFile(
base64Img,
fileName,
"image/jpeg"
);
}
}
}
}
}
Вышеупомянутое является частью кода функции, которая отправляет сообщение. Окно сообщения содержит изображения и текст. Чтобы обрабатывать изображения по отдельности, нам нужно начать сtargetполучить все узлыchildNodes, затем пройдитесь по каждому узлу, чтобы получить его тип, тип дочерних узловNodeList, то каждый его элемент равенNodeтип, если текущий пройденный элементnodeNameсобственностьIMGКогда это изображение, мы получаем его атрибут alt для дальнейшей оценки, а затем получаем атрибут src.
Однако ts сообщит об ошибкеaltа такжеsrcАтрибут не существует, и ошибка следующая:
В этот момент нам нужноitemутверждается вHTMLImageElementТипы.
сложное определение типа
В процессе адаптации компонента встречается относительно сложное определение типа данных, и данные выглядят следующим образом:
data(){
return {
friendsList: [
{
groupName: "我",
totalPeople: 2,
onlineUsers: 2,
friendsData: [
{
username: "神奇的程序员",
avatarSrc:
"https://www.kaisir.cn/uploads/1ece3749801d4d45933ba8b31403c685touxiang.jpeg",
signature: "今天的努力只为未来",
onlineStatus: true,
userId: "c04618bab36146e3a9d3b411e7f9eb8f"
},
{
username: "admin",
avatarSrc:
"https://www.kaisir.cn/uploads/40ba319f75964c25a7370e3909d347c5admin.jpg",
signature: "",
onlineStatus: true,
userId: "32ee06c8380e479b9cd4097e170a6193"
}
]
},
{
groupName: "我的朋友",
totalPeople: 0,
onlineUsers: 0,
friendsData: []
},
{
groupName: "我的家人",
totalPeople: 0,
onlineUsers: 0,
friendsData: []
},
{
groupName: "我的同事",
totalPeople: 0,
onlineUsers: 0,
friendsData: []
}
]
};
},
Вот как я определил это в начале.
Со вложенными вместе думаю проблем нет.После занесения в код длина ошибки не совпадает, поэтому написание знаний определяет тип для первого объекта.
После некоторой помощи они сказали, что они должны быть написаны отдельно, а не вложенными определениями, как это, правильный способ написания выглядит следующим образом:
-
Типы определяются отдельно
// 联系人面板Data属性定义 export type contactListDataType<V> = { friendsList: Array<V>; }; // 联系人列表类型定义 export type friendsListType<V> = { groupName: string; // 分组名称 totalPeople: number; // 总人数 onlineUsers: number; // 在线人数 friendsData: Array<V>; // 好友列表 }; // 联系人类型定义 export type friendsDataType = { username: string; // 昵称 avatarSrc: string; // 头像地址 signature: string; // 个性签名 onlineStatus: boolean; // 在线状态 userId: string; // 用户id }; -
используемые компоненты
import { contactListDataType, friendsListType, friendsDataType } from "@/type/ComponentDataType"; data(): contactListDataType<friendsListType<friendsDataType>> { return { friendsList: [ { groupName: "我", totalPeople: 2, onlineUsers: 2, friendsData: [ { username: "神奇的程序员", avatarSrc: "https://www.kaisir.cn/uploads/1ece3749801d4d45933ba8b31403c685touxiang.jpeg", signature: "今天的努力只为未来", onlineStatus: true, userId: "c04618bab36146e3a9d3b411e7f9eb8f" }, { username: "admin", avatarSrc: "https://www.kaisir.cn/uploads/40ba319f75964c25a7370e3909d347c5admin.jpg", signature: "", onlineStatus: true, userId: "32ee06c8380e479b9cd4097e170a6193" } ] }, { groupName: "我的朋友", totalPeople: 0, onlineUsers: 0, friendsData: [] }, { groupName: "我的家人", totalPeople: 0, onlineUsers: 0, friendsData: [] }, { groupName: "我的同事", totalPeople: 0, onlineUsers: 0, friendsData: [] } ] }; }
Глубокое понимание использования дженериков машинописного текста, опыт++😄
атрибут тега удален
мы используемrouter-link, он будет отображаться как тег по умолчанию. Если вы хотите, чтобы он отображался как другие пользовательские теги, вы можете передатьtagсвойства изменить следующим образом:
<router-link :to="{ name: 'list' }" tag="div">
Однако вvue-routerВ новой версии официальные удалили атрибуты события и тега, поэтому мы не можем использовать его таким образом.Конечно, официальный документ также дает решение для использованияv-soltВ качестве альтернативы, в приведенном выше коде мы хотим отобразить его как div сv-soltзаписывается следующим образом:
<router-link :to="{ name: 'list' }" custom v-slot="{ navigate }">
<div
@click="navigate"
@keypress.enter="navigate"
role="link"
>
</div>
</router-link>
Для получения дополнительных пояснений по этой части, пожалуйста, перейдите к официальной документации:removal-of-event-and-tag-props-in-router-link
Компоненты не могут связывать файлы
Когда я представил страницу как компонент, я обнаружил, что vue3 не поддерживает внешнюю цепочку логического кода, как показано ниже, через внешнюю цепочку src.
<script lang="ts" src="../assets/ts/message-display.ts"></script>
ссылка в компоненте.
<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>
Затем он сообщил об ошибке, и тип не мог быть выведен.
Я перепробовал много методов и, наконец, обнаружил, что проблема заключалась в том, что я не мог передать внешнюю цепочку src, поэтому я написал код в файле ts в шаблоне vue, и ошибка исчезла.
должно быть утверждено с использованием as
Когда я переместил код в шаблон vue, он сообщил об очень странных ошибках, как показано ниже.imgContentVariables may have multiple types, TS cannot inform the specific type, at this time, we need to assert the assertion to him. I use a narrator's writing. He reports wrong. WebSTORM may not be very good. His error is very strange, as показано ниже
Сначала я был смущен, когда увидел эту ошибку. Друг сказал мне использовать метод исключения, аннотируйте код ближе всего к нему и посмотреть, сообщит ли он ошибку, поэтому я нашел источник проблемы, которая является Утверждение типа выше. Горшок, после его модификации, проблема решена.
Проблема решена, но я не могу понять, почему я должен использовать as, угловые скобки такие же, как у него, поэтому я пролистал официальную документацию.
Как говорится в официальной документации, включитеjsxТогда вы можете использовать только синтаксис as. Возможно, синтаксис шаблона vue3 включен по умолчанию.jsxда.
ref-массивы не создают массивы автоматически
В vue2, вv-forПри использовании атрибута ref соответствующий массив ref будет заполнен$refsСвойство, показанное ниже, является частью кода для списка друзей, который проходит черезfriendsList,БудуgroupArrowа такжеbuddyListв массив ссылок.
<template>
<div class="group-panel">
<div class="title-panel">
<p class="title">好友</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/tchat_his_arrow_right@2x.png" alt="左箭头"/>
</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 class="head-img-panel">
<img :src="list.avatarSrc" alt="用户头像">
</div>
<div class="nickname-panel">
<!--昵称-->
<div class="name-panel">
{{list.username}}
</div>
<!--签名-->
<div class="signature-panel">
[{{list.onlineStatus?"在线":"离线"}}]{{list.signature}}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
мы проходим$refsДоступ к соответствующим узлам можно получить, как показано ниже.
import lodash from 'lodash';
export default {
name: "contact-list",
methods:{
// 分组状态切换
groupingStatus:function (index) {
if(lodash.isEmpty(this.$route.params.userId)===false){
this.$router.push({name: "list"}).then();
}
// 获取transform的值
let transformVal = this.$refs.groupArrow[index].style.transform;
if(lodash.isEmpty(transformVal)===false){
// 截取rotate的值
transformVal = transformVal.substring(7,9);
// 判断是否展开
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{
// 第一次点击添加transform属性,旋转90度
this.$refs.groupArrow[index].style.transform = "rotate(90deg)";
this.$refs.buddyList[index].style.display = "block";
}
},
// 获取列表好友信息
getBuddyInfo:function (userId) {
// 判断当前路由params与当前点击项的userId是否相等
if(!lodash.isEqual(this.$route.params.userId,userId)){
this.$router.push({name: "dataPanel", params: {userId: userId}}).then();
}
}
}
}
Вышеприведенный способ написания не является проблемой в vue2, но в vue3 результат, который вы получаете, является ошибкой. Чиновники считают, что такое поведение станет неясным и неэффективным. Для решения этой проблемы используется новый синтаксис, и функция связана ref для обработки, как показано ниже.
<template>
<!---其它代码省略--->
<img :ref="setGroupArrow" src="../assets/img/list/tchat_his_arrow_right@2x.png" alt="左箭头" />
<!---其它代码省略--->
<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);
},
// 设置分组列表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();
}
// 获取transform的值
let transformVal = this.groupArrow[index].style.transform;
if (!_.isEmpty(transformVal)) {
// 截取rotate的值
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 {
// 第一次点击添加transform属性,旋转90度
this.groupArrow[index].style.transform = "rotate(90deg)";
this.groupList[index].style.display = "block";
}
}
)}
Пожалуйста, перейдите к полному коду:contact-list.vue
Для более подробного описания ссылки перейдите к официальному документу:Массив ссылок в v-for
Событие emit необходимо добавить проверку
Когда мы запускаем метод родительского компонента в дочернем компоненте, мы передаемthis.$emit("xx-xx-xx")Для запуска Vue3 рекомендует генерировать все события, генерируемые в компоненте, черезemitsДля записи, если вы не записываете через эмиты, вы увидите следующее предупреждение в консоли браузера.
Component emitted event "xx-xx-xx" but it is neither declared in the emits option nor as an "xx-xx-xx" prop.
Решение также очень простое, просто нужно добавить дочерний компонент, который вызывает родительский метод.emitsМожно проверить, как показано ниже, параметр оценивается заново, если условие выполнено, вернуть true, в противном случае вернуть false.
export default defineComponent({
emits: {
// vue3中建议对所有emit事件进行验证
"update-last-message": (val: string) => {
return !_.isEmpty(val);
}
}
}
адрес проекта
На этом проект можно запускать в обычном режиме, работа по рефакторингу завершена.vue-native-websocketЭтот плагин больше не работает в vue3. Сначала я думал, что было бы неплохо изменить способ написания макета строки прототипа, но я подумал, что это слишком просто.После изменения редактор не будет сообщать об ошибке, но он будет сообщать много ошибок при время выполнения. У меня не было выбора, кроме как сначала удалить часть кода, взаимодействующую с сервером.
Далее попробую рефакторитьvue-native-websocketЭтот плагин позволяет ему поддерживать vue3.
Наконец, поместите адрес рефакторинга кода проекта этой статьи:chat-system
напиши в конце
-
Если в статье есть ошибки, исправьте их в комментариях, если статья вам поможет, ставьте лайк и подписывайтесь 😊
-
Эта статья была впервые опубликована на Наггетс, перепечатка без разрешения запрещена 💌