vue3工程化开发入门

81 阅读7分钟

环境准备

  • install node, reference: nodejs.org;
    若下载的是 zip,还需设置环境变量,即将 node 根目录(node.exe 和 npm 命令所在处)添加到 path
 node -v; npm -v;
  • 可选:全局安装npm替代品,yarn,pnpm;会安装到npm命令所在目录
# 全局安装 pnpm
npm install -g pnpm
pnpm -v
# 全局安装 yarn
npm i -g yarn
yarn -v

使用vue-cli工具(基于webpack构建工具)创建vue基础范例+webpack基础配置项目,

  • global install vue cli:
    • npm i -g @vue/cli 或 yarn global add @vue/cli
  • vue --version
  • vue create vue3proj(项目名不能包含中文,和大写)
  • npm run serve 或 yarn serve

使用create-vue工具(基于vite构建工具,新一代构建工具)创建vue基础范例+vite基础配置项目,

# 查看版本
node -v (must>=16)
npm -v
# create a new vue app:
npm init vue@latest # will install and execute create-vue
# Project Setup
yarn install | npm install
# Compile and Hot-Reload for Development
yarn dev | npm run dev
# Compile and Minify for Production
npm run build
npm run preview

plugins

vue-router

创建路由

/*
* 注意
* name重复的路由只会注册靠后的
* path重复的路由,都会注册,但是匹配靠前的
* */

import HomeView from "@/a-main-app/home/HomeView.vue";

const routes = {
    // to属性与path匹配的RouterLink会加上类:router-link-active router-link-exact-active
    // 而且此时to属性与其父级路由path匹配的RouterLink会加上类router-link-active
    path: '/home',
    // 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('@/a-layout/homeLayout/HomeLayout.vue'),
    redirect: '/',
    //如果父级路由设置component,访问子路由会先在app.vue的RouterView(路由出口)中显示该组件,然后再在该组件的RouterView显示子组件,若该组件没有RouterView,子组件应该显示的位置会空白
    //若未设置component,访问子路由就直接在app.vue的RouterView中显示子组件
    // component: () => import('@/components/views/DemoView.vue'),
    meta: {
        title: 'Q享-齐分享,同乐趣,共进步!', // 设置该路由在侧边栏和面包屑中展示的名字
        // icon: 'el-icon-s-help' // 设置该路由的图标,对应路径src/assets/icons/svg
        // hidden: true, // 如果设置为true,则不会在侧边栏出现
        // noCache: true // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false)
        // breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示
        // affix: true // 如果设置为true,则标签将永远显示在左侧
        // activeMenu: '/demo/note' // 如果设置路径,侧边栏将突出显示您设置的路径
        // roles: ['admin','editor'] // 设置该路由进入的权限,支持多个权限叠加
    },
    children: [
        {
            path: '/',
            name: 'home',
            component: HomeView,
            meta: {
                title: 'Q享-菜单',
                transition: 'fade',
                anchorTopOffset: 200
            },
        },
        {
            path: '/demo',
            name: 'demo',
            children: [
                // 动态路由传参,?为参数可选符,不加的话表示必须传参,否则匹配不到组件,如/demo/note,/demo/note/显示空白
                {
                    path: '/note/:words?', name: 'note',
                    component: () => import('@/a-main-app/home/note/NoteCompositionApiSimple.vue')
                },
            ]
        },
    ]
}

路由模式

histroy模式

