带你快速构建一个vite+vue3.0+ts+naive-ui的项目

6,030 阅读2分钟

一、摘要

本篇文章可以带你手把手搭建一个vite+vue3.0+ts+naive-ui的项目。

兼容性说明
Vite 需要Node.js版本 >=12.0.0。

二、创建项目

  • 在要创建项目的文件中右键打开PowerShell

  • 运行命令 $ npm init vite@latest

  • 输入项目名称

  • 选择vue框架

    image.png

  • 选择vue-ts模板

    image.png

这样就创建完成了。
我们打开文件夹看到

image.png

三、运行项目

  • 用vscode打开,在终端运行npm install。
    如果安装过程过慢,可以使用淘宝镜像,运行npm config set registry registry.npm.taobao.org
    (使用yarn、cnpm的应该没这个问题),换过镜像后再运行npm install
  • 使用npm run dev运行项目,可以看到默认初始界面(ctrl+c结束运行)

image.png

四、代码规范

1.集成prettier(代码格式化)

  • 安装prettier

    npm install prettier -D
    

    image.png

    安装过后,我们发现package.json文件中有了prettier,package.json文件中存放的是项目中依赖的插件库,有些插件像prettier这种只是在开发时要用到的,代码提交上线时不需要,所以存在devDependencies即可,dependencies中存放的是生产环境中要用到的插件。
    安装命令后的-D就是可以存到devDependencies中。

  • 配置prettier
    在根目录创建.prettierrc文件,在该文件中配置prettier各项规范。

    {
      "end_of_line": "lf"
    }
    

    这个配置是可以在ctrl+s保存后自动将行尾序列crlf转换成lf。 prettier具体配置可见:Options · Prettier

    常用的配置选项默认值注解
    printWidth: <int>80超过80个字符换行
    tabWidth: <int>2缩进空格数
    useTabs: <bool>false用制表符缩进
    trailingComma: "<es5|none|all>"es5行尾逗号
    bracketSameLine: <bool>false将多行 HTML元素放在最后一行的末尾,而不是单独放在下一行(不适用于自闭合元素)
    • 安装prettier插件

    image.png

2.集成eslint(检验代码格式的工具)

  • 安装eslint
npm install eslint -D

安装与使用可见eslint官网:入门 - ESLint 中文

  • 配置eslint
    在根目录创建.eslintrc.js文件,在该文件中配置eslint。

      module.exports = {
        root: true,
        env: {
          node: true,
        },
        extends: [
          "plugin:vue/vue3-essential",
          "eslint:recommended",
          "@vue/typescript/recommended",
          "@vue/prettier",
          "@vue/prettier/@typescript-eslint",
        ],
        parserOptions: {
          ecmaVersion: 2020, // 配置ECMAScript版本
        },
        rules: {},
      };
    

五、项目配置

1.配置路由

vue-router4配置详情可见:安装 | Vue Router (vuejs.org)

  • 安装vue-router

    npm install vue-router@4 // vue-router 4.x版本支持vue3
    
  • 在main.ts中引入

    import { createApp } from 'vue' // vue的启动函数,返回一个应用实例
    import App from './App.vue' // 入口文件
    import router from "./router" // 路由
    
    createApp(App).use(router).mount('#app')
    
  • 在src文件下创建router文件夹并创建index.ts文件。

    import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router"
    
    const routes: Array<RouteRecordRaw> = []
    
    const router = createRouter({
      history: createWebHistory(), // 使用history模式
      routes,
    })
    
    export default router;
    
    • vue-router3.x:默认使用hash模式。
    • vue-router4.x: 可以灵活的配置路由模式
      • history---createWebHistory
      • hash---createWebHashHistory
      • abstract---createMemoryHistory
  • 更改入口文件app.vue

    <template>
      <router-view></router-view>
    </template>
    
    <script lang="ts" setup></script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    
  • 在src下创建views文件夹,之后的页面文件都存入此文件夹中。在views文件夹下创建index.vue文件作为项目的首页。

    <template>
      <div>首页</div>
    </template>
    
    <script lang="ts" setup></script>
    
    <style scoped></style>
    
  • 将首页配置到路由文件中

    import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router"
    
    const routes: Array<RouteRecordRaw> = [
        {
            path: "/",
            name: "Home",
            component: () => import("../views/index.vue"),  
        }
    ]
    
    const router = createRouter({
      history: createWebHistory(),
      routes,
    })
    
    export default router;
    
  • 运行项目,可以看到

    image.png

2.配置vuex

vuex4配置详情可见: Installation | Vuex (vuejs.org)

  • 安装vuex

    npm install vuex@next --save // vuex4.x支持vue3
    
  • 在src下创建store文件夹(之后要存入vuex的数据都放入此文件夹中),并创建index.ts文件

    import { createStore } from "vuex";
    
    export default createStore({
      state: {},
      mutations: {},
      actions: {},
      modules: {},
    });
    
  • 在main.ts中引入

    import { createApp } from 'vue'
    import App from './App.vue'
    import router from "./router"
    import store from "./store"
    
    createApp(App).use(store).use(router).mount('#app')
    

3.使用naive-ui

官方文档入口:Naive UI: 一个 Vue 3 组件库

  • 安装naive-ui

    npm i -D naive-ui
    
  • 在src下创建plugins文件夹(该文件夹存放项目中用到的插件),并创建naiveui.ts文件。
    按需引入组件示例:

    import { create, NButton, NInput } from "naive-ui";
    
    const naive = create({
        components: [NButton, NInput]
    })
    
    export default naive;
    
  • 在main.ts中引入

    import { createApp } from 'vue'
    import App from './App.vue'
    import router from "./router"
    import store from "./store"
    import naiveui from "./plugins/naiveui";
    
    createApp(App).use(store).use(router).use(naiveui).mount('#app')
    
  • 组件使用示例:

    <template>
      <div>
        <h1>首页</h1>
        <n-button type="primary">按钮</n-button>
        <n-input
          v-model:value="value"
          type="text"
          placeholder="基本的 Input"
        ></n-input>
      </div>
    </template>
    
    <script lang="ts" setup>
    import { ref } from "@vue/reactivity";
    const value = ref(111);
    </script>
    
    <style scoped></style>
    

    运行后可以看到:

    image.png

