記事ディレクトリ
序文
- 時代は変わり、webpack、Rollup などの例が見られるようになりました。 および Parcel などのツールは、フロントエンド開発者の開発エクスペリエンスを大幅に向上させました。
- ただし、アプリケーションの構築がますます大きくなり始めると、処理する必要がある JavaScript コードの量が飛躍的に増加します。数千のモジュールを含む大規模なプロジェクトは非常に一般的です。 JavaScript に基づいて開発されたツールは、パフォーマンスのボトルネックに遭遇し始めます。多くの場合、開発サーバーの起動には長い時間 (数分も!) かかり、ホット モジュール置換 (HMR) を使用しても、ファイル変更の効果が反映されるまでに数秒かかります。ブラウザで。このサイクルでは、フィードバックが遅いと開発効率と開発者の幸福度に大きな影響を与えます。
- Vite は、エコシステムの新たな発展を利用してこれらの問題に対処することを目指しています。ブラウザは ES モジュールをネイティブにサポートし始めており、JavaScript ツールはコンパイル言語で作成されることが増えています。
上記はVite公式サイトの説明文より引用。
なぜこの記事を詳細に記録するのでしょうか?というのも、私が今の会社に入社した当時は基本的なフロントエンドアーキテクチャすらなく、プロジェクトをやるたびにインターネット上にある肥大化したコードテンプレートを再利用(1セット作ったらもう1セット捨てる、ということを遠慮せずに)していました。フロントエンド テンプレート フレームワーク)。このプロジェクトチームは以前は vue2 で開発を行っていましたが、最近リーダーが今後のプロジェクトでは vue3 での開発を検討すると言っていたので、このプロジェクトチームで純粋なフロントエンド担当者は私だけになりました (他のメンバーは全員 C# の偉い人たちです)多くのプロジェクトはフロントエンドでもバックエンドでもありません)。分離)、カスタム vue3 アーキテクチャを最初から構築することを検討する必要があります。以前のプロジェクトの経験を組み合わせて、このフロントエンド アーキテクチャを最初から構築しました (上司がいないため)情報を少しずつ、段階的に確認することしかできませんが、しばらく模索しており、この期間中に多くの問題に遭遇しました)最適化すべき領域があれば、コメントや交換を残してください。拳を握れ!
それでは、段階的に構築してみましょうvite
+vue3
+ts
~
ヒント: この記事は構成に従って段階的に構成するため、少し長くなりますが、初心者の場合は、段階的に構成することをお勧めします。
1. 基本的なプロジェクト テンプレートを構築する
- 最初にインストールする必要がありますノード
まずノードをインストールした後のバージョンを確認します
node -v
ヒント Vite は下位ノードのバージョンではサポートされていないため、最新の安定したバージョン、または 14.18 以降をインストールすることをお勧めします。
- 最新バージョンの Vue 基本テンプレートをインストールする
現在の基本プロジェクト テンプレートは、vue の最新バージョンに従って構築されています (他のインストール方法については、非同期 vite 公式 Web サイトで詳細を確認できます)
インストール コマンドは次のとおりです。次のとおりです:
npm init vue@latest
上記を実行する場合、いくつかの構成を選択する必要があります。このプロジェクトの具体的な選択は次のとおりです:
実行後、ディレクトリは次のように生成されます。< /span>
- 依存関係をインストールする
npm i
- プロジェクトの実施
npm run dev
- ページにアクセスする
上記の操作を行った後、http://localhost:5173/ にアクセスすると、次の内容が表示されます
現在のデモはelement-plus
に基づいているため、まずインストールしてnpm i element-plus -S
、次にmain.js
に適用します。あ>
import ElementPlus from 'element-plus'
import 'element-plus/theme-chalk/index.css'
...
const app = createApp(App)
...
app.use(ElementPlus)
2. 基本テンプレート(継続的に更新)に基づいてプロジェクト構造を構築および最適化します。
初期化された基本プロジェクト構造は比較的単純であるため、一般に個人の習慣に応じて構造を最適化し、関連情報を構成する必要があります。
A. 環境構成の最適化
上記の基本的なテンプレートは表示されますが、さまざまな環境変数は表示されません。実際には、テスト環境と本番環境が存在します。詳細は import.meta を使用して出力できます。
内main.ts
出力import.meta.env
次に、npm run dev
を実行します。ページのコンソールを見ると、MODE
の値が"development"
実行であることがわかります。 npm run build-only
は、サーバー上の構築された dist にファイルを配置します。
- ローカル開発に推奨
nginx
(推奨)。Windows および Mac 環境での Nginx の使用法と構成の詳細を参照してください。 - を使用して
express-generator
サービスをオンにすることもできます - もちろん、開発やテストで直接アクセスすることもできます
http://localhost:5173/dist/index.html
が、それには対応するファイル導入パスを変更する必要があります。さらに詳しく知りたい場合は、独自の方法でアクセスできます。研究
次にアクセスすると、コンソールが MODE
の値を"production"
ただし、プロジェクトの開発から立ち上げまでで環境要件が異なる場合があるため、異なる環境のAPIに対応するように環境変数を設定する必要がありますが、設定後は特に変更がなければ環境設定関連の変更は必要ありません。このようにして、デプロイおよびビルド後の環境エラーを回避できます。
通常のプロセスは大きく分けて
- 開発環境(実際の開発状況に応じて、対応環境のAPI連携が可能)
- 座る環境(テスト)
- uat環境(業務受入)
- 本番環境(オンライン環境)
最初に環境変数を構成し、ルート ディレクトリにさまざまな環境用の新しいファイルを作成します (特定の構成キーはデフォルトで VITE_
で始まりますが、構成することもできますenvPrefix'
カスタマイズされた値)
- .env (これはパブリック環境設定であり、内部の設定情報が取得されます)
- .env.開発
- .env.production
- .env.sit
- .env.uat
.env ファイルは共有構成であるため、実行スクリプト環境に対応する構成であっても取得されます
# 公有全局环境配置
VITE_STORE_NAME = invade-
別のコマンドを実行して対応する環境設定ファイルを読み取る場合は、package.json
で対応するスクリプトを設定する必要があります。スクリプトの実行後、vite は対応する環境ファイルを読み取ります。コンパイルされたコマンドによると、新しい追加は次のとおりです。
{
...
"scripts": {
"dev": "vite serve --mode development",
"dev-sit": "vite serve --mode sit",
"dev-uat": "vite serve --mode uat",
"dev-prod": "vite serve --mode production",
"sit": "vite build --mode sit",
"uat": "vite build --mode uat",
"prod": "vite build --mode production",
...
},
...
}
vite serve
は、ローカル開発と共同デバッグ テストに対応します。環境によってはローカルの共同デバッグとテストの問題が必要になる場合があるため、dev-sit
これらの問題があります。指定しない場合--mode
development
vite build
は実行用にビルドおよびパッケージ化されます。--mode
の後の値が指定されていない場合、デフォルトは です。production
--mode
の後には、設定された環境変数の値が続きます。これは、上のスクリーンショットのコンソールに表示されるMODE
の値に対応します
ただし、プロジェクトの構造はそれほど洗練されていないように感じます (主な理由は、読み取りに影響するため、あまり多くのディレクトリ ファイルを更新したくないからです)。
公式 Web サイトで提供されている設定に従って、これらの環境設定ファイルを配置するフォルダーをカスタマイズできます。次に、ルート ディレクトリに新しいフォルダー env を作成します (このファイルの名前は、カスタマイズされ、値以下の envDir 設定に対応します)、すべての環境設定ファイルを
に移動し、 設定を変更vite.config.ts
する必要があり、envDir: 'env',
import {
fileURLToPath, URL } from 'node:url'
import {
defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vitejs.dev/config/
export default defineConfig({
// env配置文件变量前缀, 默认 VITE_,可自行定义
envPrefix: 'VITE_',
// env配置文件夹位置
envDir: 'env',
plugins: [vue(), vueJsx()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
例如 .env.development
配置
# 本地开发环境的代理配置配置, 任何代理请写好注释
# 接口地址
VITE_PROXY_API=http://localhost:8000
# 构建后文件引用 相对路径
VITE_PUBLIC_PATH=/
# 输出路径
VITE_OUT_DIR=/dist/
実行npm run dev
コンソール印刷import.meta.env
すると、.env
および .env.development
設定が表示されます。次のような情報
例:.env.sit
設定
# 本地开发环境的代理配置配置, 任何代理请写好注释
# 接口地址
VITE_PROXY_API=http://localhost:8001
# 构建后文件引用 相对路径
VITE_PUBLIC_PATH=/
# 输出路径
VITE_OUT_DIR=/dist/
実行npm run dev-sit
(もちろんビルド後npm run sit
実行後に出力される内容は同じです。他の環境でも同様です) a> の設定情報が表示されます。 a> 値は次のように開発環境の値と異なります。 と が表示され、
コンソールに import.meta.env
.env
.env.sit
VITE_PROXY_API
B. 実稼働ビルドを最適化した後、コンソールとデバッガーを閉じます。
通常、本番環境では関連情報をコンソールに出力することを避けるため、Vite にもこの種の構成の最適化機能があります。
import {
defineConfig } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
...
build: {
minify: "terser", // 必须开启:使用 terserOptions 才有效果
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
}
})
この構成の直後にビルドすると、エラーが報告されます。terser not found.
これをインストールする必要があることを確認するterser
。を実行できます。 terser をインストールした後、ビルドは再び成功しました。次に、ビルドされたコンテンツにアクセスすると、設定が有効になったことを示す が出力されていないことがわかります。 ただし、この設定では、テスト環境では印刷情報や判定ミスなどを見たいのですが、やはり問題があり、設定を変更して再度ビルドする必要があるのは明らかに不親切なので、実行時にのみ有効になるように少し最適化することができます。 環境、つまり、コールバック関数の戻りパラメータを使用して の設定を構成します (ソース コードを見ると、 defineConfig)、次のようにnpm i terser -D
console.log(import.meta.env)
prod
defineConfig
import {
defineConfig, loadEnv } from 'vite'
// https://vitejs.dev/config/
export default defineConfig(({
command, mode, ssrBuild }) => {
const env = loadEnv(mode, `${
process.cwd()}/env`, '')
// 为什么要这样做,是为了 process.env和mode一致性
Object.assign(process.env, env, {
NODE_ENV: mode })
return {
mode,
...
build: {
minify: "terser", // 必须开启:使用 terserOptions 才有效果
terserOptions: {
compress: {
drop_console: process.env.NODE_ENV === 'production' ? true : false, // 也可直接使用mode进行判断
drop_debugger: process.env.NODE_ENV === 'production' ? true : false,
},
},
}
}
})
パラメータが 3 つあることがわかります。ssrBuild
はオプションです
command: 'build' | 'serve'; // 二选一,默认为serve ,可在执行脚本配置(配置在vite 后面) 例如 "dev": "vite serve --mode development",
mode: string; // 自定义 一般会设置为 development | sit | uat | production
/**
* @experimental
*/
ssrBuild?: boolean; // 看起来应该是配置SSR模式使用的,后面有机会再ssr模式下详细记录下
上記の構成と組み合わせると、sit | uat
ビルド時には印刷が残りますが、production
ビルド時には印刷が消えます。これは、構成が成功したことを意味します。 。
それでは、process.env.NODE_ENV
を使用して直接判断してみてはいかがでしょうか。開発環境における process.env.NODE_ENV
のデフォルト値は development
であるため、 dev-sit
を実行しても となります。 >development
。スクリーンショットを見てみましょう。設定された--mode
に基づいてコールバック関数を使用して判断する必要があります。
C. ファイル参照パスを最適化する
多くの場合、異なるページで同じコンポーネントを使用する必要があり、導入されたパスに一貫性がない場合があります。どのようにすればそれらを統合できるでしょうか?実際、ビルド プロジェクトを初期化した後、フレームワーク自体はすでに最も基本的なファイル ポイントを生成しています。構成を見てください。
import {
fileURLToPath, URL } from 'node:url'
...
import {
defineConfig } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
...
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
...
})
は、defineConfig
構成resolve
内の関連する構成ポインタを指します。ポインタ./src
がニーズを満たさない場合は、自分でカスタマイズできます (@ の使用に慣れる必要があります。前提条件は、プロジェクトのディレクトリ構造を合理化することです。つまり、ネストを深くしすぎず、モジュールごとにディレクトリを分割しないことです)
使用方法は、必要なファイルにインポートします。たとえば、/src/components/index/demo.ts に /src/views/HomeView.vue ファイルを導入します
次のコードが表示されます。これは、 構成 を使用して、マルチレイヤー ../
ファイル導入エラーなどalias
@
./src
// import HomeView from '../../views/HomeView.vue'
import HomeView from '@/views/HomeView.vue'
D. プロジェクトのグローバル ファイル タイプ宣言構成の最適化 (ts のジェネリック タイプ)
.d.ts
ファイル。型宣言 (declare) に使用されます。これは型の検出にのみ使用され、TypeScript にどのような型があるかを伝えます。d
はい(宣言、ステートメント)declare module
** ステートメントファイルdeclare namespace
** 名前空間を宣言する
初期化によって作成されたルート ディレクトリには
env.d.ts
ファイルがあり、tsconfig.app.json
ファイルにインポートされます (設定されていない場合、一部のファイル インポートでレポートが表示されます)どこにも Declare it がないため、エラーになります。その中には独自の宣言があるものもあります。たとえば、document
はlib.dom.d.ts
ファイルで宣言されています。詳細については、ctrl
キーボードを押してマウスの左ボタンをクリックすると表示されます)
このような構成ファイルが複数ある場合は、tsconfig.app.json
ファイルに 1 つずつ手動で導入する必要があります。たとえば、新しいwechat.d.ts
ファイルを追加します。ファイル構成 a>weixin-js-sdk
グローバル。
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "wechat.d.ts"],
...
}
実際、env.d.ts
ファイルでこの構成を直接構成することもできますが、さらに多くの構成がある場合はどうなるでしょうか。多くの構成が一緒に存在します。特定の構成を変更または追加するときに、他の構成が誤って削除されてしまう可能性があります。この状況を回避するにはどうすればよいですか?
/// <reference types="vite/client" />
declare module 'weixin-js-sdk'
構成ファイルが で終わっていることがわかります。 では、上記の構成を最適化して、最初にルート ディレクトリに新しい構成を作成しましょう< /span>< a i=3> フォルダーに ファイルを置き、 ファイルを変更します (通常、 が変更されると、導入された位置で自動的に変更されます)。つまり、 は に変更されます。env.d.ts
.d.ts
types
env.d.ts
tsconfig.app.json
env.d.ts
"env.d.ts"
"types/env.d.ts"
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["types/env.d.ts", "src/**/*", "src/**/*.vue"],
...
}
次に、tsconfig.app.json
構成を最適化し"types/env.d.ts"
、"types/*.d.ts"
に変更します。つまり、タイプ内のすべてのアイテムを動的に導入します。フォルダー< i=4 で終わるファイル>、次のように.d.ts
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["types/*.d.ts", "src/**/*", "src/**/*.vue"],
...
}
新しい ・global.d.ts
/**
* 自定义全局类型
*/
declare module 'js-cookie'
type IfEquals<X, Y, A=X, B=never> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? A : B;
/** 获取可编辑key */
export type WritableKeys<T> = {
[P in keyof T]-?: IfEquals<{
[Q in P]: T[P];
}, {
-readonly [Q in P]: T[P];
}, P>
}[keyof T];
新規·shims-vue.d.ts
、.vue
形式ファイル、lodash
、さまざまなファイル形式など、いくつかの宣言ファイルを設定します。など
declare module '*.vue' {
import type {
DefineComponent } from 'vue';
const component: DefineComponent<{
}, {
}, any>;
export default component;
}
// declare module '*.vue' {
// import { DefineComponent } from 'vue'
// const component: DefineComponent<{}, {}, any>
// export default component
// }
/**
* window上属性自定义
*/
interface Window {
[key: string]: string | number | Types.Noop | Types.PlainObject | unknown;
WeixinJSBridge: any;
}
declare module '*.svg';
declare module '*.png';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.gif';
declare module '*.bmp';
declare module '*.tiff';
declare module '*.mp3';
declare module 'lodash';
declare module '@/utils/pictureVerfy/pawebjs.min.js';
ニーズに応じて上記を設定できます
E. CSS グローバル変数を構成する
ここでは処理量を減らし、次の依存関係サポートをインストールする必要があります。
npm i less -D
npm i less-loader -D
一般的なプロジェクトには、再利用される色やレイアウトの位置情報などが多数含まれます。現時点では、関数を使用するために、less を使用して、対応する変数を動的に構成できます。
まず、次のことを行う必要があります。新しい .less を作成します。 ファイルは に次の内容で作成されます./src/assets/
variables.less
/**
* 定义全局使用的 less 变量
*/
@paddingDefault: 32px;
/**
* 颜色值
*/
@a94442: #a94442;
次に、ファイルをグローバル プロジェクトに設定しますvariables.less
。そうしないと、これらの定義された変数を使用できなくなります
import {
fileURLToPath, URL } from 'node:url'
import {
resolve } from 'path'
import {
defineConfig} from 'vite'
// https://vitejs.dev/config/
export default defineConfig(({
command, mode, ssrBuild }) => {
...
return {
...
css: {
preprocessorOptions: {
// less: {
// javascriptEnabled: true,
// additionalData: `@import "${resolve(__dirname, 'src/assets/styles/base.less')}";`
// }
less: {
modifyVars: {
hack: `true; @import (reference) "${
resolve('src/assets/styles/variables.less')}";`,
},
javascriptEnabled: true
}
}
},
build: {
...
}
}
})
使用法: 上記の構成後、必要な場所で直接使用できます。
<div class="content"></div>
<style lang="less">
.content {
background: @a94442;
width: @paddingDefault;
height: @paddingDefault;
}
</style>
下の図に示すように、対応する値はvariables.less
ファイルに設定された値になります。
F. 新規フォルダーの作成(機能や内容が分かれています)
良い習慣とセマンティックなディレクトリ名は、プロジェクトのメンテナンスと開発者の読書気分に役立ちます
ディレクトリ構造が合理的かどうかも、今日のチームプロジェクト開発において非常に重要な部分です
まず、/src
ディレクトリの下に次のディレクトリを作成します (詳細な構造は下部にあります。興味がある場合は、直接下部までスクロールして表示できます)
- アプリ
- コンポーザブル
- 定数
- 実在物
- 列挙型
- インフラストラクチャー
- インターフェース
- ただ
- サーバ
- ユーティリティ
G. アクシオスの導入
- axios をインストールする
npm i axios -S
- アクシオスの紹介
1. /src/server
フォルダに新しい index.ts
ファイルと interface.ts
ファイルを作成します
新しい ファイルを /src
フォルダに作成してから、新しい ファイルをインターフェイス フォルダに作成します< a i=8 > 使用されているので、次のようにインストールする必要があります (他の構成はカスタマイズできます)interface
index.ts
lodash-es
npm i lodash-es -D
/src/server/index.ts
import axios, {
type AxiosRequestConfig } from 'axios'
import {
assign } from 'lodash-es'
import {
ElMessage as message } from 'element-plus'
const UN_SUPPORT_DIY_HEADER_REGEX = /^http(s)?:\/\//i
// 请求错误统一处理
import ERRORCODES from '@/enum/error-code'
import {
resetInterfacePath } from '@/utils'
// 默认请求失效时间60s
export const AXIOS_TIMEOUT_LIMIT = 60000
axios.defaults.timeout = AXIOS_TIMEOUT_LIMIT;
import type {
NUNBER_STRING as ERROR_CODES_TYPES } from '@/interface'
// 也可以直接使用 typeof 获取 ERROR_CODES 的接口类型,这个时候需要ERROR_CODES 在同一文件内才有效果
// type ERROR_CODES_TYPES = typeof ERROR_CODES
const ERROR_CODES = ERRORCODES as ERROR_CODES_TYPES
/**
* 后台接口公共的返回格式
* 具体根据实际跟后台约定的定义
*/
export interface ResCommonType<T = unknown> {
code: number
data: T
msg?: string
}
// 请求拦截
axios.interceptors.request.use(
(config) => {
/**
* Request header not allowed by Access-Control-Allow-Headers in preflight response
* 第三方接口不支持头
*/
config.url = resetInterfacePath(config.url || '')
if (!UN_SUPPORT_DIY_HEADER_REGEX.test(config.url ?? '')) {
assign(config.headers, {
// 'X-RequestFrom': 'person',
})
}
// if (config?.url?.includes('/DownloadFile')) {
// assign(config.headers, {
// 'Accept': 'ext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
// 'Content-Type': 'application/x-www-form-urlencoded',
// 'responseType': 'blob'
// })
// }
return config
},
(err) => Promise.reject(err),
)
// 响应拦截
axios.interceptors.response.use(
(response) => {
if (typeof response.data === 'string') {
// location.href = '/signIn'
// return Promise.reject('登录失效')
}
const data = response.data
const resCode: keyof ERROR_CODES_TYPES = data.status || data.code
console.log('ERROR_CODES[resCode]', ERROR_CODES[resCode])
if (ERROR_CODES[resCode]) {
return Promise.reject(data)
}
return Promise.resolve(data)
},
(err) => {
let errCode: keyof ERROR_CODES_TYPES = 500
let errMsg = err?.message || '连接到服务器失败'
if (err?.response) {
const {
code, status } = err.response
errCode = code || status || 500
errMsg = ERROR_CODES[errCode]
}
console.log('ERROR_CODES[]', errCode, ERROR_CODES[errCode])
message.error(errMsg)
return Promise.reject({
code: errCode,
msg: errMsg,
data: err || null,
})
},
)
/**
* 发起GET请求, 泛型 T 定义返回数据 data 项类型, U 定义请求数据类型
* @param {string} url 请求链接
* @param {object} params 请求参数
* @param {object} config 配置
*/
export const get = <U = unknown, T = unknown>(
url: string,
params?: U,
config?: AxiosRequestConfig,
) => axios.get<T, T>(
url, {
params: {
...params, t: Date.now() }, ...config },
)
/**
* 发起POST请求, 泛型 T 定义返回数据 data 项类型, U 定义请求数据类型
* @param {string} url 请求链接
* @param {object} params 请求参数
* @param {object} config 配置
*/
export const post = <U = unknown, T = unknown>(
url: string,
params?: U,
config: AxiosRequestConfig = {
},
) => {
if (Array.isArray(params)) {
return axios.post<T, T>(url, [...params], config)
}
return axios.post<T, T>(url, {
...params }, config)
}
/**
* 发起FormData请求, 泛型 T 定义返回数据 data 项类型, U 定义请求数据类型
* @param {string} url 请求链接
* @param {object} params 请求参数
* @param {object} config 配置
*/
// export const postForm = <U = unknown, T = unknown>(
// url: string,
// params?: U,
// config: AxiosRequestConfig = {},
// ) => axios.post<T, ResCommonType<T>>(url, qs.stringify({ ...params }), config);
export const postForm = <U = unknown, T = unknown>(
url: string,
params?: U,
config: AxiosRequestConfig = {
},
) => axios.post<T, T>(url, params, config)
/**
* 文件下载请求, 泛型 T 定义返回数据 data 项类型, U 定义请求数据类型
* @param {string} url 请求链接
* @param {object} params 请求参数
* @param {object} config 配置
*/
// export const postFile = <U = unknown, T = unknown>(
// url: string,
// params?: U,
// config: AxiosRequestConfig = { responseType: 'blob' },
// ) => axios.post<T, ResCommonType<T>>(url, { ...params }, config);
export default {
get,
post,
// postForm,
// postFile,
}
/src/server/interface.ts
以下の通り(インターフェースの共通インターフェース定義部分)
export interface BaseResType {
ResultCode: number
ResultDescription: string
}
export interface ApiResponseType<T> extends BaseResType{
result: T
}
2. /src/enum
フォルダに新しい error-code.ts
ファイルを作成します (後で複数の言語に設定します)
// 请求错误统一处理
const ERROR_CODES = {
400: '错误请求,状态码:400',
401: '未授权,请重新登录,状态码:401',
403: '拒绝访问,状态码:403',
404: '请求错误,未找到该资源,状态码:404',
405: '请求方法未允许,状态码:405',
408: '请求超时,状态码:408',
500: '服务器端出错,状态码:500',
501: '网络未实现,状态码:501',
502: '网关错误,状态码:502',
503: '服务不可用,状态码:503',
504: '网络超时,状态码:504',
505: 'HTTP版本不支持该请求,状态码:505',
}
export default ERROR_CODES
3. /src/utils
フォルダに新しい index.ts
ファイルを作成します(プロジェクト全体に共通のメソッドなど)
import type {
unKnow } from "@/interface";
/**
* 拼接接口路径,例如代理接口的时候后台接口前缀不统一等,可以自定义个前缀,有助于代理配置。
* @param url 接口路径(一般只会配置相对路径,也可直接配置绝对路径)
* @returns
*/
export const resetInterfacePath = (url: string) => {
// return `/api/${url}`
return url
}
/**
* 对象转formData
* @param data
*/
export const objectToFormData = (data: object): FormData => {
let formData = new FormData();
for (const [key, value] of Object.entries(data)) {
if (value !== null && value !== undefined) {
formData.append(key, value);
} else {
formData.append(key, '');
}
}
return formData;
};
/**
* 对象转数组
* @param data
*/
export const objectToArray = (data: object): Array<Object> => {
let arr: Array<Object> = []
for (const [key, value] of Object.entries(data)) {
if (value !== null && value !== undefined) {
arr.push(value)
}
}
return arr;
};
/**
* 地址数据转换
* @param data
* @returns
*/
export const translateAddress = (data: any) => {
if (data instanceof Object && !Array.isArray(data)) {
data = objectToArray(data)
data = data.map((item: any) => {
return {
value: item.code,
label: item.name,
children: translateAddress(item.node)
}
})
}
return data
}
interface Obj {
[key: string]: string | number;
}
/**
* 根据对象value获取key
* @param obj
* @param value
* @returns
*/
export const objectGetKeyForValue = (obj: Obj | undefined, value: string | number): string => {
if (!obj) {
return ''
}
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const element = obj[key];
if (value === element) {
return key
}
}
}
return ''
}
/**
* 根据数组中子对象value获取对应label
* @param arr
* @param value
* @returns
*/
export const arrayGetLabelForValue = (arr: Array<unKnow> | undefined, value: string | number): string => {
if (!arr?.length) {
return ''
}
let label = ''
arr.forEach(element => {
if (element.value === value) {
label = element.label
}
});
return label
}
4. /src/interface/index.ts
(プロジェクト全体の interface
インターフェイス定義、つまりパブリック部分) を追加します
export interface unKnow {
[key: string]: any;
}
export interface Undefined {
[key: string]: undefined;
}
export interface NUNBER_STRING{
[key: number]: string;
}
export interface OBJ_STRING {
[key: string]: string;
}
設定後axios
、その設定が利用可能かどうかを確認する必要があります。
インターフェイス API を例として挙げます。/user/login
(他のインターフェイスは、このロジックに従って順番に構成できます)
それでは、やってみましょう< /span> と としてディレクトリに新しいフォルダーを作成し、ここに 2 つの新しいファイルを作成します/src/server
API のプレフィックス部分であるuser
index.ts
interface.ts
index.ts
書類
import {
post, get, postForm } from '@/server/index';
import {
objectToFormData } from '@/utils'
import type {
LoginParamsType, LoginResType
} from './interface';
/**
* 登录
* @param params
*/
export const login = (params: LoginParamsType) => {
return post<LoginParamsType, LoginResType>('/user/login', params)
}
// export const login = (params: LoginParamsType) => {
// return get<LoginParamsType, LoginResType>('/user/login', params)
// }
// export const login = (params: LoginParamsType) => {
// return postForm<FormData, LoginResType>('/user/login', objectToFormData( params ))
// }
interface.ts
書類
/**
* 登录参数
*/
export interface LoginParamsType {
}
/**
* 登录返回结果
*/
export interface LoginResType {
}
その後、次のように、このインターフェイスを呼び出す必要がある場所で直接使用できます。
import {
login } from '@/server/user'
login({
})
インターフェイスが呼び出されていることがわかります。API がまだオープンされていないため、404 です。ただし、インターフェイス サービスが有効になっている場合でも、クロスドメインの状況が発生します。次のステップが詳細に記録されます。
H. プロジェクト開発で必然的に遭遇するクロスドメインの問題
ブラウザが API を呼び出すにはクロスドメインが避けられないため、同一オリジン ポリシーとクロスドメイン ソリューションについても以前説明しました。
ここでは主にローカル開発における Vite エージェント ソリューション スパン ソリューションについて説明します
多くの場合、バックエンド API には異なる API プレフィックスが付いています。もちろん、一部のバックエンド ボスはすべて次で始まります。 /api (プロキシ構成を支援)、フロントエンド自体も、Api スプライシング/api をプレフィックスとして自動的に構成できます
ファイルが長すぎるためvite.config.ts
見たくないため、新しいbuild
フォルダを作成して<このフォルダの下に i=3>ファイルproxy.ts
const fs = require('fs');
const path = require('path');
/**
* 自动添加代理
* 遍历mock代理配置 START
*/
const getProxyConfig = () => {
// 读取 ../src/server 下的目录作为代理的根目录
const mockFiles = fs.readdirSync(path.resolve(__dirname, '../src/server'));
const proxyMap = {
};
mockFiles.forEach((fileName) => {
if (!fileName.includes('.ts')) {
proxyMap[`^/${
fileName}`] = {
target: process.env.VITE_PROXY_API,
ws: true,
secure: true,
changeOrigin: true,
};
}
});
/**
* 统一以 /api 为前缀(也可为其他自定义,这里以 /api 为例)
* 如果后端Api没有此前缀,前端可自行在调用接口路径前拼接 /api 例如 后台为 /login ,在实际调用接口改为 /api/login(即当前构建配置的resetInterfacePath方法处理)
* 不过不想手动拼接/api前缀,也可直接在server目录下新建 api 文件夹,把对应配置的接口放入即可,按照mockFiles的配置会直接配置上的,不过的注意 rewrite 问题哦
* 所以这里直接写上 /api 的代理配置
*/
proxyMap[`^/api`] = {
target: process.env.VITE_PROXY_API, // 这个配置用到了上面env的环境配置字段
ws: true,
secure: true,
changeOrigin: true,
// 如果后端没有这个前缀,需要重写"/api"为 ""
rewrite: (path: string) => path.replace(/^\/api/, ""),
bypass(req: any, res: any, options: any) {
// 查看代理前路径
const proxyUrl = new URL( options.rewrite(req.url) || '', (options.target) as string)?.href || ''
res.setHeader("x-req.proxyUrl", proxyUrl)
}
};
console.log('proxyMap_proxyMap', proxyMap)
return proxyMap;
};
module.exports = getProxyConfig;
然后在vite.config.ts
文件配置proxy
即可
import {
fileURLToPath, URL } from 'node:url'
import {
defineConfig, loadEnv } from 'vite'
const getProxyConfig = require('./build/proxy');
// https://vitejs.dev/config/
export default defineConfig(({
command, mode }) => {
const env = loadEnv(mode, `${
process.cwd()}/env`, '')
Object.assign(process.env, env, {
NODE_ENV: mode })
return {
mode,
...
server: {
// host设置为true才可以使用network的形式,以ip访问项目
host: true,
// 本地编译后访问的端口号
port: 8082,
// 编译后是否自动打开浏览器访问
open: true,
hmr: {
overlay: false
},
// 跨域设置允许
cors: false,
// 如果端口已占用直接退出
strictPort: false,
// 设置代理
proxy: getProxyConfig()
},
...
}
})
これについて言えば、ローカルで開発およびコンパイルする場合、アクセス ポートは5173
デフォルトであり、http://localhost:5173/
上記で確認できることにも触れておきます。< /span>を設定するだけです。詳細については、上記の注記server
に値port
I. 多言語構成
多言語システムのWebサイトも一般的ですので、多言語システムの構築も行います。
複数の言語で一般的に使用されるプラグインにはi18n
があり、vue フレームワークに適したプラグインにはvue-i18n
があります。最初にインストールしてください。 をインストールする必要があります。そうしないとエラーが発生します (もちろん、手書き設定を使用して Cookie を直接設定することもできます) と を使用します。 、Cookie を使用して現在の表示言語を記録します。TS 以降、環境で使用するには 2 つの依存関係 npm i vue-i18n -S
。通常、言語を切り替えた後、ページの更新やその他の操作では、切り替えた言語システムを保持する必要があります。キャッシュ レコードを使用できます。ここでは、キャッシュ設定に js-cookie
js-cookie
@types/js-cookie
npm i @types/js-cookie -D
npm i js-cookie -D
インストールするだけの場合@types/js-cookie
、次のエラーが表示されます
次に、/src/lang
フォルダの下に次のファイル(フォルダ)を作成します。
- 新建
index.ts
文件 - に対応する言語フォルダーは、中国語と英語を例として、
zh-CN
フォルダーとen-US
フォルダーを作成し、index.ts
ドキュメント - アプリケーション用のサードパーティ リソース ライブラリを作成します (たとえば、新しい
element-plus.ts
ファイルを作成します)
/src/lang/index.ts
ファイル (ここでは具体的な構成については詳しく説明しません。elememt-plus
の多言語構成を含むコードを参照してください)
import {
createI18n } from 'vue-i18n'
import Cookies from 'js-cookie'
import elementPlus from './element-plus'
import enLocale from './en-US/index'
import zhLocale from './zh-CN/index'
const messages = {
'zh-CN': zhLocale,
'en-US': enLocale
}
/**
* 设置语言环境
* @param lang 语言环境
*/
export function setLanguage(lang: string) {
Cookies.set('language', lang || 'zh-CN')
}
/**
* 获取配置环境
* @returns
*/
export function getLanguage() {
// const bool = true
// if (bool) {
// return 'en'
// }
const chooseLanguage = Cookies.get('language')
if (chooseLanguage) return chooseLanguage
// 如果有需要也可以根据当前用户设备浏览器默认语言
// const language = navigator.language.toLowerCase() // IE10及IE10以下的版本 使用 navigator.browserLanguage
// const locales = Object.keys(messages)
// for (const locale of locales) {
// if (language.indexOf(locale.toLowerCase()) > -1) return locale
// }
return 'en-US'
}
const i18n = createI18n({
globalInjection: true, // 全局生效$t
locale: getLanguage(), // getLanguage()
messages,
legacy: false
})
export const elementPlusLocale = elementPlus
export const lang = () => {
const lang = getLanguage()
switch (lang) {
case 'zh-CN':
return messages['zh-CN']
case 'en-US':
return messages['en-US']
}
return messages['zh-CN']
}
export default i18n
/src/lang/zh-CN.index.ts
書類
export default {
ErrorPageTips: `抱歉!您访问的页面失联啦`
}
/src/lang/en-US/index.ts
書類
export default {
ErrorPageTips: `I'm sorry! The page you visitedis lost`
}
/src/lang/element-plus.ts
ファイル (その他の設定方法は公式 Web サイトで直接確認できます)
import elementZhLocale from 'element-plus/lib/locale/lang/zh-CN'
import elementEnLocale from 'element-plus/lib/locale/lang/en'
import {
getLanguage } from './index'
export default () => {
const lang = getLanguage()
switch (lang) {
case 'zh-CN':
return elementZhLocale
case 'en-US':
return elementEnLocale
}
}
上記のコンテンツを作成して構成した後、main.ts
ファイルに直接適用できます。
import {
createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/theme-chalk/index.css'
import App from '@/App/index.vue'
import i18n, {
elementPlusLocale } from './lang'
...
// import VConsole from 'vconsole';
// 设置语言变量
// import { setLanguage } from '@/lang/index'
// setLanguage('zh-CN')
// const vConsole = new VConsole() as any
const app = createApp(App)
...
// .use(vConsole)
.use(ElementPlus, {
locale: elementPlusLocale() })
.use(i18n)
.mount('#app')
を構成し、言語構成を参照すると、使用できるようになります。
たとえば、 を
html
に直接使用します。
<div>{
{$t('ErrorPageTips')}}</div>
または、ロジックを作成するときに使用します (セットアップ内で使用する必要があることに注意してください)
import {
defineComponent } from 'vue'
import {
useI18n } from 'vue-i18n'
export default defineComponent({
...
setup() {
const {
t } = useI18n()
console.log(t('ErrorPageTips'))
return {
... }
}
}
上記の操作に従って、多言語設定が実装されます。言語の切り替えについては、切り替える必要があるボタンで
setLanguage
メソッドを直接トリガーできます
ここで複数の言語が設定されたので、/src/enum
フォルダerror-code.ts
にある上記のファイルを変更する必要があります。まずここに新しいフォルダを作成してください。 index.ts
import {
getLanguage } from "@/lang"
/**
* 获取枚举
* @param ZHCN 中文枚举
* @param EN 英文枚举
* @returns
*/
export const getEnum = (ZHCN: unknown, EN: unknown) => {
const lang = getLanguage()
switch (lang) {
case 'zh-CN':
return ZHCN
case 'en-US':
return EN
default :
return {
}
}
}
次に、error-code.ts
ファイルを変更して、ロケールに従って対応する列挙値データを取得できるようにします (他の列挙にも同じことが当てはまります)
import {
getEnum } from "./index"
export const errorCodeEN = {
400: 'Error request, status code:400',
401: 'Unauthorized, please login again, status code:401',
403: 'Access denied, status code:403',
404: 'Request error, the resource was not found, status code:404',
405: 'Request method not allowed, status code:405',
408: 'Request timeout, status code:408',
500: 'Server side error, status code:500',
501: 'Network not implemented, status code:501',
502: 'Gateway error, status code:502',
503: 'Service unavailable, status code:503',
504: 'Network timeout, status code:504',
505: 'The HTTP version does not support this request, status code:505',
}
export const errorCodeCN = {
400: '错误请求,状态码:400',
401: '未授权,请重新登录,状态码:401',
403: '拒绝访问,状态码:403',
404: '请求错误,未找到该资源,状态码:404',
405: '请求方法未允许,状态码:405',
408: '请求超时,状态码:408',
500: '服务器端出错,状态码:500',
501: '网络未实现,状态码:501',
502: '网关错误,状态码:502',
503: '服务不可用,状态码:503',
504: '网络超时,状态码:504',
505: 'HTTP版本不支持该请求,状态码:505',
}
export default getEnum(errorCodeCN, errorCodeEN)
K. 状態管理の導入(pinia、Vuex)
公式 Web サイトでは pinia を推奨しているので、直接使用しましょう
まず、初期化されたテンプレートが使用されているかどうかを確認できます。pinia
キャッシュが残っていないことを確認します。デフォルトで使用されます。 操作は特定のページに導入でき/src/stores/counter.ts
、書き込み方法は開発を組み合わせて
、対応するフィールド値を変更し、別のルートにジャンプします。そして、導入/src/stores/counter.ts
を繰り返して、対応するフィールドを取得します。データが変更されたばかりの値であることがわかります
。たとえば、ルーティング< a i=7> はそれぞれ以下のコードを添付します。 /a
/b
import {
storeToRefs } from "pinia"
import {
useCounterStore} from '@/stores/counter'
// 如果此代码是抽离到了单独ts文件内,那下面部分需要再 放置 setup 内
// 变量需要使用 storeToRefs 转为响应式数据
const {
count } = storeToRefs(useCounterStore())
// 方法直接使用即可
const {
increment } = useCounterStore()
increment()
console.log('count', count)
図に示すように、/a
/b
のルーティングを切り替えると、count
の値が重なっていることがわかります。 、 データが共有されていることを示します
キャッシュ要件がある場合は、別のpinia-plugin-persistedstate
npm i pinia-plugin-persistedstate -S
をインストールして、main.ts
ファイルにプラグインを導入することもできます
import {
createApp } from 'vue'
import App from '@/App/index.vue'
import {
createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import './assets/styles/app.less'
...
const app = createApp(App)
app.use(
createPinia()
.use(piniaPluginPersistedstate)
)
...
.mount('#app')
キャッシュを使用する場合は、定義方法が異なるため、複合開発はできません。たとえば、/src/stores
配下に mapState.ts
ファイルを新規作成します。特定の記述方法のコードを次のように直接記述します。
import {
defineStore } from 'pinia'
export interface MapState {
address: string;
}
const {
SIS_STORE_NAME } = import.meta.env
export const useMapStore = defineStore(SIS_STORE_NAME + 'map', {
state: (): MapState => ({
address: '',
}),
getters: {
},
actions: {
setAdress(address: string) {
this.address = address;
},
clearMessage() {
this.address = '';
},
},
persist: {
/**
* 使用的存储
* @default $store.id
*/
key: SIS_STORE_NAME + 'map',
/**
* 存储位置
* @default localStorage
*/
storage: sessionStorage,
/**
* 需要设置缓存的state 如果未设置即存储所有state
* @default undefined
*/
// paths: [],
/**
* 存储之前
* @default null
*/
beforeRestore: () => {
},
/**
* 存储之后
* @default undefined
*/
afterRestore: () => {
},
/**
* 启用时在控制台中记录错误。
* @default false
*/
debug: true
},
});
それの使い方
/a
ルート設定値を確認し、キャッシュを確認します (現在はsessionStorage
です)
import {
useMapStore} from '@/stores/mapState'
// 如果此代码是抽离到了单独ts文件内,那下面部分需要再 放置 setup 内
// 变量需要使用 storeToRefs 转为响应式数据
const {
setAdress } = useMapStore()
setAdress('验证地区')
無事に設定されていることがわかります
/b
ルート取得値
import {
useMapStore} from '@/stores/mapState'
// 如果此代码是抽离到了单独ts文件内,那下面部分需要再 放置 setup 内
// 变量需要使用 storeToRefs 转为响应式数据
const {
address } = useMapStore()
console.log('address', address)
印刷されたデータが上記で設定した値であることがわかります (ページを更新せずに、mapState.ts
ファイルのキャッシュをオフにして値を取得することもできます)
J. オンデマンドでリソースを紹介する
たとえば、プロジェクトではEcharts
を使用します。これを完全に導入すると、構築後のファイル サイズが増加し、ほとんどのプロジェクトでは Echarts のグラフィックスのほとんどを使用できなくなります。今回は、必要に応じてプロジェクトで使用する部品をインポートする必要があります。
以前に Echarts の使用(オンデマンドで最適化された導入) という記事を書いたので、必要であれば、ここで読むことができます は
K. 最適化された構造
構築の最適化には、実際には、オンデマンドのリソース、画像圧縮、JS 圧縮などの導入が含まれます。
この最適化は、パッケージ化最適化の Vite 構築 (ビュー分析、CDN 導入、依存関係のサブコントラクト、gzip 圧縮) の前に個別に書かれたものでもあり、興味があります。友達は非同期で読むことができます
3. プロジェクト構造を再度最適化する
この時点で、構成の最適化が必要な部分は基本的に完了しました。
もちろん、上で説明したものに加えて、次のようなフォルダー定義の仕様もあります。
App
プロジェクトルーティングエントリの抽出composables
パブリックフック機能entity
エンティティ(クラス)interface
インターフェースタイプの定義
…等
私のプロジェクトの習慣によれば、プロジェクトの構造は次のとおりです (後で時間をかけて、この構造で単純な完全な構成プロジェクトを構築し、アップロードします。興味のある友人は、上記の手順に従って、独自のカスタム テンプレートを段階的に構成することもできます) )
└── build // 构建抽离
├── proxy.js // 接口代理配置
└── docs // 项目文档说明
├── user-url.json // 项目测试连接账号
└── env // 环境变量相关配置
├── .env // 公用变量配置
├── .env.development // 开发环境变量配置
├── .env.production // 生产环境变量配置
├── .env.sit // 测试环境变量配置
├── .env.uat // 业务验收环境变量配置
└── public // 静态资源(无需经过构建内容)
└── src
├── App // 项目路由入口
└── assets // 静态文件
├── dic // 字段库
├── images // 图片
├── js // js库
├── styles // 样式库
└── components // 公用组件
└── composables // 公用Hooks函数
└── const // 常量定义
└── entity // 实体(class类)-- 页面级别
└── enum // 枚举值
└── infrastructure // 基础设施实体(class类) -- 项目级别(如配置数据、人脸、定位等功能相关实体)
└── interface // 类型定义
└── lang // 多语言
└── router // 路由配置
└── server // 接口
└── sotres // 状态管理(缓存)
└── types // 自定义全局类型
└── utils // 常用方法
└── views // 页面
└── main.js // Vue配置入口
├── .eslintrc.cjs // ESLint 规则配置
├── index.html // 项目单页入口
├── package.json // 项目依赖
要約する
プロジェクトの構築にはほとんど常に Webpack を使用していたため、gulp を使用することもありました。 。私は開発プロジェクトで Vue3 を使い始めたのはここ 2 年ほどなので、構築期間中の Vite の使用状況を特別に記録しました。上記の手順に従って設定してください。また、今後は Webpack 構築プロジェクトに関する記事を書くことに時間を費やしていきますので、今後の更新を楽しみにしています~