url scheme: [//[user[:password]@]host[:port]][/path][?query][#fragment]
url=http://localhost:9000/demo/?q=3#contact?a=1   
location={
    hash: "#contact?a=1"
    host: "localhost:9000"
    hostname: "localhost"
    href: "http://localhost:9000/demo/?q=3#contact?a=1"
    origin: "http://localhost:9000"
    pathname: "/demo/"
    port: "9000"
    protocol: "http:"
    search: "?q=3"
}
// 依赖 HTML5 History API 和服务器配置。
route={
    fullPath: "/demo/?q=3#contact?a=1"
    hash: "#contact?a=1"
    matched: Array(2)
    meta: Object
    name: "demo"
    params: Object
    path: "/demo/"
    query: {
         q: "3"
    }
}

hash模式

url=http://localhost:9000/demo/?a=1#/demoZj/?b=2#contact?c=3  
location={
    hash: "#/demoZj/?b=2#contact?c=3"
    host: "localhost:9000"
    hostname: "localhost"
    href: "http://localhost:9000/demo/?a=1#/demoZj/?b=2#contact?c=3"
    origin: "http://localhost:9000"
    pathname: "/demo/"
    port: "9000"
    protocol: "http:"
    search: "?a=1"
}
// 会根据location.hash进行路由
// 支持所有浏览器,包括不支持 HTML5 History Api 的浏览器。
route={
    fullPath: "/demoZj/?b=2#contact?c=3"
    hash: "#contact?c=3"
    matched: Array(3)
    meta: Object
    name: "/demoZJ"
    params: Object
    path: "/demoZj/"
    query: {
      b: "2"
    }
}

配置

// 参考https://v3.router.vuejs.org/zh/api/#routes  
const router = createRouter({
    // 路由模式简洁配置方法
    // history路由,地址栏不带#号;hash路由,地址栏带#号,默认值
    // mode: 'history',

    // 配置地址栏基路径,会覆盖vite.config.js中base
    // history: createWebHistory(import.meta.env.BASE_URL),// 为vite.config.js中base
    // history: createWebHistory('/'),
    // history: createWebHistory('/base'),

    history: createWebHashHistory(),

    linkActiveClass: 'linkActiveClass',
    linkExactActiveClass: 'linkExactActiveClass',
    callBack:'null',
    
    // 当路由跳转后滚动条所在的位置,默认为上一路由所在位置
    // 只是hash值变也会回调, url全完相同也会回调
    scrollBehavior(to, from, savedPosition) {
        if (from.path === to.path) {
            //滚动条不动
            return
        } else {
            return {left: 0, top: 0}
        }
    },
    routes:constantRoutes,
})

区别和注意点

  • history(推荐使用,更加兼容原生)
    • 上线后,会按服务器实际配置的location(nginx)处理请求,所以需要配置后端,否则出现各种问题:如即点击RouterLink跳转正常,再次刷新或直接访问会404
    • 不会对访问的url做任何变动
  • hash
    • 上线后,也是先根据后端location获取到html,在根据hash进行路由导航
    • 会对访问的url智能加上#/,但路由导航取决于最终location.hash值
  • 注意
    • hash模式下,/#hash定位需要使用RouterLink,a标签不行,history模式都可以
    • 使用a标签会刷新页面,会导致所有显示组件重新渲染,重走生命周期,所以非外链都应该用RouterLink来动态加载路由组件。如果只改变hash值使用a不会刷新页面,但任然建议使用RouterLink

other

Vite配置

vite环境变量

console.log("环境变量", import.meta.env)
/**
 * BASE_URL: "/"
 * DEV: true 是否处于开发环境
 * MODE: "development"
 * PROD: false
 * */
  • vite自定义环境变量
    在以下文件中配置的变量
    .env.development
    .env.production
    .env.staging
    配置前缀envPrefix(官方文档), 然后上述文件中,以 envPrefix 开头的环境变量会通过 import.meta.env 暴露在你的客户端源码中。
    vite项目process.env.时process为undefined,webpack 才用 process.env.

自动导入配置

  • 按需引入, 自动导入配置
// vite.config.ts
import {ConfigEnv, defineConfig, UserConfig} from 'vite'
import {VantResolver} from "@vant/auto-import-resolver";
import Components from 'unplugin-vue-components/vite';
import {ElementPlusResolver, LayuiVueResolver} from "unplugin-vue-components/resolvers";
import AutoImport from 'unplugin-auto-import/vite'
import IconsResolver from "unplugin-icons/resolver";

export default defineConfig((configEnv: ConfigEnv): UserConfig => {
    // ...其他逻辑
    return {
        // ...其他配置
        plugins: [
            /**
             * Vant, ElementPlus, Layui等库的按需引入, 自动导入
             * ElementPlus参考:https://github.com/sxzz/element-plus-best-practices/blob/main/vite.config.ts
             * 整体配置,原理参考:https://zhuanlan.zhihu.com/p/612397686
             */
            AutoImport({
                // 自动导入对应库中相关函数,但还是建议使用直接引入
                // vue: ref, reactive, toRef...
                // vue-router: useRoute, useRouter...
                imports: [
                    "vue",
                    "vue-router",
                    "pinia",
                ],
                resolvers: [
                    // 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式)
                    ElementPlusResolver(),
                    // 自动导入 Layui 相关函数,
                    LayuiVueResolver(),
                    // 自动导入图标组件
                    IconsResolver({
                        // prefix: 'Icon',
                    }),
                ],
                eslintrc: {
                    // 如果使用了eslint,需要设置 eslintrc 字段
                    // 是否自动生成 eslint 规则至 filepath 指定文件,建议生成之后设置 false
                    enabled: false,
                    // 确保该文件在 eslint 配置文件 .eslintrc.js 或 .eslintrc.cjs 中被 extends
                    // 这样 ESlint 就不会报变量没有定义的错误了
                    filepath: "src/typings/.generated-auto-import-eslintrc.json",
                    globalsPropValue: true,
                },
                // 是否在 vue 模板中自动导入
                vueTemplate: true,
                // 自动生成函数类型声明类型至指定文件
                // dts: false, //关闭自动生成
                dts: "src/typings/generated-auto-import.d.ts",
            }),

            // ???开启后 RouterLink,RouterView 没有显性配置也会自动导入
            Components({
                resolvers: [
                    // 自动导入 Vant 组件
                    VantResolver(),
                    // 自动导入 Element Plus 组件
                    ElementPlusResolver(),
                    // 自动导入 LayuiVue 组件
                    LayuiVueResolver(),
                    // 自动注册图标组件
                    IconsResolver({
                        enabledCollections: [
                            // element-plus图标库,
                            "ep",
                            // 其他图标库 https://icon-sets.iconify.design/
                        ],
                    }),
                ],
                // 自动导入指定目录下的.vue组件 (默认为"src/components"  ""等价于"src/*")
                dirs: ["src/!**!/auto-import"],
                // dirs: ["src/nocomponents"],
                // dirs: ["src/components", "src/!**/components"],
                /**
                 * 自动生成组件类型声明至指定文件,
                 * 声明只用于能在.vue中找到组件声明,解决TS类型丢失导致编译报错
                 * 真正的自动导入取决于resolvers,dirs配置
                 * 1.对于本地组件(dirs):就算没被引用也会生成生成声明
                 * 2.对于组件库(resolvers):
                 *  被引用的组件在dev,并且在浏览器访问后生成声明
                 *  若不希望生成该声明,则需要先注释reolvers生成本地组件声明,然后关闭自动生成,再解注释resolvers
                 *  或者在没在浏览器打开前关闭自动生成
                 * 4.声明只会增加,覆盖,不会删除(1.手动删除属性 2.删除声明文件再重新生成)
                 * 5.声明属性名称: 若为index.*则为目录名,否则为文件名,且会转为大坨峰
                 */
                dts: false, // 关闭自动生成
                // dts: "src/typings/generated-components.d.ts",
            }),
        ],
    }
})