4.集成scss

  • 安装

    npm install sass -D
    
    • 安装后可见:

      image.png

    • 之后在vue文件中可以直接使用

      <style lang="scss" scoped></style>
      

5.封装axios

  • 安装axios

    npm install axios
    
  • 在plugins文件夹下创建axios文件夹(该文件夹用来封装axios)

    • 在axios文件下创建interceptors文件夹(该文件夹用来拦截请求、拦截响应,为什么拦截请求和拦截响应要分两个文件呢?因为单一职能原则)

      • 在interceptors文件夹下创建request.ts文件
      import { Axios } from "axios";
      
      export default (axiosInstance: Axios): void => {
        axiosInstance.interceptors.request.use((config) => {
          return config;
        });
      };
      
      
      • 在interceptors文件夹下创建response.ts文件
      import { Axios } from "axios";
      
      export default (axiosInstance: Axios): void => {
        // 相应拦截器
        axiosInstance.interceptors.response.use(
          (response) => {
            const { responseType } = response.config;
            // 不拦截blob
            if (responseType === "blob") {
              return response;
            }
            const { success, errMsg } = response.data;
            // 处理失败请求
            if (!success) {
              window.$message.error(errMsg);
              return Promise.reject(errMsg);
            }
            return response.data;
          },
          (error) => {
            return Promise.reject(error);
          }
        );
      };
      
    • 在axios下创建index.ts,用来创建axios实例

      import axios, { Axios } from "axios";
      import responseInterceptor from "./interceptors/response";
      import requestInterceptor from "./interceptors/request";
      
      // 创建axios实例
      const axiosInstance: Axios = axios.create({
        baseURL: process.env.VUE_APP_BASE_API_PATH,
        timeout: 10000,
        withCredentials: true,
      });
      
      // 请求拦截器
      requestInterceptor(axiosInstance);
      // 响应拦截器
      responseInterceptor(axiosInstance);
      
      export default axiosInstance;
      
  • 在src下创建api文件夹(之后页面要调用的接口都统一从这里导出,为什么api与axios封装没在一个文件夹下?为了解耦)

    • 在api文件夹下创建index.ts文件(由该文件统一导出各个文件接口)
    • 在api文件夹下创建bussniess文件夹(该文件夹里边存放所有业务接口)
  • 补充
    因为在/plugins/axios/interceptor/request.ts文件中使用了naive-ui中message组件,所以需要引入:

    • 在/plugins/naiveui.ts文件中

      import { create, NButton, NInput, NMessageProvider } from "naive-ui";
      
      const naive = create({
          components: [NButton, NInput, NMessageProvider]
      })
      
      export default naive;
      

    引入后直接移步到【六、踩坑系列】第3点处

  • 接口调用示例

    • 在api/bussniess下创建user文件夹,并创建index.ts文件。
    import axiosInstance from "@/plugins/axios"
    
    const GETUSERINFOAPI = (data: any): Promise<any> =>
      axiosInstance.get('/blog/user', data);
    
    export default{
      GETUSERINFOAPI, // 获取用户信息
    }
    
    • 在api/index.ts文件中统一导出。
    import userApi from '@/api/bussniess/user'
    
    export  {
      userApi,
    }
    
    • 在页面中调用
    <script lang="ts" setup>
    import { userApi } from "@/api";
    
    const userInfo = () => {
      userApi.GETUSERINFOAPI({}).then(() => {});
    };
    </script>
    

六、搭建中遇到的坑

1.全局样式引入失败

这个坑暂时还没有解决,解决后再更新文章。

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from "path"

export default defineConfig({
  resolve: {
    alias: {
     "@": path.resolve(__dirname, 'src')
    }
  },
  plugins: [vue()],
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import '@/assets/scss/index.scss';`
      }
    }
  }
})

2.vite路径修改别名失败(已解决)

  • 安装@types/node

    npm i @types/node -D  
    

    image.png

  • 配置tsconfig.json文件

    image.png

  • vite.config.ts

image.png

  • 然后就可以优雅的使用别名了

3.全局使用naive-ui中的message组件(已解决)

  • 在声明文件env.d.ts中声明$message

    declare interface Window {
      $message: any;
    }
    
  • 在components文件中创建MessageApi.vue组件

    <template>
      <div></div>
    </template>
    
    <script lang="ts" setup>
    import { defineComponent, getCurrentInstance } from "vue";
    import { useMessage } from "naive-ui";
    
    window.$message = useMessage();
    </script>
    

    此时useMessage()已挂载到window上,可以通过window.$message使用

  • 在App.vue文件中引入MessageApi组件

    <template>
      <n-message-provider>
        <MessageApi />
      </n-message-provider>
      <router-view></router-view>
    </template>
    
  • 在文件中使用

    image.png

    打印window可以看到

    image.png

    页面上点击按钮显示

    image.png


最后

1.项目结构一览图

image.png

2.项目开源链接

vite-practice: vite+vue3.0+ts+naive-ui (gitee.com)

3.其他

本人正在参加年度人气创作者评选,如果你看到了最后,感觉对你有所帮助的话,请为我投上宝贵的一票,附上链接:掘金 - 2021年度人气创作者榜单 (juejin.cn)