Сводка обзора Vue3 + TypeScript

Vue.js
Сводка обзора Vue3 + TypeScript

задний план

В последнее время разрабатывается система управления IoT-устройствами, основной целью которой является комплексное управление и контроль аппаратных устройств в смарт-парке под ответственность компании через облачную платформу IoT.

Поскольку этот продукт является экспериментальным проектом, нет контракта и явной выгоды. Так что ресурсов очень мало.

Конкретным продуктом занимается всего 1,5 человека, почти один я. Поэтому необходимо быть продакт-менеджером, разработчиком и эксплуатацией и обслуживанием. Однако с технической точки зрения выбор может быть более свободным.

Вся система разделена на 4 слоя с точки зрения архитектуры. Снизу вверх расположены аппаратное обеспечение оборудования, шлюз доступа к оборудованию, платформа IoT и система управления оборудованием. За исключением аппаратного обеспечения устройства, остальные три уровня относятся к категории программного обеспечения.

В этой статье в основном зафиксированы некоторые мои выводы в процессе разработки последнего слоя — front-end разработки системы управления устройствами.

Внешний интерфейс разработан с использованием Vite2.x, Vue3.x, Vuex4.x, VueRouter4.x, TypeScript и Element-Plus. Как видите, версии, используемые этими фреймворками и библиотеками, более агрессивны, большинство из них — последние версии, а также rc и бета-версии. Однако с начала проекта и до написания этого резюме версии некоторых библиотек уже не актуальны, и я должен чувствовать, что фронтенд-технологии быстро меняются.

Думая о компоненте

Давайте сначала посмотрим на компонент.

水波纹.gif

Это небольшая точка с волнистым эффектом, указывающая на текущий статус подключения к веб-сокету. очень простой чистый компонент представления. Эффекты стилей реализованы с использованием переменных css3, анимации и псевдоклассов до и после.

Дизайн реквизита очень простой, есть только одно поле типа. Цвет ряби отличается в зависимости от типа поля.

С идеей, ниже приведены некоторые детали реализации.

Как объявить тип с полем с именем enum?

По замыслу поле типа должно быть значением перечисления и не должно произвольно задаваться вызывающей стороной.

Ниже приведено объявление перечисления для Type с 6 полями.

enum Type {
  primary = "primary",
  success = "success",
  warning = "warning",
  warn = "warn", // warning alias
  danger = "danger",
  info = "info",
}

В TypeScript есть два ключевых слова для объявления типов, интерфейс и тип, которые немного отличаются при объявлении полей, ключи которых имеют неопределенный тип.

Используйте тип для объявления:

type ColorConfig = {
  [key in Type]: Colors;
};

Использовать интерфейс можно только так:

interface ColorConfig {
  [key: string]: Colors;
}

Поскольку индексы интерфейсов могут быть только примитивными типами, псевдонимы типов также не могут использоваться. И индекс типа может быть составным типом.

Как Vue 3 получает экземпляр элемента?

В vue3 логика компонента может быть помещена в функцию настройки, но this в настройке больше нет, поэтому использование this.$refs в vue2 нельзя использовать в vue3.

Новое использование:

  1. Добавьте атрибут ref к элементу.
  2. В настройках объявлена ​​переменная с тем же именем, что и у элемента ref.
  3. Верните переменную ref как свойство с тем же именем в возвращаемом объекте установки.
  4. Доступ к переменной ref во время жизненного цикла onMounted, который является экземпляром элемента.

первый шаг:

<div class="point point-flicker" ref="point"></div>

Шаг 2:

const point = ref<HTMLDivElement | null>(null);

Обратите внимание, что тип должен быть указан в HTMLDivElement, чтобы вы могли наслаждаться выводом типа.

третий шаг:

return { point };

Этот шаг очень важен.Если возвращенный объект не содержит этого свойства с тем же именем, объект ref, доступный в onMounted, будет нулевым.

четвертый шаг:

onMounted(() => {
  if (point?.value) {
    // logic
  }
});

Как манипулировать псевдоклассами?

JavaScript не может получить элементы псевдокласса, но вы можете передумать. Стиль псевдокласса ссылается на переменную css, а затем управляет переменной css через js, чтобы завершить эффект косвенного управления псевдоклассом.

Например, это псевдокласс:

.point-flicker:after {
  background-color: var(--afterBg);
}

Он зависит от переменной afterBg.

