Подготовка к 2021 году: лучшие практики проекта Vite2

Vue.js
Подготовка к 2021 году: лучшие практики проекта Vite2

vit2 здесь

Vite1Еще не пользовался,Vite2был обновлен, с новой архитектурой плагинов, удобным опытом разработки иVue3идеальное сочетание. Первая пуля в 2021 году, староста деревни планирует начать всеобщее обучение с темы Vite2 + Vue3.

В 2021 году правильно выучить вите первым

Цель этой статьи

  • vite2анализ изменений
  • Общие задачи в проектеvite2+vue3упражняться

Создайте проект Vite2

Излишне говорить сплетни, давайте перечислим герояvite2

Используйте NPM:

$ npm init @vitejs/app

Укажите имя проекта и шаблон по запросу или напрямую

$ npm init @vitejs/app my-vue-app --template vue

Основные изменения в Vite2

Я отметил все те, которые оказали большее влияние на наши предыдущие проекты:

  • Изменения параметров конфигурации:vue特有选项, параметры создания, параметры css, параметры jsx и т. д.
  • 别名行为变化: больше не требуется/начать или закончить
  • Vue支持:пройти через@vitejs/plugin-vueПоддержка плагинов
  • Реагировать на поддержку
  • Изменения API HMR
  • Изменения формата списка
  • 插件API重新设计

Поддержка Vue

Интеграция Vue также достигается с помощью плагинов, как и в других фреймворках:

В определении SFC используется значение по умолчанию.setup script, синтаксис радикальнее, но лаконичнее, хвала!

Определение псевдонима

больше не нужно что-то вродеvite1Добавьте то же самое до и после псевдонима/,с участиемwebpackКонфигурация проекта может быть последовательной и легко трансплантируемой, хвала!

import path from 'path'

export default {
  alias: {
    "@": path.resolve(__dirname, "src"),
    "comps": path.resolve(__dirname, "src/components"),
  },
}

App.vueПопробуйте внутри

<script setup>
import HelloWorld from 'comps/HelloWorld.vue'
</script>

Редизайн API плагина

Vite2Основное изменение коснулось системы подключаемых модулей, которая стала более стандартизированной и легко расширяемой.Vite2API плагина расширяется отRollupподключаемая система, поэтому она совместима с существующимиRollupПлагины, написанные плагины Vite, также могут работать в разработке и создании одновременно, хвала!

Я открою специальную дискуссию о написании плагинов, и вы можете обратить на меня внимание.

Поддержка Vue3 JSX

vue3серединаjsxПоддержка требует внедрения плагинов:@vitejs/plugin-vue-jsx

$ npm i @vitejs/plugin-vue-jsx -D

зарегистрировать плагин,vite.config.js

import vueJsx from "@vitejs/plugin-vue-jsx";

export default {
  plugins: [vue(), vueJsx()],
}

Есть также требования для использования, реформировать егоApp.vue

<!-- 1.标记为jsx -->
<script setup lang="jsx">
import { defineComponent } from "vue";
import HelloWorld from "comps/HelloWorld.vue";
import logo from "./assets/logo.png"

// 2.用defineComponent定义组件且要导出
export default defineComponent({
  render: () => (
    <>
      <img alt="Vue logo" src={logo} />
      <HelloWorld msg="Hello Vue 3 + Vite" />
    </>
  ),
});
</script>
Мок-плагин

я познакомил тебя раньшеvite-plugin-mockРефакторинг для поддержки Vite2.

Установить плагин

npm i mockjs -S
npm i vite-plugin-mock cross-env -D

настроить,vite.config.js

import { viteMockServe } from 'vite-plugin-mock'

export default {
  plugins: [ viteMockServe({ supportTs: false }) ]
}

установить переменные окружения,package.json

{
  "scripts": {
    "dev": "cross-env NODE_ENV=development vite",
    "build": "vite build"
  },
} 

Инфраструктура проекта

маршрутизация

Установитьvue-router 4.x

npm i vue-router@next -S

настройка маршрутизации,router/index.js

import { createRouter, createWebHashHistory } from 'vue-router';

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    { path: '/', component: () => import('views/home.vue') }
  ]
});

export default router

импорт,main.js

