你在骂一遍 發表於 2024-6-3 14:37:00

Vue3.0+typescript+Vite+Pinia+Element-plus搭建vue3框架!

<h1 id="使用-vite-快速搭建脚手架"><strong>使用 Vite 快速搭建脚手架</strong></h1>
<h2 id="命令行选项直接指定项目名称和想要使用的模板vite--vue-项目运行推荐使用yarn">命令行选项直接指定项目名称和想要使用的模板,Vite + Vue 项目,运行(推荐使用yarn)</h2>
<pre><code class="language-js"># npm 6.x
npm init vite@latest my-vue-app --template vue

# npm 7+, 需要额外的双横线:
npm init vite@latest my-vue-app -- --template vue

# yarn
yarn create vite my-vue-app --template vue

# pnpm
pnpm create vite my-vue-app -- --template vue
</code></pre>
<h2 id="这里我们想要直接生成一个vue3vite2ts的项目模板因此我们执行的命令是-yarn-create-vite-my-vue-app---template-vue-ts这样我们就不需要你单独的再去安装配置ts了">这里我们想要直接生成一个Vue3+Vite2+ts的项目模板,因此我们执行的命令是: yarn create vite my-vue-app --template vue-ts,这样我们就不需要你单独的再去安装配置ts了。</h2>
<p><img alt="" loading="lazy"></p>
<h2 id="cd-到项目文件夹安装node_modules依赖运行项目">cd 到项目文件夹,安装node_modules依赖,运行项目</h2>
<pre><code class="language-js"># cd进入my-vue-app项目文件夹
cd my-vue-app
# 安装依赖
yarn
# 运行项目
yarn dev
</code></pre>
<p><img alt="" loading="lazy"></p>
<p>至此,一个最纯净的vue3.0+vite2+typescript项目就完成了。在浏览地址栏中输入http://localhost:3000/,就看到了如下的启动页,然后就可以安装所需的插件了。</p>
<p><img alt="" loading="lazy"></p>
<h1 id="配置文件路径引用别名-alias">配置文件路径引用别名 alias</h1>
<h2 id="修改viteconfigts中的reslove的配置">修改vite.config.ts中的reslove的配置</h2>
<pre><code class="language-js">import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

// https://vitejs.dev/config/
export default defineConfig({
plugins: ,
resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
},
})
</code></pre>
<h2 id="在修改tsconfigjson文件的配置">在修改tsconfig.json文件的配置</h2>
<pre><code class="language-js">{
"compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
    "baseUrl": ".",
    "paths": {
      "@/*":["src/*"]
    }
},
"include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue"
]
}
</code></pre>
<h1 id="配置路由">配置路由</h1>
<h2 id="安装">安装</h2>
<pre><code class="language-js"># npm
npm install vue-router@4

# yarn
yarn add vue-router@4
</code></pre>
<h2 id="在src下新建router文件夹用来集中管理路由在router文件夹下新建--indexts文件">在src下新建router文件夹,用来集中管理路由,在router文件夹下新建index.ts文件。</h2>
<pre><code class="language-js">import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
{
    path: '/',
    name: 'Login',
    // 注意这里要带上文件后缀.vue
    component: () =&gt; import('@/pages/login/Login.vue'),
    meta: {
      title: '登录',
    },
},
]

const router = createRouter({
history: createWebHistory(),
routes,
strict: true,
// 期望滚动到哪个的位置
scrollBehavior(to, from, savedPosition) {
    return new Promise(resolve =&gt; {
      if (savedPosition) {
      return savedPosition;
      } else {
      if (from.meta.saveSrollTop) {
          const top: number =
            document.documentElement.scrollTop || document.body.scrollTop;
          resolve({ left: 0, top });
      }
      }
    });
}
})

export function setupRouter(app: App) {
app.use(router);
}

export default router
</code></pre>
<h2 id="修改入口文件-miants">修改入口文件 mian.ts</h2>
<pre><code class="language-js">import { createApp } from "vue";
import App from "./App.vue";
import router, { setupRouter } from './router';