Если вам нужно изменить его содержимое, просто используйте js для управления содержимым afterBg.

point.value.style.setProperty("--bg", colorConfig[props.type].bg);

Изменения API

Как компоненты изменяют свои собственные реквизиты в Vue3?

Существует не очень распространенная ситуация, когда компоненту необходимо изменить реквизиты, переданные родительским компонентом, на свои собственные.

Например, сборка ящика, мимикрия сборки рамы.

Обычно в vue2 используются синхронизация и v-модель.

Только v-model:xxx="" рекомендуется в vue3.

Например, родительский компонент передает:

<ws-log v-model="wsLogVisible" />

Сборка:

<template>
    <div v-model:visible="visible">
    ...
    </div>
</template>

<script>
// ...
 props: {
    visible: {
      type: Boolean,
    },
  },
</script>

Изменения в использовании часов в Vue3

смотреть стало легче.

import { watch } from "vue";

watch(source, (currentValue, oldValue) => {
    // logic
});

При изменении источника автоматически выполняется функция, переданная вторым параметром watch.

Изменения в использовании вычислений в Vue3

вычисляется также проще.

import { computed } from "vue"

const v = computed(() => {
    return x
});

Переменная, возвращаемая вычислением, является реактивным объектом.

Трюк с зацикливанием компонентов в Vue3

Это метод разработки компонентов.

Предположим, у вас есть древовидные данные неопределенной глубины.

{
  "label": "root",
  "children": [
    {
      "label": "a",
      "children": [
        {
          "label": "a1",
          "children": []
        },
        {
          "label": "a2",
          "children": []
        }
      ]
    }
  ]
}

Его тип определяется следующим образом:

export interface Menu {
  id: string;
  label: string;
  children: Menu | null;
}

Вам нужно реализовать своего рода древовидный компонент для их рендеринга. Вот где нужна эта техника.

<template>
    <div>{{ menu.label }}</div>
    <Menu
      @select="select"
      v-for="item in menu.children"
      :key="item.id"
      :menu="item"
    />
</template>

<script  lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  name: "Menu",
  props: {
    menu: {
      type: Object,
    },
  },
});
</script>

Имя компонента может использоваться непосредственно внутри него без необходимости объявлять его в компоненте.

некоторые ямы

Vuex: используйте Карты с осторожностью

В Vuex я разработал структуру данных для хранения различных состояний модулей (бизнес-концепций).

type Code = number;
export type ModuleState = Map<Code, StateProperty>;

Но я обнаружил проблему: когда я изменяю свойство в значении на карте, мониторинг Vuex не запускается.

Поэтому мне пришлось преобразовать структуру данных в форму объектов.

export type ModuleState = { [key in Code]: StateProperty };

Индексы в ts не могут использовать псевдонимы типов, но могут быть записаны следующим образом:

type Code = number;
export type ModuleState = { [key in Code]: StateProperty };

Кроме того, есть еще одна проблема с Map.

Когда прокси-объект типа Map передается в качестве параметра, такие методы Map, как get, set и clear, использовать нельзя, но TypeScript подскажет, что эти методы доступны. Если вы используете эти методы, вы получите Uncaught TypeError.

Эта проблема не возникает, если вы используете Object.

Исключения WebSocket не могут быть прослушаны с помощью try catch

Исключение ws может быть обработано только в двух событиях, onerror и onclose, а try catch не может быть перехвачен.

Иногда onerror и onclose будут выполняться непрерывно, например, запуск onerror, вызывающий закрытие соединения, а затем запуск onclose.

Vue Devtools

vue devtools в настоящее время не поддерживает Vue3, но vue devtools является почти незаменимым инструментом в разработке.В настоящее время доступна бета-версия vue devtools, но есть некоторые ошибки.

ссылка для скачивания

Использование очень простое, просто перезапустите браузер после установки. Настройка не требуетсяvue.config.devtools = true, атрибут devtools не существует в экземпляре vue.config в vue3.

Зависимости установки ESbuild

Очень легко столкнуться с ошибкой при установке зависимостей при использовании vite для запуска службы.

Error: EBUSY: resource busy or locked, open 'E:\gxt\property-relay-fed\node_modules\esbuild\esbuild.exe'

Причина этой проблемы в том, что инструмент компиляции esbuild.exe, от которого зависит vite, занят.Решение очень простое, то есть остановить vite и перезапустить vite после установки зависимостей.