import router from "@/router";
createApp(App).use(router).mount("#app");

не забудьте создатьhome.vueи изменитьApp.vue

Использование маршрутизации немного изменилось,видеоурок

государственное управление

Установитьvuex 4.x

npm i vuex@next -S
image

Конфигурация магазина,store/index.js

import {createStore} from 'vuex';

export const store = createStore({
  state: {
    couter: 0
  }
});

импорт,main.js

import store from "@/store";
createApp(App).use(store).mount("#app");

Использование в основном такое же, как и раньше,видеоурок

организация стиля

установить sass

npm i sass -D

stylesКаталог содержит различные стили

截屏2020-12-24 上午11.51.30

index.scssОрганизуйте эти стили как экспорт при написании некоторых глобальных стилей.

image-20201224115414266

Наконец вmain.jsимпорт

import "styles/index.scss";

обрати внимание наvite.config.jsДобавить кstylesпсевдоним

библиотека пользовательского интерфейса

просто используйте насКоманда Хуагошаньсобственныйelement3.

китайский документ

Установить

npm i element3 -S

полный импорт,main.js

import element3 from "element3";
import "element3/lib/theme-chalk/index.css";

createApp(App).use(element3)

импорт по требованию,main.js

import "element3/lib/theme-chalk/button.css";
import { ElButton } from "element3"
createApp(App).use(ElButton)

Было бы лучше извлечь его как плагин.plugins/element3.js

// 完整引入
import element3 from "element3";
import "element3/lib/theme-chalk/index.css";

// 按需引入
// import { ElButton } from "element3";
// import "element3/lib/theme-chalk/button.css";

export default function (app) {
  // 完整引入
  app.use(element3)

  // 按需引入
  // app.use(ElButton);
}

тестовое задание

<el-button>my button</el-button>

Базовая компоновка

Нашему приложению нужна базовая страница макета, как показано на рисунке ниже.В будущем каждая страница может взять страницу макета в качестве родительской страницы:

image-20201223143247535

страница макета,layout/index.vue

<template>
  <div class="app-wrapper">
    <!-- 侧边栏 -->
    <div class="sidebar-container"></div>
    <!-- 内容容器 -->
    <div class="main-container">
      <!-- 顶部导航栏 -->
      <navbar />
      <!-- 内容区 -->
      <app-main />
    </div>
  </div>
</template>

<script setup>
import AppMain from "./components/AppMain.vue";
import Navbar from "./components/Navbar.vue";
</script>

<style lang="scss" scoped>
@import "../styles/mixin.scss";

.app-wrapper {
  @include clearfix;
  position: relative;
  height: 100%;
  width: 100%;
}
</style>

не забудьте создатьAppMain.vueа такжеNavbar.vue

настройка маршрутизации,router/index.js

{
  path: "/",
	component: Layout,
  children: [
    {
      path: "",
      component: () => import('views/home.vue'),
      name: "Home",
      meta: { title: "首页", icon: "el-icon-s-home" },
    },
  ],
},

Динамическая навигация

боковая навигация

Динамически создавать боковые навигационные меню на основе таблиц маршрутизации.

image-20201225180300250

Сначала создайте компонент боковой панели, рекурсивно выведитеroutesКонфигурация представляет собой многоуровневое меню,layout/Sidebar/index.vue

<template>
  <el-scrollbar wrap-class="scrollbar-wrapper">
    <el-menu
      :default-active="activeMenu"
      :background-color="variables.menuBg"
      :text-color="variables.menuText"
      :unique-opened="false"
      :active-text-color="variables.menuActiveText"
      mode="vertical"
    >
      <sidebar-item
        v-for="route in routes"
        :key="route.path"
        :item="route"
        :base-path="route.path"
      />
    </el-menu>
  </el-scrollbar>
</template>

<script setup>
import SidebarItem from "./SidebarItem.vue";
import { computed } from "vue";
import { useRoute } from "vue-router";
import { routes } from "@/router";
import variables from "styles/variables.module.scss";

const activeMenu = computed(() => {
  const route = useRoute();
  const { meta, path } = route;
  if (meta.activeMenu) {
    return meta.activeMenu;
  }
  return path;
});
</script>