const app = createApp(App);
// 挂在路由
setupRouter(app);
// 路由准备就绪后挂载APP实例
await router.isReady();

app.mount('#app', true);
</code></pre>
<p>更多的路由配置可以移步vue-router(https://next.router.vuejs.org/zh/introduction.html)。 vue-router4.x支持typescript,路由的类型为RouteRecordRaw。meta字段可以让我们根据不同的业务需求扩展 RouteMeta 接口来输入它的多样性。以下的meta中的配置仅供参考:</p>
<pre><code class="language-js">// typings.d.ts or router.ts
import 'vue-router'

declare module 'vue-router' {
interface RouteMeta {
    // 页面标题,通常必选。
    title: string;
    // 菜单图标
    icon?: string;
    // 配置菜单的权限
    permission: string[];
    // 是否开启页面缓存
    keepAlive?: boolean;
    // 二级页面我们并不想在菜单中显示
    hidden?: boolean;
    // 菜单排序
    order?: number;
    // 嵌套外链
    frameUrl?: string;
}
}
</code></pre>
<h1 id="配置-css-预处理器-scss">配置 css 预处理器 scss</h1>
<h2 id="安装-1">安装</h2>
<pre><code class="language-js">yarn add sass-loader --dev
yarn add dart-sass --dev
yarn add sass --dev
</code></pre>
<h2 id="配置全局-scss-样式文件-在-src文件夹下新增-styles-文件夹用于存放全局样式文件新建一个-variblesscss文件用于统一管理声明的颜色变量">配置全局 scss 样式文件 在 src文件夹下新增 styles 文件夹,用于存放全局样式文件,新建一个 varibles.scss文件,用于统一管理声明的颜色变量:</h2>
<pre><code class="language-css">$white: #FFFFFF;
$primary-color: #1890ff;
$success-color: #67C23A;
$warning-color: #E6A23C;
$danger-color: #F56C6C;
$info-color: #909399;
</code></pre>
<h2 id="组件中使用在viteconfigts中将这个样式文件全局注入到项目即可全局使用不需要在任何组件中再次引入这个文件或者颜色变量">组件中使用在vite.config.ts中将这个样式文件全局注入到项目即可全局使用,不需要在任何组件中再次引入这个文件或者颜色变量。</h2>
<pre><code class="language-css">css: {
preprocessorOptions: {
    scss: {
      modifyVars: {},
      javascriptEnabled: true,
      // 注意这里的引入的书写
      additionalData: '@import "@/style/varibles.scss";'
    }
}
},
</code></pre>
<p>在组件中使用</p>
<pre><code class="language-css">.div {
color: $primary-color;
background-color: $success-color;
}
</code></pre>
<h1 id="统一请求封装">统一请求封装</h1>
<h2 id="在src文件夹下新建http文件夹在http文件夹下新增indextsconfigtscoretstypesdtsutilsts">在src文件夹下,新建http文件夹,在http文件夹下新增index.ts,config.ts,core.ts,types.d.ts,utils.ts</h2>
<h3 id="corets">core.ts</h3>
<pre><code class="language-typescript">import Axios, { AxiosRequestConfig, CancelTokenStatic, AxiosInstance } from "axios";
import NProgress from 'nprogress'
import { genConfig } from "./config";
import { transformConfigByMethod } from "./utils";
import {
cancelTokenType,
RequestMethods,
HttpRequestConfig,
HttpResoponse,
HttpError
} from "./types.d";

class Http {
constructor() {
    this.httpInterceptorsRequest();
    this.httpInterceptorsResponse();
}
// 初始化配置对象
private static initConfig: HttpRequestConfig = {};

// 保存当前Axios实例对象
private static axiosInstance: AxiosInstance = Axios.create(genConfig());

// 保存 Http实例
private static HttpInstance: Http;

// axios取消对象
private CancelToken: CancelTokenStatic = Axios.CancelToken;

// 取消的凭证数组
private sourceTokenList: Array&lt;cancelTokenType&gt; = [];

// 记录当前这一次cancelToken的key
private currentCancelTokenKey = "";

public get cancelTokenList(): Array&lt;cancelTokenType&gt; {
    return this.sourceTokenList;
}

// eslint-disable-next-line class-methods-use-this
public set cancelTokenList(value) {
    throw new Error("cancelTokenList不允许赋值");
}

/**
   * @description 私有构造不允许实例化
   * @returns void 0
   */
// constructor() {}

/**
   * @description 生成唯一取消key
   * @param config axios配置
   * @returns string
   */
// eslint-disable-next-line class-methods-use-this
private static genUniqueKey(config: HttpRequestConfig): string {
    return `${config.url}--${JSON.stringify(config.data)}`;
}

/**
   * @description 取消重复请求
   * @returns void 0
   */
private cancelRepeatRequest(): void {
    const temp: { : boolean } = {};

    this.sourceTokenList = this.sourceTokenList.reduce&lt;Array&lt;cancelTokenType&gt;&gt;(
      (res: Array&lt;cancelTokenType&gt;, cancelToken: cancelTokenType) =&gt; {
      const { cancelKey, cancelExecutor } = cancelToken;
      if (!temp) {
          temp = true;
          res.push(cancelToken);
      } else {
          cancelExecutor();
      }
      return res;
      },
      []
    );
}

/**
   * @description 删除指定的CancelToken
   * @returns void 0
   */
private deleteCancelTokenByCancelKey(cancelKey: string): void {
    this.sourceTokenList =
      this.sourceTokenList.length &lt; 1
      ? this.sourceTokenList.filter(
            cancelToken =&gt; cancelToken.cancelKey !== cancelKey
          )
      : [];
}

/**
   * @description 拦截请求
   * @returns void 0
   */

private httpInterceptorsRequest(): void {
    Http.axiosInstance.interceptors.request.use(
      (config: HttpRequestConfig) =&gt; {
      const $config = config;
      NProgress.start(); // 每次切换页面时,调用进度条
      const cancelKey = Http.genUniqueKey($config);
      $config.cancelToken = new this.CancelToken(
          (cancelExecutor: (cancel: any) =&gt; void) =&gt; {
            this.sourceTokenList.push({ cancelKey, cancelExecutor });
          }
      );
      this.cancelRepeatRequest();
      this.currentCancelTokenKey = cancelKey;
      // 优先判断post/get等方法是否传入回掉,否则执行初始化设置等回掉
      if (typeof config.beforeRequestCallback === "function") {
          config.beforeRequestCallback($config);
          return $config;
      }
      if (Http.initConfig.beforeRequestCallback) {
          Http.initConfig.beforeRequestCallback($config);
          return $config;
      }
      return $config;
      },
      error =&gt; {
      return Promise.reject(error);
      }
    );
}

/**
   * @description 清空当前cancelTokenList
   * @returns void 0
   */
public clearCancelTokenList(): void {
    this.sourceTokenList.length = 0;
}

/**
   * @description 拦截响应
   * @returns void 0
   */
private httpInterceptorsResponse(): void {
    const instance = Http.axiosInstance;
    instance.interceptors.response.use(
      (response: HttpResoponse) =&gt; {
      const $config = response.config;
      // 请求每次成功一次就删除当前canceltoken标记
      const cancelKey = Http.genUniqueKey($config);
      this.deleteCancelTokenByCancelKey(cancelKey);

      NProgress.done();
      // 优先判断post/get等方法是否传入回掉,否则执行初始化设置等回掉
      if (typeof $config.beforeResponseCallback === "function") {
          $config.beforeResponseCallback(response);
          return response.data;
      }
      if (Http.initConfig.beforeResponseCallback) {
          Http.initConfig.beforeResponseCallback(response);
          return response.data;
      }
      return response.data;
      },
      (error: HttpError) =&gt; {
      const $error = error;
      // 判断当前的请求中是否在 取消token数组理存在,如果存在则移除(单次请求流程)
      if (this.currentCancelTokenKey) {
          const haskey = this.sourceTokenList.filter(
            cancelToken =&gt; cancelToken.cancelKey === this.currentCancelTokenKey
          ).length;
          if (haskey) {
            this.sourceTokenList = this.sourceTokenList.filter(
            cancelToken =&gt;
                cancelToken.cancelKey !== this.currentCancelTokenKey
            );
            this.currentCancelTokenKey = "";
          }
      }
      $error.isCancelRequest = Axios.isCancel($error);
      NProgress.done();
      // 所有的响应异常 区分来源为取消请求/非取消请求
      return Promise.reject($error);
      }
    );
}

public request&lt;T&gt;(
    method: RequestMethods,
    url: string,
    param?: AxiosRequestConfig,
    axiosConfig?: HttpRequestConfig
): Promise&lt;T&gt; {
    const config = transformConfigByMethod(param, {
      method,
      url,
      ...axiosConfig
    } as HttpRequestConfig);
    // 单独处理自定义请求/响应回掉
    return new Promise((resolve, reject) =&gt; {
      Http.axiosInstance
      .request(config)
      .then((response: undefined) =&gt; {
          resolve(response);
      })
      .catch((error: any) =&gt; {
          reject(error);
      });
    });
}

public post&lt;T&gt;(
    url: string,
    params?: T,
    config?: HttpRequestConfig
): Promise&lt;T&gt; {
    return this.request&lt;T&gt;("post", url, params, config);
}

public get&lt;T&gt;(
    url: string,
    params?: T,
    config?: HttpRequestConfig
): Promise&lt;T&gt; {
    return this.request&lt;T&gt;("get", url, params, config);
}
}

export default Http;
</code></pre>
<h3 id="configts">config.ts</h3>
<pre><code class="language-typescript">import { AxiosRequestConfig } from "axios";
import { excludeProps } from "./utils";
/**
* 默认配置
*/
export const defaultConfig: AxiosRequestConfig = {
baseURL: "",
//10秒超时
timeout: 10000,
headers: {
    Accept: "application/json, text/plain, */*",
    "Content-Type": "application/json",
    "X-Requested-With": "XMLHttpRequest"
}
};

export function genConfig(config?: AxiosRequestConfig): AxiosRequestConfig {
if (!config) {
    return defaultConfig;
}

const { headers } = config;
if (headers &amp;&amp; typeof headers === "object") {
    defaultConfig.headers = {
      ...defaultConfig.headers,
      ...headers
    };
}
return { ...excludeProps(config!, "headers"), ...defaultConfig };
}

export const METHODS = ["post", "get", "put", "delete", "option", "patch"];
</code></pre>
<h3 id="utilsts">utils.ts</h3>
<pre><code class="language-typescript">import { HttpRequestConfig } from "./types.d";

export function excludeProps&lt;T extends { : any }&gt;(
origin: T,
prop: string
): { : T } {
return Object.keys(origin)
    .filter(key =&gt; !prop.includes(key))
    .reduce((res, key) =&gt; {
      res = origin;
      return res;
    }, {} as { : T });
}

export function transformConfigByMethod(
params: any,
config: HttpRequestConfig
): HttpRequestConfig {
const { method } = config;
const props = ["delete", "get", "head", "options"].includes(
    method!.toLocaleLowerCase()
)
    ? "params"
    : "data";
return {
    ...config,
    : params
};
}
</code></pre>
<h3 id="typesdts">types.d.ts</h3>
<pre><code class="language-typescript">import Axios, {
AxiosRequestConfig,
Canceler,
AxiosResponse,
Method,
AxiosError
} from "axios";

import { METHODS } from "./config";

export type cancelTokenType = { cancelKey: string; cancelExecutor: Canceler };

export type RequestMethods = Extract&lt;
Method,
"get" | "post" | "put" | "delete" | "patch" | "option" | "head"
&gt;;

export interface HttpRequestConfig extends AxiosRequestConfig {
// 请求发送之前
beforeRequestCallback?: (request: HttpRequestConfig) =&gt; void;
// 相应返回之前
beforeResponseCallback?: (response: HttpResoponse) =&gt; void;
}

export interface HttpResoponse extends AxiosResponse {
config: HttpRequestConfig;
}

export interface HttpError extends AxiosError {
isCancelRequest?: boolean;
}

export default class Http {
cancelTokenList: Array&lt;cancelTokenType&gt;;
clearCancelTokenList(): void;
request&lt;T&gt;(
    method: RequestMethods,
    url: string,
    param?: AxiosRequestConfig,
    axiosConfig?: HttpRequestConfig
): Promise&lt;T&gt;;
post&lt;T&gt;(
    url: string,
    params?: T,
    config?: HttpRequestConfig
): Promise&lt;T&gt;;
get&lt;T&gt;(
    url: string,
    params?: T,
    config?: HttpRequestConfig
): Promise&lt;T&gt;;
}
</code></pre>
<h3 id="indexts">index.ts</h3>
<pre><code class="language-typescript">import Http from "./core";
export const http = new Http();
</code></pre>
<h1 id="统一api管理">统一api管理</h1>
<p>在src下新增api文件夹,对项目中接口做统一管理,按照模块来划分。</p>
<p>例如,在 api 文件下新增 user.ts和types.ts ,分别用于存放登录,注册等模块的请求接口和数据类型。</p>
<pre><code class="language-typescript">// login.ts
import { http } from "@/http/index";
import { ILoginReq, ILoginRes } from "./types";

export const getLogin = async(req: ILoginParams): Promise&lt;ILoginRes&gt; =&gt; {
const res:any = await http.post('/login/info', req)
return res as ILoginRes
}
# 或者
export const getLogin1 = async(req: ILoginParams): Promise&lt;ILoginRes&gt; =&gt; {
const res:any = await http.request('post', '/login/info', req)
return res as ILoginRes
}
</code></pre>
<pre><code class="language-typescript">// types.ts
export interface ILoginReq {
userName: string;
password: string;
}

export interface ILoginRes {
access_token: string;
refresh_token: string;
scope: string
token_type: string
expires_in: string
}
</code></pre>
<p>除了自己手动封装 axios ,这里还推荐一个十分非常强大牛皮的 vue3 的请求库: VueRequest,里面的功能非常的丰富(偷偷告诉你我也在使用中)。官网地址:https://www.attojs.com/</p>
<h1 id="状态管理-pinia">状态管理 Pinia</h1>
<p>Pinia 是 Vue.js 的轻量级状态管理库,最近很受欢迎。它使用 Vue 3 中的新反应系统来构建一个直观且完全类型化的状态管理库。</p>
<p>由于 vuex 4 对 typescript 的支持很不友好,所以状态管理弃用了 vuex 而采取了 pinia, pinia 的作者是 Vue 核心团队成员,并且pinia已经正式加入了Vue,成为了Vue中的一员。尤大佬 pinia 可能会代替 vuex,所以请放心使用(公司项目也在使用中)。</p>
<p>Pinia官网地址(https://pinia.vuejs.org)</p>
<p>Pinia的一些优点:</p>
<p>(1)Pinia 的 API 设计非常接近 <code>Vuex 5</code> 的提案。</p>
<p>(2)无需像 <code>Vuex 4</code> 自定义复杂的类型来支持 typescript,天生具备完美的类型推断。</p>
<p>(3)模块化设计,你引入的每一个 store 在打包时都可以自动拆分他们。</p>
<p>(4)无嵌套结构,但你可以在任意的 store 之间交叉组合使用。</p>
<p>(5)Pinia 与 Vue devtools 挂钩,不会影响 Vue 3 开发体验。</p>
<p><img alt="" loading="lazy"></p>
<p>Pinia的成功可以归功于其管理存储数据的独特功能(可扩展性、存储模块组织、状态变化分组、多存储创建等)。</p>
<p>另一方面,Vuex也是为Vue框架建立的一个流行的状态管理库,它也是Vue核心团队推荐的状态管理库。Vuex高度关注应用程序的可扩展性、开发人员的工效和信心。它基于与Redux相同的流量架构。</p>
<p>Pinia和Vuex都非常快,在某些情况下,使用Pinia的web应用程序会比使用Vuex更快。这种性能的提升可以归因于Pinia的极轻的体积,Pinia体积约1KB。</p>
<h2 id="安装-2">安装</h2>
<pre><code class="language-js"># 安装
yarn add pinia@next
</code></pre>
<h2 id="在src下新建store文件夹在store文件夹下新建indextsmutation-types变量集中管理typests类型和modules文件夹分模块管理状态">在src下新建store文件夹,在store文件夹下新建index.ts,mutation-types(变量集中管理),types.ts(类型)和modules文件夹(分模块管理状态)</h2>
<p><img alt="" loading="lazy"></p>
<pre><code class="language-typescript">// index.ts
import type { App } from "vue";
import { createPinia } from "pinia";

const store = createPinia();
export function setupStore(app: App&lt;Element&gt;) {
    app.use(store)
}

export { store }
</code></pre>
<pre><code class="language-typescript">// modules/user.ts
import { defineStore } from 'pinia';
import { store } from '@/store';
import { ACCESS_TOKEN } from '@/store/mutation-types';
import { IUserState } from '@/store/types'

export const useUserStore = defineStore({
// 此处的id很重要
id: 'app-user',
state: (): IUserState =&gt; ({
    token: localStorge.getItem(ACCESS_TOKEN)
}),
getters: {
    getToken(): string {
      return this.token;
    }
},
actions: {
    setToken(token: string) {
      this.token = token;
    },
    // 登录
    async login(userInfo) {
      try {
      const response = await login(userInfo);
      const { result, code } = response;
      if (code === ResultEnum.SUCCESS) {
          localStorage.setItem(ACCESS_TOKEN, result.token);
          this.setToken(result.token);
      }
      return Promise.resolve(response);
      } catch (e) {
      return Promise.reject(e);
      }
    },
}
})

// Need to be used outside the setup
export function useUserStoreHook() {
return useUserStore(store);
}
</code></pre>
<pre><code class="language-typescript">/// mutation-types.ts
// 对变量做统一管理
export const ACCESS_TOKEN = 'ACCESS-TOKEN'; // 用户token
</code></pre>
<h2 id="修改maints">修改main.ts</h2>
<pre><code class="language-js">import { createApp } from 'vue'
import App from './App.vue'
import { setupStore } from '@/store'
import router from './router/index'

const app = createApp(App)
// 挂载状态管理
setupStore(app);

app.use(router)

app.mount('#app')
</code></pre>
<h2 id="在组件中使用">在组件中使用</h2>
<pre><code class="language-vue">&lt;template&gt;
&lt;div&gt;{{userStore.token}}&lt;/div&gt;
&lt;/template&gt;

&lt;script lang="ts"&gt;
import { defineComponent } from 'vue'
import { useUserStoreHook } from "@/store/modules/user"

export default defineComponent({
setup() {
    const userStore = useUserStoreHook()
   
    return {
      userStore
    }
},
})
&lt;/script&gt;
</code></pre>
<h2 id="getters的用法介绍">getters的用法介绍</h2>
<pre><code class="language-typescript">// modules/user.ts
import { defineStore } from 'pinia';
import { store } from '@/store';
import { ACCESS_TOKEN } from '@/store/mutation-types';
import { IUserState } from '@/store/types'

export const useUserStore = defineStore({
// 此处的id很重要
id: 'app-user',
state: (): IUserState =&gt; ({
    token: localStorge.getItem(ACCESS_TOKEN),
    name: ''
}),
getters: {
    getToken(): string {
      return this.token;
    },
    nameLength: (state) =&gt; state.name.length,
},
actions: {
    setToken(token: string) {
      this.token = token;
    },
    // 登录
    async login(userInfo) {
      // 调用接口,做逻辑处理
    }
}
})

// Need to be used outside the setup
export function useUserStoreHook() {
return useUserStore(store);
}
</code></pre>
<pre><code class="language-vue">&lt;template&gt;
&lt;div&gt;
   &lt;span&gt;{{userStore.name}}&lt;/span&gt;
&lt;span&gt;{{userStore.nameLength}}&lt;/span&gt;
&lt;buttton @click="changeName"&gt;&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script lang="ts"&gt;
import { defineComponent } from 'vue'
import { useUserStoreHook } from "@/store/modules/user"

export default defineComponent({
setup() {
    const userStore = useUserStoreHook()
   
    const changeName = ()=&gt;{
    // $patch 修改 store 中的数据
      userStore.$patch({
      name: '名称被修改了,nameLength也改变了'
      })
}
   
    return {
      userStore,
      updateName
    }
},
})
&lt;/script&gt;
</code></pre>
<h2 id="actions">actions</h2>
<p>这里与 Vuex 有极大的不同,Pinia 仅提供了一种方法来定义如何更改状态的规则,<strong>放弃 mutations 只依靠 Actions</strong>,这是一项重大的改变。</p>
<p>Pinia 让 Actions 更加的灵活</p>
<ul>
<li>
<p>可以通过<strong>组件</strong>或其他 <strong>action</strong> 调用</p>
</li>
<li>
<p>可以从<strong>其他 store</strong> 的 action 中调用</p>
</li>
<li>
<p>直接在商店实例上调用</p>
</li>
<li>
<p>支持<strong>同步</strong>或<strong>异步</strong></p>
</li>
<li>
<p>有任意数量的参数</p>
</li>
<li>
<p>可以包含有关如何更改状态的逻辑(也就是 vuex 的 mutations 的作用)</p>
</li>
<li>
<p>可以 <code>$patch</code> 方法直接更改状态属性</p>
<p>更多详细的用法请参考Pinia中的actions官方网站:</p>
<p>actions的用法(https://pinia.vuejs.org/core-concepts/actions.html)</p>
</li>
</ul>
<h1 id="环境变量配置">环境变量配置</h1>
<h2 id="vite-提供了两种模式具有开发服务器的开发模式development和生产模式production在项目的根目录中我们新建开发配置文件envdevelopment和生产配置文件envproduction">vite 提供了两种模式:具有开发服务器的开发模式(development)和生产模式(production)。在项目的根目录中我们新建开发配置文件.env.development和生产配置文件.env.production。</h2>
<pre><code class="language-js"># 网站根目录
VITE_APP_BASE_URL= ''
</code></pre>
<h2 id="组件中使用">组件中使用:</h2>
<pre><code class="language-js">console.log(import.meta.env.VITE_APP_BASE_URL)
</code></pre>
<h2 id="配置-packagejson打包区分开发环境和生产环境">配置 package.json,打包区分开发环境和生产环境</h2>
<pre><code class="language-js">"build:dev": "vue-tsc --noEmit &amp;&amp; vite build --mode development",
"build:pro": "vue-tsc --noEmit &amp;&amp; vite build --mode production",
</code></pre>
<h1 id="使用组件库">使用组件库</h1>
<p>根据自己的项目需要选择合适的组件库即可,这里推荐两个优秀的组件库Element-plus和Naive UI。下面简单介绍它们的使用方法。</p>
<h2 id="使用element-plushttpselement-plusgiteeiozh-cn">使用element-plus(https://element-plus.gitee.io/zh-CN/)</h2>
<pre><code class="language-js">yarn add element-plus
</code></pre>
<p>推荐按需引入的方式:</p>
<p>按需引入需要安装unplugin-vue-components和unplugin-auto-import两个插件。</p>
<pre><code class="language-js">yarn add -D unplugin-vue-components unplugin-auto-import
</code></pre>
<p>再将vite.config.ts写入一下配置,即可在项目中使用element plus组件,无需再引入。</p>
<pre><code class="language-js">// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default {
plugins: [
    // ...
    AutoImport({
      resolvers: ,
    }),
    Components({
      resolvers: ,
    }),
],
}
</code></pre>
<h2 id="naive-uihttpswwwnaiveuicomzh-cnos-theme">Naive UI(https://www.naiveui.com/zh-CN/os-theme)</h2>
<pre><code class="language-js"># 安装naive-ui
npm i -D naive-ui

# 安装字体
npm i -D vfonts
</code></pre>
<p><strong>按需全局安装组件</strong></p>
<pre><code class="language-js">import { createApp } from 'vue'
import {
// create naive ui
create,
// component
NButton
} from 'naive-ui'

const naive = create({
components:
})

const app = createApp()
app.use(naive)
</code></pre>
<p>安装后,你可以这样在 SFC 中使用你安装的组件。</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;n-button&gt;naive-ui&lt;/n-button&gt;
&lt;/template&gt;
</code></pre>
<h1 id="vite-常用基础配置">Vite 常用基础配置</h1>
<h2 id="基础配置">基础配置</h2>
<p>运行代理和打包配置</p>
<pre><code class="language-js">server: {
    host: '0.0.0.0',
    port: 3000,
    open: true,
    https: false,
    proxy: {}
},
</code></pre>
<p>生产环境去除 console debugger</p>
<pre><code class="language-js">build:{
...
terserOptions: {
      compress: {
      drop_console: true,
      drop_debugger: true
      }
}
}
</code></pre>
<p>生产环境生成 .gz 文件,开启 gzip 可以极大的压缩静态资源,对页面加载的速度起到了显著的作用。使用 vite-plugin-compression 可以 gzip 或 brotli 的方式来压缩资源,这一步需要服务器端的配合,vite 只能帮你打包出 .gz 文件。此插件使用简单,你甚至无需配置参数,引入即可。</p>
<pre><code class="language-js"># 安装
yarn add --dev vite-plugin-compression
</code></pre>
<pre><code class="language-js">// vite.config.ts中添加
import viteCompression from 'vite-plugin-compression'

// gzip压缩 生产环境生成 .gz 文件
viteCompression({
verbose: true,
disable: false,
threshold: 10240,
algorithm: 'gzip',
ext: '.gz',
}),
</code></pre>
<p>最终 vite.config.ts文件配置如下(自己根据项目需求配置即可)</p>
<pre><code class="language-typescript">import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
//@ts-ignore
import viteCompression from 'vite-plugin-compression'

// https://vitejs.dev/config/
export default defineConfig({
base: './', //打包路径
plugins: [
    vue(),
    // gzip压缩 生产环境生成 .gz 文件
    viteCompression({
      verbose: true,
      disable: false,
      threshold: 10240,
      algorithm: 'gzip',
      ext: '.gz',
    }),
],
// 配置别名
resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
},
css:{
    preprocessorOptions:{
      scss:{
      additionalData:'@import "@/assets/style/mian.scss";'
      }
    }
},
//启动服务配置
server: {
    host: '0.0.0.0',
    port: 8000,
    open: true,
    https: false,
    proxy: {}
},
// 生产环境打包配置
//去除 console debugger
build: {
    terserOptions: {
      compress: {
      drop_console: true,
      drop_debugger: true,
      },
    },
},
})
</code></pre><br><br>
来源:https://www.cnblogs.com/jqCode/p/18228885
頁: [1]
查看完整版本: Vue3.0+typescript+Vite+Pinia+Element-plus搭建vue3框架!