Проблемы с отладкой Vite в Chrome

В системе есть несколько мобильных страниц, которые необходимо встроить в приложение.

Существует два распространенных способа отладки WebView: простой способ — использовать vcosnole с открытым исходным кодом от Tencent, а еще один, более сложный способ отладки — использовать Chrome DevTools.

Но vconsole не так прост в использовании, как ожидалось.

image.png

Поэтому я решил использовать Chrome для отладки, chrome://inspect/#devices

Однако в процессе отладки я обнаружил, что исходный код TS на самом деле работает в инструменте отладки Chrome, а синтаксис TS напрямую расценивается как синтаксическая ошибка. (Я использую Vite для запуска службы разработки.)

Решение простое, но довольно низкое. Сначала используйте vite build для компиляции кода TS в JS, а затем используйте предварительный просмотр vite для запуска службы.

WebSocket

websocket не имеет ничего общего с Vue3, но кратко упоминается здесь.

Основной концепцией системы управления устройствами является устройство, а устройство имеет множество атрибутов, которые в аппаратном обеспечении также называются точками данных. Эти свойства перемещаются по очень длинным ссылкам на пользовательский интерфейс. Общий процесс выглядит примерно следующим образом: аппаратное обеспечение загружается в шлюз доступа по протоколу tcp, шлюз доступа обрабатывается и затем загружается на платформу IoT через протокол mqtt, затем платформа IoT обрабатывается механизмом правил и отправляется к бизнес-системе в виде веб-перехватчика restful, а затем система передается на внешний интерфейс через веб-сокет.

Хотя данные предоставляются пользователю через уровни кодирования и декодирования и различные протоколы на очень большом расстоянии, интерфейсу нужно заботиться только о веб-сокетах.

переподключение WebSocket

При переподключении нужно обратить внимание на непрерывное выполнение onerror и onclose, что обычно решается с помощью метода, похожего на антишейк.

Мой подход заключается в добавлении переменной для управления количеством повторных подключений.

let connecting = false; // 断开连接后,先触发 onerror,再触发 onclose,主要用于防止重复触发
  conn();
  function conn() {
    connecting = false;
    if (ctx.state.stateWS.instance && ctx.state.stateWS.instance.close) {
      ctx.state.stateWS.instance.close();
    }
    const url = ctx.state.stateWS.url + "?Authorization=" + getAuthtication();
    ctx.state.stateWS.instance = new WebSocket(url);
    ctx.state.stateWS.instance.onopen = () => {
      ctx.commit(ActionType.SUCCESS);
    };
    ctx.state.stateWS.instance.onclose = () => {
      if (connecting) return;
      ctx.commit(ActionType.CLOSE);
      setTimeout(() => {
        conn();
      }, 10 * 1000);
      connecting = true;
    };
    ctx.state.stateWS.instance.onerror = () => {
      if (connecting) return;
      ctx.commit(ActionType.ERROR);
      setTimeout(() => {
        conn();
      }, 10 * 1000);
      connecting = true;
    };
    ctx.state.stateWS.instance.onmessage = function (
      this: WebSocket,
      ev: MessageEvent
    ) {
      // logic
      } catch (e) {
        console.log("e:", e);
      }
    };
  }

Журнал активности подключения WebSocket

Система рассчитана на круглосуточную работу. Поэтому websocket легко отключается из-за каких-то сетевых факторов или других факторов.Переподключение - очень важная функция, и он также должен иметь функцию журнала переподключений.

В разных средах пользователей очень хлопотно проверять состояние соединения WebSocket.Хорошим решением является добавление функции журнала соединений, чтобы вы могли видеть состояние соединения в разное время.

image.png

Следует отметить, что эти журналы хранятся в памяти браузера пользователя, и необходимо установить верхний предел.При достижении верхнего предела ранние журналы будут автоматически очищены.

WebSocket-аутентификация

Аутентификация веб-сокета — это момент, который многие люди склонны игнорировать.

В моем дизайне системы аутентификация restful API достигается путем присоединения поля авторизации к заголовку запроса и установки сгенерированного JWT.

Websocket не может установить заголовок, но может установить запрос для реализации схемы аутентификации, аналогичной restful.

Что касается истечения срока действия, обновления, разрешений и других вопросов аутентификации ws, это может соответствовать restful.

настройка скрипта: более чистый API

настройка скрипта все еще является экспериментальной функцией, но она действительно освежает.