Уведомление:sassМногомерный анализ экспорта файлов необходимо использоватьcss module,следовательноvariablesфайл для добавленияmoduleинфикс.

Добавьте связанные стили:

  • styles/variables.module.scss
  • styles/sidebar.scss
  • styles/index.scssвведен в

СоздайтеSidebarItem.vueКомпонент, определяет, является ли текущий маршрут навигационной ссылкой или родительским меню:

image-20201229123955087

Панировочные сухари

Хлебные крошки могут быть динамически сгенерированы с помощью массивов сопоставления маршрутов.

компонент панировочных сухарей,layouts/components/Breadcrumb.vue

<template>
  <el-breadcrumb class="app-breadcrumb" separator="/">
      <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
        <span
          v-if="item.redirect === 'noRedirect' || index == levelList.length - 1"
          class="no-redirect"
          >{{ item.meta.title }}</span>
        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
      </el-breadcrumb-item>
  </el-breadcrumb>
</template>

<script setup>
import { compile } from "path-to-regexp";
import { reactive, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";

const levelList = ref(null);
const router = useRouter();
const route = useRoute();

const getBreadcrumb = () => {
  let matched = route.matched.filter((item) => item.meta && item.meta.title);

  const first = matched[0];
  if (first.path !== "/") {
    matched = [{ path: "/home", meta: { title: "首页" } }].concat(matched);
  }

  levelList.value = matched.filter(
    (item) => item.meta && item.meta.title && item.meta.breadcrumb !== false
  );
}

const pathCompile = (path) => {  
  var toPath = compile(path);
  return toPath(route.params);
}

const handleLink = (item) => {
  const { redirect, path } = item;
  if (redirect) {
    router.push(redirect);
    return;
  }
  router.push(pathCompile(path));
}

getBreadcrumb();
watch(route, getBreadcrumb)

</script>

<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
  display: inline-block;
  font-size: 14px;
  line-height: 50px;
  margin-left: 8px;

  .no-redirect {
    color: #97a8be;
    cursor: text;
  }
}
</style>

Не забудьте добавить зависимости:path-to-regexp

Уведомление:vue-router4больше не используетсяpath-to-regexpРазбирать динамическиpath, поэтому здесь необходимы дальнейшие улучшения.

инкапсуляция данных

Унифицированная инкапсуляция сервисов запросов данных полезна для решения следующих проблем:

  • Единый запрос конфигурации
  • Единая обработка запросов и ответов

Готов к работе:

  • Установитьaxios:

    npm i axios -S
    
  • Добавить файл конфигурации:.env.development

    VITE_BASE_API=/api
    

инкапсуляция запроса,utils/request.js

import axios from "axios";
import { Message, Msgbox } from "element3";

// 创建axios实例
const service = axios.create({
  // 在请求地址前面加上baseURL
  baseURL: import.meta.env.VITE_BASE_API,
  // 当发送跨域请求时携带cookie
  // withCredentials: true,
  timeout: 5000,
});

// 请求拦截
service.interceptors.request.use(
  (config) => {
    // 模拟指定请求令牌
    config.headers["X-Token"] = "my token";
    return config;
  },
  (error) => {
    // 请求错误的统一处理
    console.log(error); // for debug
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  /**
   * 通过判断状态码统一处理响应,根据情况修改
   * 同时也可以通过HTTP状态码判断请求结果
   */
  (response) => {
    const res = response.data;

    // 如果状态码不是20000则认为有错误
    if (res.code !== 20000) {
      Message.error({
        message: res.message || "Error",
        duration: 5 * 1000,
      });

      // 50008: 非法令牌; 50012: 其他客户端已登入; 50014: 令牌过期;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // 重新登录
        Msgbox.confirm("您已登出, 请重新登录", "确认", {
          confirmButtonText: "重新登录",
          cancelButtonText: "取消",
          type: "warning",
        }).then(() => {
          store.dispatch("user/resetToken").then(() => {
            location.reload();
          });
        });
      }
      return Promise.reject(new Error(res.message || "Error"));
    } else {
      return res;
    }
  },
  (error) => {
    console.log("err" + error); // for debug
    Message({
      message: error.message,
      type: "error",
      duration: 5 * 1000,
    });
    return Promise.reject(error);
  }
);

export default service;

обработка бизнеса

Представление структурированных данных

использоватьel-tableОтображать структурированные данные, сотрудничать сel-paginationВыполните подкачку данных.

image-20210201110626262

Организационная структура документа выглядит следующим образом:list.vueпоказать список,edit.vueа такжеcreate.vueРедактировать или создавать, повторно использовать внутриdetail.vueиметь дело с,modelОтвечает за бизнес-обработку данных.

image-20210201110542893

list.vueотображение данных в

<el-table v-loading="loading" :data="list">
  <el-table-column label="ID" prop="id"></el-table-column>
  <el-table-column label="账户名" prop="name"></el-table-column>
  <el-table-column label="年龄" prop="age"></el-table-column>
</el-table>

listа такжеloadingЛогика сбора данных, вы можете использоватьcompsition-apiизвлечь вuserModel.js

export function useList() {
  // 列表数据
  const state = reactive({
    loading: true, // 加载状态
    list: [], // 列表数据
  });

  // 获取列表
  function getList() {
    state.loading = true;
    return request({
      url: "/getUsers",
      method: "get",
    }).then(({ data, total }) => {
      // 设置列表数据
      state.list = data;
    }).finally(() => {
      state.loading = false;
    });
  }
  
  // 首次获取数据
  getList();

  return { state, getList };
}

list.vueиспользуется в

import { useList } from "./model/userModel";
const { state, getList } = useList();

обработка пагинации,list.vue

<pagination
      :total="total"
      v-model:page="listQuery.page"
      v-model:limit="listQuery.limit"
      @pagination="getList"
    ></pagination>

данные такжеuserModelобработка

const state = reactive({
  total: 0,   // 总条数
  listQuery: {// 分页查询参数
    page: 1,  // 当前页码
    limit: 5, // 每页条数
  },
});
request({
  url: "/getUsers",
  method: "get",
  params: state.listQuery, // 在查询中加入分页参数
})
обработка формы

Добавление, редактирование и использование пользовательских данныхel-formиметь дело с

доступен один компонентdetail.vueДля обработки разница только в том, заливается ли информация обратно в форму при инициализации.

<el-form ref="form" :model="model" :rules="rules">
  <el-form-item prop="name" label="用户名">
    <el-input v-model="model.name"></el-input>
  </el-form-item>
  <el-form-item prop="age" label="用户年龄">
    <el-input v-model.number="model.age"></el-input>
  </el-form-item>
  <el-form-item>
    <el-button @click="submitForm" type="primary">提交</el-button>
  </el-form-item>
</el-form>

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

export function useItem(isEdit, id) {
  const model = ref(Object.assign({}, defaultData));

  // 初始化时,根据isEdit判定是否需要获取详情
  onMounted(() => {
    if (isEdit && id) {
      // 获取详情
      request({
        url: "/getUser",
        method: "get",
        params: { id },
      }).then(({ data }) => {
        model.value = data;
      });
    }
  });
  return { model };
}

Поддерживающая демонстрация видео

Я специально записал набор видеороликов, чтобы продемонстрировать все операции, проделанные в этой статье, и маленьких друзей, которые любят смотреть обучающее видео движение:«Подготовка к 2021 году» Лучшие практики проекта Vite2 + Vue3

Это не легко сделать, попросите один3连,关注Не переусердствуйте! ?

Поддержка раздаточных материалов с исходным кодом

Добро пожаловать, чтобы обратить внимание на публичный аккаунт «Village Head School Front-end», чтобы забрать

Последующий творческий план

Самым важным обновлением vite2 является система плагинов, я планирую сделать это позже, включая, но не ограничиваясь следующим:

  • Разбор рабочего механизма vite2
  • Анализ механизма плагина vit2
  • Практика написания плагина Vite2

Все, ставьте лайки и добавляйте в закладки для дальнейшего изучения.

поддержать старосту села

Это про практику проекта vite2.Этот контент кидал уже давно.Я наступил на много ям и потерял несколько волосков.Друзья,пожалуйста, лайкните и поддержите меня,окей?

Мои последние статьи (спасибо моим друзьям за поддержку и поддержку 🌹🌹🌹):