Общее использование установки для однофайлового компонента выглядит следующим образом:

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  setup () {
    return {}
  }
})
</script>

После настройки скрипта код становится таким:

<script setup lang="ts">
  
</script>

Переменные верхнего уровня и функции в теге scirt будут возвращены.

В этом режиме сокращается объем кода, что может повысить эффективность разработки и снизить умственную нагрузку.

Но в настоящее время также есть несколько вопросов, например, как использовать жизненный цикл и функции наблюдения/вычисления в настройке скрипта? Как использовать компоненты? Как получить реквизит и контекст?

использовать компоненты

После прямого импорта компонента vue автоматически распознает его без использования монтирования компонента.

<script setup lang="ts">
  import C from "component"
</script>

Вычисление функций с использованием жизненного цикла и прослушивания

Это в основном то же самое, что и стандартная нотация.

<script setup lang="ts">
  import { watch, computed, onMounted } from "vue"
</script>

Использование реквизита и контекста

Поскольку setup повышен до тега script, нет возможности получить два параметра props и context.

Таким образом, vue предоставляет функции defineProps, defineEmit и useContext.

defineProps

Использование defineProps почти такое же, как использование реквизита в OptionsAPI.

<script setup lang="ts">
import { defineProps } from "vue";

interface Props {
  moduleID: string;
}

const props = defineProps<Props>(["moduleID"]);
console.log(props.moduleID);
</script>

defineEmit

Использование defineEmit почти такое же, как и emit в OptionsAPI.

<script setup lang="ts">
import { defineEmit } from "vue";

const emit = defineEmit(["select"]);
console.log(emit("select"));
</script>

Первым параметром emit является имя события, за которым следует неопределенное количество параметров.

useContext

useContext — это функция-ловушка, которая возвращает объект контекста.

const ctx = useContext()

принцип

Принцип довольно прост. Добавлен слой процесса компиляции для компиляции настроек скрипта в код стандартного режима.

Однако в реализации много деталей, поэтому официальная версия еще не запущена.

Модульный метод разработки, представленный Vue3 Composition

Самое глубокое чувство, которое вызывает у меня этот стек технологий, — это изменение пути развития.

При разработке Vue2 API-интерфейсу Options очень сложно работать со страницами со сложной бизнес-логикой. Отслеживание изменений в переменной может быть головной болью, когда логика состоит из тысяч строк.

Но с Composition API это больше не проблема, он предлагает совершенно новый способ разработки, и в нем есть ощущение React, но он намного лучше, чем раньше!

Все страницы в этом проекте разработаны с использованием хуков.

В модуле устройства мой js-код выглядит так.

<script lang="ts">
import { defineComponent, toRefs } from "vue";
import { useDeviceCreate } from "./create";
import { useDeviceQuery } from "./query";
import { useDeviceDelete } from "./delete";
import { useUnbind } from "./unbind";
import { useBind } from "./bind";
import { useDeviceEdit } from "./edit";
import { useState } from "./state";
import { useAssign } from "./assign";

export default defineComponent({
  setup() {
    const queryObj = useDeviceQuery();
    const { query, devices } = queryObj;
    const reload = query;
    return {
      ...toRefs(useDeviceCreate(reload)),
      ...toRefs(queryObj),
      ...toRefs(useDeviceDelete(reload)),
      ...toRefs(useUnbind(reload)),
      ...toRefs(useBind(reload)),
      ...toRefs(useDeviceEdit(reload)),
      ...toRefs(useState(devices)),
      ...toRefs(useAssign()),
    };
  },
});
</script>

Каждый модуль выполняет свои обязанности и имеет свои внутренние данные.Если каждому модулю необходимо обмениваться данными, их можно передать через Vuex или в настройках компонента верхнего уровня, например, в функции перезагрузки выше.

Моя структура каталогов такая.

image.png

В целом очень освежает, и ощущение инженерии становится все сильнее и сильнее.

Фронтенд-архитектура отличается от бэкенд-архитектуры.

Серверная часть больше связана с высокой доступностью, высокой производительностью и масштабируемостью. Внешний интерфейс больше касается того, как добиться многоуровневого дизайна с высокой связностью и низкой связанностью.Архитектура — это дизайн.

Хороший архитектурный проект может значительно повысить эффективность разработки и снизить умственную нагрузку разработчиков.

Это то, о чем мы всегда беспокоились.