Typescript+Vue大型項目構建+詳細講解

寫在前面

在開擼以前,先把文檔網址貼一貼javascript

Typescript 中文手冊css

vue-property-decoratorhtml

vuex-class前端

vue-class-componentvue

先來認識下面的小玩意

vue-property-decorator

這裏單頁面組件的書寫採用的是 vue-property-decorator 庫,該庫徹底依賴於 vue-class-component ,也是 vue 官方推薦的庫。java

單頁面組件中,在 @Component({}) 裏面寫 propsdata 等調用起來極其不方便,而 vue-property-decorator裏面包含了 8 個裝飾符則解決了此類問題,他們分別爲:node

  • @Emit 指定事件emit,可使用此修飾符,也能夠直接使用 this.$emit()
  • @Inject 指定依賴注入
  • @Mixins mixin 注入
  • @Model 指定 model
  • @Prop 指定 Prop
  • @Provide 指定 Provide
  • @Watch 指定 Watch
  • @Component export from vue-class-component

舉個🌰webpack

import {
  Component, Prop, Watch, Vue
} from 'vue-property-decorator'

@Component
export class MyComponent extends Vue {
  dataA: string = 'test'
  count = 0
  
  @Prop({ default: 0 }) private propA!: number
  @Prop({ default: () => [10, 20, 30, 50] }) private propB!: number[]
  @Prop({ default: 'total, sizes, prev, pager, next, jumper' }) private propC!: string
  @Prop({ default: true }) private propD!: boolean,
  @prop([String, Boolean]) propE: string | boolean;
  
  @Emit('reset')
  resetCount() {
    this.count = 0
  }
  @Emit()
  returnValue() {
    return 10
  }
  @Emit()
  onInputChange(e) {
    return e.target.value
  }
  
  // watcher
  @Watch('child')
  onChildChanged (val: string, oldVal: string) {}
  @Watch('person', { immediate: true, deep: true })
  onPersonChanged (val: Person, oldVal: Person) {}

  // 其餘修飾符詳情見上面的 github 地址,這裏就不一一作說明了
}
複製代碼

解析以後會變成ios

export default {
  data () {
    return {
      dataA: 'test'
    }
  },
  props: {
    propA: {
	  type: Number
    },
    propB: {
      type: Array,
      default: [10, 20, 30, 50]
    },
    propC: {
      type: String,
      default: 'total, sizes, prev, pager, next, jumper'
    },
    propD: {
      type: String,
      default: 'total, sizes, prev, pager, next, jumper'
    },
    propE: {
      type: [String, Boolean]
  },
    
  watch: {
    'child': {
      handler: 'onChildChanged',
      immediate: false,
      deep: false
    },
    'person': {
      handler: 'onPersonChanged',
      immediate: true,
      deep: true
    }
  },
  methods: {
     resetCount() {
      this.count = 0
      this.$emit('reset')
    },
    
    returnValue() {
      this.$emit('return-value', 10)
    },
    
    onInputChange(e) {
      this.$emit('on-input-change', e.target.value, e)
    }
 
    onChildChanged (val, oldVal) {},
    onPersonChanged (val, oldVal) {}
  }
}
複製代碼

這裏有兩個經常使用修飾符!``?,!和可選參數?是相對的, !表示強制解析(也就是告訴typescript編譯器,我這裏必定有值),你寫?的時候再調用,typescript會提示可能爲undefinedgit

@Emit

@Emit裝飾器的函數會在運行以後觸發等同於其函數名(駝峯式會轉爲橫槓式寫法)的事件, 並將其函數傳遞給$emit

  • @Emit()不傳參數,那麼它觸發的事件名就是它所修飾的函數名.
  • @Emit(name: string),裏面傳遞一個字符串,該字符串爲要觸發的事件名

@Watch

watch 是一個對象,對象就有鍵,有值。

  • 第一個handler:其值是一個回調函數。即監聽到變化時應該執行的函數。
  • 第二個是deep:其值是truefalse;確認是否深刻監聽。deep的意思就是深刻觀察,監聽器會一層層的往下遍歷,給對象的全部屬性都加上這個監聽器(受現代 JavaScript 的限制 (以及廢棄 Object.observe),Vue不能檢測到對象屬性的添加或刪除)
  • 第三個是immediate:其值是truefalseimmediate:true表明若是在 wacth 裏聲明瞭以後,就會當即先去執行裏面的handler方法,若是爲 false就跟咱們之前的效果同樣,不會在綁定的時候就執行

@Watch使用很是簡單,接受第一個參數爲要監聽的屬性名, 第二個屬性爲可選對象。@Watch所裝飾的函數即監聽到屬性變化以後應該執行的函數。 @Watch裝飾的函數的函數名並不是如上onStateChanged嚴格命名,它是多元化的,你能夠爲所欲爲的命名,固然,能按照規範化的命名會使你的代碼閱讀性更好。

@Minxins

// myMixin.ts
import { Vue, Component } from 'vue-property-decorator';
declare module 'vue/types/vue' {
    interface Vue {
        mixinValue: string;
    }
}
@Component
export default class myMixins extends Vue {
    mixinValue: string = 'Hello World!!!'
}
複製代碼

引用

import { Vue, Component, Prop } from 'vue-property-decorator';
import MyMixin from './myMixin.js'

@Component({
    mixins: [MyMixin]
})
export default class extends Vue{
    created(){
        console.log(mixinValue) // => Hello World!!!
    }
}
複製代碼

mixin另外一寫法,在下面會有出現。

@Model

@Model裝飾器容許咱們在一個組件上自定義v-model,接收兩個參數:

  • event: string 事件名。
  • options: Constructor | Constructor[] | PropOptions 與@Prop的第一個參數一致。
import { Vue, Component, Model } from 'vue-property-decorator'

@Component
export default class MyInput extends Vue {
  @Model('change', { type: String, default: 'Hello world!!!' }) readonly value!: string
}
複製代碼

等同於

<template>
  <input
    type="text"
    :value="value"
    @change="$emit('change', $event.target.value)"
  />
</template>

export default {
  model: {
    prop: 'value',
    event: 'change'
  },
  props: {
    value: {
      type: String,
      default: 'Hello world!!!'
    }
  }
}
複製代碼

@Provide @Inject

@Provide 聲明一個值 , 在其餘地方用@Inject 接收,在實戰項目中用得很少,通常用於不依賴於任何第三方狀態管理庫(如vuex)的組件編寫

@Ref(refKey?: string)

@Ref裝飾器接收一個可選參數,用來指向元素或子組件的引用信息。若是沒有提供這個參數,會使用裝飾器後面的屬性名充當參數

import { Vue, Component, Ref } from 'vue-property-decorator'
import { Form } from 'element-ui'

@Componentexport default class MyComponent extends Vue {
  @Ref() readonly loginForm!: Form
  @Ref('changePasswordForm') readonly passwordForm!: Form

  public handleLogin() {
    this.loginForm.validate(valide => {
      if (valide) {
        // login...
      } else {
        // error tips
      }
    })
  }
}
複製代碼

等同於

export default {
  computed: {
    loginForm: {
      cache: false,
      get() {
        return this.$refs.loginForm
      }
    },
    passwordForm: {
      cache: false,
      get() {
        return this.$refs.changePasswordForm
      }
    }
  }
}
複製代碼

vuex-class

vuex-class 是一個基於 VueVuexvue-class-component 的庫,和 vue-property-decorator 同樣,它也提供了4 個修飾符以及 namespace,解決了 vuex.vue 文件中使用上的不便的問題。

  • @State
  • @Getter
  • @Mutation
  • @Action
  • namespace
import Vue from 'vue'
import Component from 'vue-class-component'
import {
  State,
  Getter,
  Action,
  Mutation,
  namespace
} from 'vuex-class'

const someModule = namespace('path/to/module')

@Component
export class MyComp extends Vue {
  @State('foo') stateFoo
  @State(state => state.bar) stateBar
  @Getter('foo') getterFoo
  @Action('foo') actionFoo
  @Mutation('foo') mutationFoo
  @someModule.Getter('foo') moduleGetterFoo

  // If the argument is omitted, use the property name
  // for each state/getter/action/mutation type
  @State foo
  @Getter bar
  @Action baz
  @Mutation qux

  created () {
    this.stateFoo // -> store.state.foo
    this.stateBar // -> store.state.bar
    this.getterFoo // -> store.getters.foo
    this.actionFoo({ value: true }) // -> store.dispatch('foo', { value: true })
    this.mutationFoo({ value: true }) // -> store.commit('foo', { value: true })
    this.moduleGetterFoo // -> store.getters['path/to/module/foo']
  }
}
複製代碼

搭建環境

建立項目

? Please pick a preset:(使用上下箭頭)
 ◯ default (babel, eslint)        //默認配置
❯◉ Manually select features       //手動選擇
複製代碼
? Check the features needed for your project:
 ◉ Babel                                    // javascript轉譯器
 ◉ TypeScript                               // 使用 TypeScript 書寫源碼
 ◯ Progressive Web App (PWA) Support        // 漸進式WEB應用
 ◉ Router                                   // 使用vue-router
 ◉ Vuex                                     // 使用vuex
 ◉ CSS Pre-processors                       // 使用css預處理器
❯◉ Linter / Formatter                       // 代碼規範標準
 ◯ Unit Testing                             // 單元測試
 ◯ E2E Testing                              // e2e測試
複製代碼

是否使用class風格的組件語法: 使用前:home = new Vue()建立vue實例 使用後:class home extends Vue{}

? Use class-style component syntax? (Y/n) Y

// 使用Babel與TypeScript一塊兒用於自動檢測的填充
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n) Y

// 路由
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y

// 預處理器
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
❯◉ Sass/SCSS (with dart-sass)    // 保存後編譯
 ◯ Sass/SCSS (with node-sass)    // 實時編譯 
 ◯ Less
 ◯ Stylus

// 代碼格式化檢測
? Pick a linter / formatter config: (Use arrow keys)
 ◯ ESLint with error prevention only     // 只進行報錯提醒
 ◯ ESLint + Airbnb config                // 不嚴謹模式
 ◯ ESLint + Standard config              // 正常模式
 ◯ ESLint + Prettier                     // 嚴格模式
❯◉ TSLint(deprecated)                    // typescript格式驗證工具

// 代碼檢查方式
? Pick additional lint features: (Press <space> to select, <a>
to toggle all, <i> to invert selection)
❯◉ Lint on save             // 保存檢查
 ◯ Lint and fix on commit   // commit時fix

// 文件配置
? Where do you prefer placing config for Babel, ESLint, etc.? (
Use arrow keys)
  In dedicated config files // 配置在獨立的文件中
❯ In package.json
  
// 保存上述配置,保存後下一次可直接根據上述配置生成項目
? Save this as a preset for future projects? (y/N) N

// 建立成功
🎉  Successfully created project vue-typescript-admin-demo.
複製代碼

yarn run serve運行項目以後會報一堆莫名的錯誤,這都是 tslint.json 搞的鬼,配置一下從新運行便可

// tsconfig.json
Error: Calls to 'console.log' are not allowed.

Error: 去除行尾必加';'

Error: 禁止自動檢測末尾行必須使用逗號,always老是檢測,never從不檢測,ignore忽略檢測

"rules": {
    "no-console": false,
    "semicolon": [
        false,
        "always"
    ],
    "trailing-comma": [true, {
        "singleline": "never",
        "multiline": {
            "objects": "ignore",
            "arrays": "ignore",
            "functions": "never",
            "typeLiterals": "ignore"
        }
    }]
}

複製代碼

至此,整個項目算是正常運行起來了。But... 這仍是傳統的Vue項目,咱們要開發的是Vue+ts實戰項目,因此須要改造一番,詳細的目錄結構,等改造完以後再附上吧。

目錄結構

這是改造後的目錄結構

├── public                          // 靜態頁面
├── scripts                         // 相關腳本配置
├── src                             // 主目錄
    ├── assets                      // 靜態資源
    ├── api                         // axios封裝
    ├── filters                     // 過濾
    ├── lib                         // 全局插件
    ├── router                      // 路由配置
    ├── store                       // vuex 配置
    ├── styles                      // 樣式
    ├── types                       // 全局注入
    ├── utils                       // 工具方法(全局方法等)
    ├── views                       // 頁面
    ├── App.vue                     // 頁面主入口
    ├── main.ts                     // 腳本主入口
    ├── registerServiceWorker.ts    // PWA 配置
├── tests                           // 測試用例
├── .editorconfig                   // 編輯相關配置
├── .npmrc                          // npm 源配置
├── .postcssrc.js                   // postcss 配置
├── babel.config.js                 // preset 記錄
├── cypress.json                    // e2e plugins
├── f2eci.json                      // 部署相關配置
├── package.json                    // 依賴
├── README.md                       // 項目 readme
├── tsconfig.json                   // ts 配置
├── tslint.json                     // tslint 配置
└── vue.config.js                   // webpack 配置

複製代碼

主要涉及 shims-tsx.d.tsshims-vue.d.ts 兩個文件

  • shims-tsx.d.ts,容許你以 .tsx 結尾的文件,在 Vue 項目中編寫 jsx代碼
  • shims-vue.d.ts 主要用於 TypeScript 識別 .vue文件, ts 默認並不支持導入 .vue文件,這個文件告訴 ts 導入.vue 文件都按 VueConstructor<Vue> 處理。

tslint添加以下配置

// tslint.json
// 不檢測隱式類型
{
  "defaultSeverity": "none", // 值爲warn時爲警告
	"rules": {
		...
  }
  "arrow-parens": [
	false,
	"as-needed"
  ]
}
複製代碼

其餘內容配置(自選)

// tslint.json
{
  "defaultSeverity": "warning",
  "extends": [
    "tslint:recommended"
  ],
  "linterOptions": {
    "exclude": [
      "node_modules/**"
    ]
  },
  "rules": {
    "quotemark": false, // 字符串文字須要單引號或雙引號。
    "indent": false, // 使用製表符或空格強制縮進。
    "member-access": false, // 須要類成員的顯式可見性聲明。
    "interface-name": false, // 接口名要求大寫開頭
    "ordered-imports": false, // 要求將import語句按字母順序排列並進行分組。
    "object-literal-sort-keys": false, // 檢查對象文字中鍵的排序。
    "no-consecutive-blank-lines": false, // 不容許連續出現一個或多個空行。
    "no-shadowed-variable": false, // 不容許隱藏變量聲明。
    "no-trailing-whitespace": false, // 不容許在行尾添加尾隨空格。
    "semicolon": false, // 是否分號結尾
    "trailing-comma": false, // 是否強象添加逗號
    "eofline": false, // 是否末尾另起一行
    "prefer-conditional-expression": false, // for (... in ...)語句必須用if語句過濾
    "curly": true, //for if do while 要有括號
    "forin": false, //用for in 必須用if進行過濾
    "import-blacklist": true, //容許使用import require導入具體的模塊
    "no-arg": true, //不容許使用 argument.callee
    "no-bitwise": true, //不容許使用按位運算符
    "no-console": false, //不能使用console
    "no-construct": true, //不容許使用 String/Number/Boolean的構造函數
    "no-debugger": true, //不容許使用debugger
    "no-duplicate-super": true, //構造函數兩次用super會發出警告
    "no-empty": true, //不容許空的塊
    "no-eval": true, //不容許使用eval
    "no-floating-promises": false, //必須正確處理promise的返回函數
    "no-for-in-array": false, //不容許使用for in 遍歷數組
    "no-implicit-dependencies": false, //不容許在項目的package.json中導入未列爲依賴項的模塊
    "no-inferred-empty-object-type": false, //不容許在函數和構造函數中使用{}的類型推斷
    "no-invalid-template-strings": true, //警告在非模板字符中使用${
    "no-invalid-this": true, //不容許在非class中使用 this關鍵字
    "no-misused-new": true, //禁止定義構造函數或new class
    "no-null-keyword": false, //不容許使用null關鍵字
    "no-object-literal-type-assertion": false, //禁止object出如今類型斷言表達式中
    "no-return-await": true, //不容許return await
    "arrow-parens": false, //箭頭函數定義的參數須要括號
    "adjacent-overload-signatures": false, // Enforces function overloads to be consecutive.
    "ban-comma-operator": true, //禁止逗號運算符。
    "no-any": false, //不需使用any類型
    "no-empty-interface": true, //禁止空接口 {}
    "no-internal-module": true, //不容許內部模塊
    "no-magic-numbers": false, //不容許在變量賦值以外使用常量數值。當沒有指定容許值列表時,默認容許-1,0和1
    "no-namespace": [true, "allpw-declarations"], //不容許使用內部modules和命名空間
    "no-non-null-assertion": true, //不容許使用!後綴操做符的非空斷言。
    "no-parameter-reassignment": true, //不容許從新分配參數
    "no-reference": true, // 禁止使用/// <reference path=> 導入 ,使用import代替
    "no-unnecessary-type-assertion": false, //若是類型斷言沒有改變表達式的類型就發出警告
    "no-var-requires": false, //不容許使用var module = require("module"),用 import foo = require('foo')導入
    "prefer-for-of": true, //建議使用for(..of)
    "promise-function-async": false, //要求異步函數返回promise
    "max-classes-per-file": [true, 2], // 一個腳本最多幾個申明類
    "variable-name": false,
    "prefer-const": false // 提示能夠用const的地方
  }
}
複製代碼

世界頓時清淨了~~~ 有硬須要的朋友能夠自行打開,前提是必定要配置好tslint規則,不然仍是有點痛苦不堪的,畢竟warn多了看着難受。告辭

./src/config/index.ts

/** * 線上環境 */
export const ONLINEHOST: string = 'https://xxx.com'

/** * 測試環境 */
export const QAHOST: string = 'http://xxx.com'

/** * 線上mock */
export const MOCKHOST: string = 'http://xxx.com'

/** * 是否mock */
export const ISMOCK: boolean = true

/** * 當前的host ONLINEHOST | QAHOST | MOCKHOST */
export const MAINHOST: string = ONLINEHOST

/** * 請求的公共參數 */
export const conmomPrams: any = {}

/** * @description token在Cookie中存儲的天數,默認1天 */
export const cookieExpires: number = 1
複製代碼

./src/utils/common.ts

// 下載js-cookie
cnpm i js-cookie --S
cnpm install @types/js-cookie --D
複製代碼
import Cookies from 'js-cookie'
import { cookieExpires } from '@/config' // cookie保存的天數

/** * @Author: asheng * @msg: 存取token * @param {string} token */
export const TOKEN_KEY: string = 'token'
export const setToken = (token: string) => {
  Cookies.set(TOKEN_KEY, token, { expires: cookieExpires || 1 })
}
export const getToken = () => {
  const token = Cookies.get(TOKEN_KEY)
  if (token) {
    return token
  } else {
    return false
  }
}

/** * @param {String} url * @description 從URL中解析參數 */
export const getParams = (url: string) => {
  const keyValueArr = url.split('?')[1].split('&')
  let paramObj: any = {}
  keyValueArr.forEach(item => {
    const keyValue = item.split('=')
    paramObj[keyValue[0]] = keyValue[1]
  })
  return paramObj
}

/** * 判斷一個對象是否存在key,若是傳入第二個參數key,則是判斷這個obj對象是否存在key這個屬性 * 若是沒有傳入key這個參數,則判斷obj對象是否有鍵值對 */
export const hasKey = (obj: any, key: string | number) => {
  if (key) {
    return key in obj
  } else {
    const keysArr = Object.keys(obj)
    return keysArr.length
  }
}

/** * @msg: 獲取系統當前時間 * @param {string} fmt 時間格式 具體看代碼 * @return: string */
export const getDate = (fmt: any) => {
  let time = ''
  const date = new Date()
  const o: any = {
    "M+": date.getMonth() + 1, // 月份 
    "d+": date.getDate(), // 日 
    "H+": date.getHours(), // 小時 
    "m+": date.getMinutes(), // 分 
    "s+": date.getSeconds(), // 秒 
    "q+": Math.floor((date.getMonth() + 3) / 3), // 季度 
    "S": date.getMilliseconds() // 毫秒 
  }
  if (/(y+)/.test(fmt)) {
    time = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length))
  }
  for (const k in o) {
    if (new RegExp("(" + k + ")").test(fmt)) {
      time = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)))
    }
  }
  return time
}

/** * @msg: 獲取系統當前時間 * @param {string} date 時間 * @param {string} fmt 時間格式 * @return: string */
export const formatDate = (date: any, fmt: string) => {
  let time = ''
  const o: any = {
    "M+": date.getMonth() + 1, // 月份 
    "d+": date.getDate(), // 日 
    "H+": date.getHours(), // 小時 
    "m+": date.getMinutes(), // 分 
    "s+": date.getSeconds(), // 秒 
    "q+": Math.floor((date.getMonth() + 3) / 3), // 季度 
    "S": date.getMilliseconds() // 毫秒 
  }
  if (/(y+)/.test(fmt)) {
    time = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length))
  }
  for (const k in o) {
    if (new RegExp("(" + k + ")").test(fmt)) {
      time = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)))
    }
  }
  return time
}

// copy in the 'fx-fuli' utils
/** * 校驗手機號是否正確 * @param phone 手機號 */

export const verifyPhone = (phone: string | number) => {
  const reg = /^1[34578][0-9]{9}$/
  const _phone = phone.toString().trim()
  let toastStr = _phone === '' ? '手機號不能爲空~' : !reg.test(_phone) && '請輸入正確手機號~'
  return {
    errMsg: toastStr,
    done: !toastStr,
    value: _phone
  }
}

export const verifyStr = (str: string | number, text: string) => {
  const _str = str.toString().trim()
  const toastStr = _str.length ? false : `請填寫${text}~`
  return {
    errMsg: toastStr,
    done: !toastStr,
    value: _str
  }
}

// 截取字符串
export const sliceStr = (str: any, sliceLen: number) => {
  if (!str) { return '' }
  let realLength = 0
  const len = str.length
  let charCode = -1
  for (let i = 0; i < len; i++) {
    charCode = str.charCodeAt(i)
    if (charCode >= 0 && charCode <= 128) {
      realLength += 1
    } else {
      realLength += 2
    }
    if (realLength > sliceLen) {
      return `${str.slice(0, i)}...`
    }
  }

  return str
}


/** * JSON 克隆 * @param {Object | Json} jsonObj json對象 * @return {Object | Json} 新的json對象 */
export function objClone(jsonObj: any) {
  let buf: any
  if (jsonObj instanceof Array) {
    buf = []
    let i = jsonObj.length
    while (i--) {
      buf[i] = objClone(jsonObj[i])
    }
    return buf
  } else if (jsonObj instanceof Object) {
    buf = {}
    for (let k in jsonObj) {
      buf[k] = objClone(jsonObj[k])
    }
    return buf
  } else {
    return jsonObj
  }
}
複製代碼

1、巧用Webpack

Webpack是實現咱們前端項目工程化的基礎,但其實她的用處遠不只僅如此,咱們能夠經過Webpack來幫咱們作一些自動化的事情。首先咱們要了解require.context()這個API

require.context()

您可使用require.context()函數建立本身的上下文。 它容許您傳入一個目錄進行搜索,一個標誌指示是否應該搜索子目錄,還有一個正則表達式來匹配文件。

實際上是Webpack經過解析 require()的調用,提取出來以下這些信息:

Directory: ./template
Regular expression: /^.*\.ejs$/
複製代碼

而後來建立咱們本身的上下文,什麼意思呢,就是咱們能夠經過這個方法篩選出來咱們須要的文件而且讀取

/** * @param directory 要搜索的文件夾目錄不能是變量,不然在編譯階段沒法定位目錄 * @param useSubdirectories 是否搜索子目錄 * @param regExp 匹配文件的正則表達式 * @return function 返回一個具備 resolve, keys, id 三個屬性的方法 resolve() 它返回請求被解析後獲得的模塊 id keys() 它返回一個數組,由全部符合上下文模塊處理的請求組成。 id 是上下文模塊裏面所包含的模塊 id. 它可能在你使用 module.hot.accept 的時候被用到 */
require.context('.', useSubdirectories = false, regExp = /\.js$/)
// (建立了)一個包含了 demo 文件夾(不包含子目錄)下面的、全部文件名以 `js` 結尾的、能被 require 請求到的文件的上下文。

複製代碼

這麼講,是否以爲抽象,接下來咱們應用下這個小東西。

2、路由

對於Vue中的路由,你們都很熟悉,相似於聲明式的配置文件,其實已經很簡潔了。如今咱們來讓他更簡潔

分割路由

router                           // 路由文件夾
  |__index.ts                    // 路由組織器:用來初始化路由等等
  |__common.ts                   // 通用路由:聲明通用路由
  |__modules                     // 業務邏輯模塊:因此的業務邏輯模塊
        |__index.ts              // 自動化處理文件:自動引入路由的核心文件
        |__home.ts               // 業務模塊home:業務模塊
複製代碼

modules

modules文件夾中存放着咱們全部的業務邏輯模塊,至於業務邏輯模塊怎麼分,我相信你們天然有本身的一套標準。咱們經過上面提到的require.context()接下來編寫自動化的核心部分index.js。

const files: any = require.context('.', false, /\.ts/)

let configRouters: Array<any> = []

files.keys().forEach((key) => {
  if (key === './index.ts') {
    return
  }
  configRouters = configRouters.concat(files(key).default)
})

export default configRouters
複製代碼

common

common路由處理 咱們的項目中有一大堆的公共路由須要處理好比404阿,503阿等等路由咱們都在common.ts中進行處理。

export default [
  {
    path: '/',
    name: 'Login',
    // redirect: '/Login',
    component: Login
  },
  {
    path: '*',
    name: 'Lost',
    component: () => import('@/views/404.vue')
  }
]

複製代碼

路由初始化 這是咱們的最後一步了,用來初始化咱們的項目路由

import Vue from 'vue'
import Router from 'vue-router'
import ConfigRouters from './modules'
import Common from './common'
// 因爲是網站開發,這個是進度條,具體開百度瞭解一下
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/common'

Vue.use(Router)

const router = new Router({
  // mode: "history",
  // base: process.env.BASE_URL,
  scrollBehavior() {
    return { x: 0, y: 0 }
  },
  routes: ConfigRouters.concat(Common)
})

// 登錄頁面路由 name
const LOGIN_PAGE_NAME = 'Login'

// 跳轉以前
router.beforeEach((to, from, next) => {
  NProgress.start()
  const token = getToken()
  if (!token && to.name !== LOGIN_PAGE_NAME) {
    // 未登陸且要跳轉的頁面不是登陸頁
    next({
      name: LOGIN_PAGE_NAME // 跳轉到登陸頁
    })
  } else if (!token && to.name === LOGIN_PAGE_NAME) {
    // 未登錄且要跳轉的頁面是登陸頁
    next() // 跳轉
  } else if (token && to.name === LOGIN_PAGE_NAME) {
    // 已登陸且要跳轉的頁面是登陸頁
    next({
      name: 'Home' // 跳轉到 index 頁
    })
  } else {
    if (token) {
      next() // 跳轉
    } else {
      next({
        name: LOGIN_PAGE_NAME
      })
    }
  }
})

router.afterEach(() => {
  NProgress.done() // finish progress bar
})
export default router
複製代碼

3、充分利用Nodejs

放着node這麼好得東西不用真是有點浪費,那麼咱們來看看node能爲咱們增長效率作出什麼貢獻。

有這麼一個場景,咱們每次建立模塊的時候都要新建一個vue文件和對應的router配置,並且新頁面的大部分東西都還差很少,還得去複製粘貼別得頁面。這想一想就有點low。那既然有了node咱們可不能夠經過node來作這寫亂七八糟得事情? 下面來把咱們的想法付諸於顯示。

./scripts/template.js
const fs = require('fs')
const path = require('path')
const basePath = path.resolve(__dirname, '../src')

const dirName = process.argv[2]
const capPirName = dirName.substring(0, 1).toUpperCase() + dirName.substring(1)
if (!dirName) {
    console.log('文件夾名稱不能爲空!')
    console.log('示例:npm run tep ${capPirName}')
    process.exit(0)
}

/** * @msg: vue頁面模版 */
const VueTep = `<template> <div class="${dirName}-wrap"> {{data.pageName}} </div> </template> <script lang="ts" src="./${dirName}.ts"></script> <style lang="scss"> @import './${dirName}.scss' </style> `

// ts 模版
const tsTep = `import { Component, Vue } from "vue-property-decorator" import { Getter, Action } from "vuex-class" import { ${capPirName}Data } from '@/types/views/${dirName}.interface' // import { } from "@/components" // 組件 @Component({}) export default class About extends Vue { // Getter // @Getter ${dirName}.author // Action // @Action GET_DATA_ASYN // data data: ${capPirName}Data = { pageName: '${dirName}' } created() { // } activated() { // } mounted() { // } // 初始化函數 init() { // } } `

// scss 模版
const scssTep = `@import "@/assets/scss/variables.scss"; .${dirName}-wrap { width: 100%; } `

// interface 模版
const interfaceTep = `// ${dirName}.Data 參數類型 export interface ${capPirName}Data { pageName: string } // VUEX ${dirName}.State 參數類型 export interface ${capPirName}State { data?: any } // GET_DATA_ASYN 接口參數類型 // export interface DataOptions {} `

// vuex 模版
const vuexTep = `import { ${capPirName}State } from '@/types/views/${dirName}.interface' import { GetterTree, MutationTree, ActionTree } from 'vuex' import * as ${capPirName}Api from '@/api/${dirName}' const state: ${capPirName}State = { ${dirName}: { author: undefined } } // 強制使用getter獲取state const getters: GetterTree<${capPirName}State, any> = { author: (state: ${capPirName}State) => state.${dirName}.author } // 更改state const mutations: MutationTree<${capPirName}State> = { // 更新state都用該方法 UPDATE_STATE(state: ${capPirName}State, data: ${capPirName}State) { for (const key in data) { if (!data.hasOwnProperty(key)) { return } state[key] = data[key] } } } const actions: ActionTree<${capPirName}State, any> = { UPDATE_STATE_ASYN({ commit, state: ${capPirName}State }, data: ${capPirName}State) { commit('UPDATE_STATE', data) }, // GET_DATA_ASYN({ commit, state: LoginState }) { // ${capPirName}.getData() // } } export default { state, getters, mutations, actions } `

// api 接口模版
const apiTep = `import Api from '@/utils/request' export const getData = () => { return Api.getData() } `

fs.mkdirSync(`${basePath}/views/${dirName}`) // mkdir

process.chdir(`${basePath}/views/${dirName}`) // cd views
fs.writeFileSync(`${dirName}.vue`, VueTep) // vue 
fs.writeFileSync(`${dirName}.ts`, tsTep) // ts
fs.writeFileSync(`${dirName}.scss`, scssTep) // scss

process.chdir(`${basePath}/types/views`); // cd types
fs.writeFileSync(`${dirName}.interface.ts`, interfaceTep) // interface

process.chdir(`${basePath}/store/module`); // cd store
fs.writeFileSync(`${dirName}.ts`, vuexTep) // vuex

process.chdir(`${basePath}/api`); // cd api
fs.writeFileSync(`${dirName}.ts`, apiTep) // api

process.exit(0)
複製代碼
./scripts/component.js
const fs = require('fs')
const path = require('path')
const basePath = path.resolve(__dirname, '../src')

const dirName = process.argv[2]
const capPirName = dirName.substring(0, 1).toUpperCase() + dirName.substring(1)
if (!dirName) {
    console.log('文件夾名稱不能爲空!')
    console.log('示例:npm run tep ${capPirName}')
    process.exit(0)
}

/** * @msg: vue頁面模版 */
const VueTep = `<template> <div class="${dirName}-wrap"> {{data.pageName}} </div> </template> <script lang="ts" src="./${dirName}.ts"></script> <style lang="scss"> @import './${dirName}.scss' </style> `

// ts 模版
const tsTep = `import { Component, Vue } from "vue-property-decorator" import { Getter, Action } from "vuex-class" import { ${capPirName}Data } from '@/types/views/${dirName}.interface' // import { } from "@/components" // 組件 @Component({}) export default class About extends Vue { // Getter // @Getter ${dirName}.author // Action // @Action GET_DATA_ASYN // data data: ${capPirName}Data = { pageName: '${dirName}' } created() { // } activated() { // } mounted() { // } // 初始化函數 init() { // } } `

// scss 模版
const scssTep = `@import "@/assets/scss/variables.scss"; .${dirName}-wrap { width: 100%; } `

// interface 模版
const interfaceTep = `// ${dirName}.Data 參數類型 export interface ${capPirName}Data { pageName: string } // VUEX ${dirName}.State 參數類型 export interface ${capPirName}State { data?: any } // GET_DATA_ASYN 接口參數類型 // export interface DataOptions {} `

// vuex 模版
const vuexTep = `import { ${capPirName}State } from '@/types/views/${dirName}.interface' import { GetterTree, MutationTree, ActionTree } from 'vuex' import * as ${capPirName}Api from '@/api/${dirName}' const state: ${capPirName}State = { ${dirName}: { author: undefined } } // 強制使用getter獲取state const getters: GetterTree<${capPirName}State, any> = { author: (state: ${capPirName}State) => state.${dirName}.author } // 更改state const mutations: MutationTree<${capPirName}State> = { // 更新state都用該方法 UPDATE_STATE(state: ${capPirName}State, data: ${capPirName}State) { for (const key in data) { if (!data.hasOwnProperty(key)) { return } state[key] = data[key] } } } const actions: ActionTree<${capPirName}State, any> = { UPDATE_STATE_ASYN({ commit, state: ${capPirName}State }, data: ${capPirName}State) { commit('UPDATE_STATE', data) }, // GET_DATA_ASYN({ commit, state: LoginState }) { // ${capPirName}.getData() // } } export default { state, getters, mutations, actions } `

// api 接口模版
const apiTep = `import Api from '@/utils/request' export const getData = () => { return Api.getData() } `

fs.mkdirSync(`${basePath}/views/${dirName}`) // mkdir

process.chdir(`${basePath}/views/${dirName}`) // cd views
fs.writeFileSync(`${dirName}.vue`, VueTep) // vue 
fs.writeFileSync(`${dirName}.ts`, tsTep) // ts
fs.writeFileSync(`${dirName}.scss`, scssTep) // scss

process.chdir(`${basePath}/types/views`); // cd types
fs.writeFileSync(`${dirName}.interface.ts`, interfaceTep) // interface

process.chdir(`${basePath}/store/module`); // cd store
fs.writeFileSync(`${dirName}.ts`, vuexTep) // vuex

process.chdir(`${basePath}/api`); // cd api
fs.writeFileSync(`${dirName}.ts`, apiTep) // api

process.exit(0)
複製代碼

使用

cnpm run tep index
cnpm run tep login
複製代碼

咱們實現這個功能主要要藉助Nodefsprocess, 感興趣的話能夠深刻研究一下。

首先咱們要編寫咱們的node腳本,這裏是一個比較簡單的版本。什麼驗證文件夾或者文件的都沒有,只是來實現咱們這個想法:

4、狀態管理Vuex

傳統的vuexvue+ts的項目裏面是行不通的,vue 2.0版本對ts的兼容性自己並非特別友好,因此要達到狀態管理的效果,這裏要額外引用一個類庫vuex-module-decorators,它是基於vue-class-component 所作的拓展,它提供了一系列的裝飾器,讓vue+ts結合的項目達到狀態管理的做用。

先來看看要完成的模塊化管理的目錄結構

.
├─ src/        
│   ├─ store/
│   ├─── modules/
│   │ 		├─ app.ts 
│   │ 		├─ user.ts
│   ├─── index.ts   
複製代碼
import Vue from 'vue'
import Vuex from 'vuex'
import { IAppState } from './modules/app'
import { IUserState } from './modules/user'

Vue.use(Vuex)

export interface IRootState {
    app: IAppState
    user: IUserState
}

// Declare empty store first, dynamically register all modules later.
export default new Vuex.Store<IRootState>({})
複製代碼

等同於

import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app'
import user from './modules/user'
Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    app,
    user
  }
})

export default store
複製代碼

這樣,模塊化狀態管理的雛形就完成了。對比來看,只是語法風格的變化,其它的變化不大。ts版的狀態管理最大的改變體如今各個功能功能函數上

先看一看原始的vuex配置,輕車熟路

export default new Vuex.Store({
    state: {
    },
    mutations: {
    },
    actions: {
    },
    modules: {
    }
});
複製代碼

爲了顯得不那麼囉嗦,直接上版ts版的狀態管理吧,能夠有個直觀的對比

// user.ts
import { VuexModule, Module, Action, Mutation, getModule } from 'vuex-module-decorators'
import store from '@/store'

export interface IUserState {
    id_token: string
}

@Module({ dynamic: true, store, name: 'user' })
class User extends VuexModule implements IUserState {
    public id_token = ''
    
    @Mutation
    private SET_TOKEN(token: string) {
        this.id_token = token
    }
    
    @Action
    public async Login(params: any) {
        this.SET_TOKEN(`token!!!`)
    }
}

export const UserModule = getModule(User)
複製代碼

Module

定義一個modules,直接使用裝飾器@Module 注意:原始的vuex一樣有一個名爲Module的類,但它不是一個裝飾器,因此別用混淆了

@Module({ dynamic: true, store, name: 'user' })
複製代碼

從上面能夠看到,咱們定義modules不僅僅用了裝飾器,還帶了參數值,這個是代表是經過命名空間的形式來使用module,如上,這裏的namespaced值即爲user

詳細vuex命名空間的說明,能夠參考vuex命名空間

除了namespaced,咱們看到還有另一個參數值store,它即爲主入口頁對應的整個vuex模塊的store

import store from '@/store'
複製代碼

若是去掉它的話,瀏覽器會報如下錯誤

state

這裏全部的state屬性由於加了tslint都會添加上public修飾,其它的用法都是類似的

Getters

原始的getters計算函數,在這裏對應的即便get方法,即

@Module
export default class UserModule extends VuexModule {
  countsNum = 2020
  
  get calculatCount() {
    return countsNum / 2
  }
}
複製代碼

等同於

export default {
  state: {
    countsNum: 2
  },
  getters: {
    calculatCount: (state) => state.countsNum / 2
  }
}
複製代碼

Mutations

@Mutation
private SET_TOKEN(token: string) {
    this.token = token
}

@Mutation
...
複製代碼

等同於

mutations: {
    SET_TOKEN: (state, token) => {
        state.token = token
    },
    ...
}
複製代碼

說明: 二者的區別其實就是語法糖,原始的Mutation同步方法都是定義在mutations內,而ts版的每個Mutation都要加上裝飾器@Mutation修飾

注意: 一旦使用@Mutation裝飾某一函數後, 函數內的this上下文即指向當前的state,因此想引用state的值,能夠直接this.token訪問便可。

Muation函數不可爲async函數, 也不能使用箭頭函數來定義, 由於在代碼須要在運行從新綁定執行的上下文

Action

@Action
public async Login(userInfo: { username: string, password: string}) {
    ...
    this.SET_TOKEN(data.accessToken)
}
複製代碼

等同於

actions: {
    async Login({ commit }, data) {
        ...
        commit('SET_TOKEN', data.accessToken)
    }
}
複製代碼

說明: 異步函數Action和同步函數Mutation使用方法大同小異,區別就是一個是同步,一個是異步,只要作好區分便可

注意

  • 若是須要在action函數中運行耗時很長的任務/函數, 建議將該任務定義爲異步函數*(async methods)*
  • 千萬不要使用箭頭函數=>來定義action函數, 由於在運行時須要動態綁定this上下文

vuex+ts版的配置搭建成功,接下來咱們把它運用到項目中來吧,這裏抽一個登錄頁面的模塊作介紹

import {
  VuexModule,
  Module,
  Action,
  Mutation,
  getModule
} from 'vuex-module-decorators'
import { login } from '@/api/users' //調用api方法
import store from '@/store'

//聲明user模塊的state變量類型
//export interface 只是對一個東西的聲明(不能具體的操做)
//export class 導出一個類 類裏面可有參數 ,函數,方法(幹一些具體的事情)
export interface IUserState {
  id_token: string
}

@Module({ dynamic: true, store, name: 'user' })
class User extends VuexModule implements IUserState {
  public id_token = ''

  @Mutation
  private SET_TOKEN(token: string) {
    //同步存儲id_token變量
    this.id_token = token
  }

  @Action
  public async Login(params: any) {
    let { mobilePhone, password } = params
    const { data } = await login({ mobilePhone, password })
    this.SET_TOKEN(`Bearer ${data.id_token}`)
  }
}

export const UserModule = getModule(User)
複製代碼

login頁面中調用

import { UserModule } from '@/store/modules/user'

await UserModule.Login({
  ...this.loginForm,
  router: this.$router
})
複製代碼

把路由對象做爲參數傳過去是爲了根據不一樣的響應狀態作判斷,當請求成功後,能夠直接應用傳過來的路由對象參數跳轉頁面。

router.push('/')
複製代碼

注意: 這一步操做實際上是調用了vuexAction操做,即原始的this.$store.commit('action'),可是在vuex+ts項目中,調用異步函數Action,不須要再用this.$store.commit('action')這種方法,引用模塊後,直接調用裏面的Action方法就行了,一樣的,同步的Mutation也是這樣調用。這些都要歸功於vuex-module-decorators類庫的封裝 好了,調用Action後粗發Mutation同步操做,保存好token令牌,由於登陸以後全部的請求都要把token值放在header頭中發起請求 除了vuex狀態管理,在項目中可能咱們還會結合工具類js-cookie一塊兒使用,管理各類變量的值,具體用法跟原始版沒有什麼區別,最主要的是安裝類庫的過程當中,還得安裝一個開發ts編譯版

yarn add js-cookie // dependencies yarn add @types/js-cookie --dev // devDependencies(必裝)
複製代碼

注意點

這裏使用的是vuex-modulevuex-class仍是有很多區別,在下面的內容,使用的是vuex-class

可能有人會有疑問,爲何介紹vuex-module,而使用vuex-class。。。 當初構建項目時,使用的是vuex-class,最近學習到vuex-module,就記錄下。

具體vuex-class的使用,文章最開始就把文檔貼出來了,可進去了解一下。

5、Mixins

若是咱們有大量的表格頁面,仔細一扒拉你發現很是多的東西都是能夠複用的例如分頁,表格高度,加載方法, laoding聲明等一大堆的東西。下面咱們來整理出來一個簡單通用混入index.js

import { Provide, Vue } from 'vue-property-decorator'
import Component from 'vue-class-component'
// 這裏使用的是vuex-class,與上面的vuex-module不一樣,請注意
import { namespace } from 'vuex-class'
import moment from 'moment'

const usreModel = namespace('user')

@Component
export default class MyMixin extends Vue {
  @Provide() public loading: boolean = false
  @Provide() public form: any
  @Provide() public data: Array<any> = []
  @Provide() public pagination: any = {
    defaultPageSize: 6,
    showQuickJumper: true,
    hideOnSinglePage: false
  }

  @usreModel.State(state => state.user_id) user_id
  @usreModel.State(state => state.authority) authority
  
  formatDate(value, format = 'YYYY-MM-DD HH:mm') {
    if (value) {
      return moment(value).format(format)
    }
  }
}
複製代碼

mixins使用

import Component, { mixins } from 'vue-class-component'
import { Vue, Provide } from 'vue-property-decorator'
import MyMixin from '@/mixins'

@Component
export default class Home extends mixins(MyMixin) {
  @Provide() private columns: Object = Columns
  @Provide() private search: string = ''
}
複製代碼

這樣就能夠正常使用lodingform等數據方法等

注意:全局mixins必定要慎用,若是不是必需要用的話我仍是不建議使用。

6、axios的封裝

vue項目中,和後臺交互獲取數據這塊,咱們一般使用的是axios庫,它是基於promisehttp庫,可運行在瀏覽器端和node.js中。他有不少優秀的特性,例如攔截請求和響應、取消請求、轉換json、客戶端防護XSRF等。因此咱們的尤大大也是果斷放棄了對其官方庫vue-resource的維護,直接推薦咱們使用axios庫。若是還對axios不瞭解的,能夠移步axios文檔。

安裝

npm install axios; // 安裝axios
複製代碼

引入

通常我會在項目的src目錄中,新建一個api文件夾,而後在裏面新建一個api.ts和一個requestConfig.ts文件。api.ts文件用來封裝咱們的axiosrequestConfig.ts用來統一管理咱們的接口。

// src/api/api.ts
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios'
// config文件夾日後會出現,這裏就不說明了
import { MAINHOST, ISMOCK, QAHOST, conmomPrams } from '@/config' 
// 接口
import requestConfig from './requestConfig'
// 獲取存儲在 cookies 的 token
import { getToken, removeToken } from '@/utils/common'
// 這裏我使用了 antd ,你們根據本身的UI來使用
import { message } from 'ant-design-vue'
// 路由
import router from '@/router'
// 下面兩個是加解密文件,由於用的是http,爲了安全考慮,使用到這兩個。(可忽略)
import apiEncrypt from '@/utils/apiEncrypt'
import apiDecrypt from '@/utils/apiDecrypt'

declare type Methods = 'GET' | 'OPTIONS' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT'

declare interface Datas {
  method?: Methods
  [key: string]: any
}

// 根據環境,切換請求不一樣的url
const baseURL = process.env.NODE_ENV === 'production' ? MAINHOST : QAHOST//QAHOST

class HttpRequest {
  public queue: any // 請求的url集合
  public hide: any
  public constructor() {
    this.queue = {}
  }
  destroy(url: string) {
    delete this.queue[url]
    if (!Object.keys(this.queue).length) {
      // 關閉loding
      setTimeout(this.hide, 0)
    }
  }
  interceptors(instance: any, url?: string) {
    // 請求攔截
    instance.interceptors.request.use(
      (config: AxiosRequestConfig) => {
        // 添加全局的loading...
        if (!Object.keys(this.queue).length) {
          // show loading
          this.hide = message.loading('加載中..', 0)
        }
        if (url) {
          this.queue[url] = true
        }
        return config
      },
      (error: any) => {
        console.error(error)
      }
    )
    // 響應攔截
    instance.interceptors.response.use(
      (res: AxiosResponse) => {
        if (url) {
          this.destroy(url)
        }
        let { data, status } = res
        data = apiDecrypt(data)
        if (status === 200 && ISMOCK) {
          return data.result
        } // 若是是mock數據,直接返回
        if (status === 200 && data && data.code === 200) {
          return data.result
        } // 請求成功
        res.data = data
        return requestFail(res) // 失敗回調
      },
      (error: any) => {
        if (url) {
          this.destroy(url)
        }
        message.error('服務器出錯')
        console.error(error)
      }
    )
  }
  async request(options: AxiosRequestConfig) {
    const instance = axios.create()
    await this.interceptors(instance, options.url)
    return instance(options)
  }
}

// 請求失敗
const requestFail = (res: AxiosResponse) => {
  let errStr = '網絡繁忙!'

  if (res.data.code) {
    switch (res.data.code) {
      // 401: 未登陸
      // 未登陸則跳轉登陸頁面,並攜帶當前頁面的路徑
      // 在登陸成功後返回當前頁面,這一步須要在登陸頁操做。
      case 401:
        router.replace({
          path: '/'
        })
        removeToken()
        break
      // 403 token過時
      // 登陸過時對用戶進行提示
      // 清除本地token和清空vuex中token對象
      // 跳轉登陸頁面
      case 403:
        // 清除token
        // store.commit('loginSuccess', null);
        // 跳轉登陸頁面,並將要瀏覽的頁面fullPath傳過去,登陸成功後跳轉須要訪問的頁面
        router.replace({
          path: '/'
        })
        removeToken()
        // localStorage.removeItem('token')
        break
      // 404請求不存在
      case 404:
        ...      
        break
    }
  }
  console.error({
    code: res.data.errcode || res.data.code,
    msg: res.data.errMsg || errStr
  })

  if (typeof res.data.errMsg === 'object') {
    res.data.errMsg = '服務器錯誤'
  }
  message.error(res.data.errMsg || errStr)
  return null
}

// 合併axios參數
const conbineOptions = (_opts: any, data: Datas, method: Methods): AxiosRequestConfig => {
  let opts = _opts
  if (typeof opts === 'string') {
    opts = { url: opts }
  }
  const _data = { ...conmomPrams, ...opts.data, ...data }
  const options = {
    method: opts.method || data.method || method || 'GET',
    url: opts.url,
    headers: { Authorization: `Bearer${getToken()}` },// 這個須要與後端配合,讓後端去除掉Bearer,加上這個是爲了(安全考慮)
    baseURL,
    timeout: 10000
  }
  const c = apiEncrypt(_data) // 加密數據
  return options.method !== 'GET' ? Object.assign(options, { data: c }) : Object.assign(options, { params: _data })
}

const HTTP = new HttpRequest()

/** * 拋出整個項目的api方法 */
const Api = (() => {
  const apiObj: any = {}
  const requestList: any = requestConfig
  const fun = (opts: AxiosRequestConfig | string) => {
    return async (data = {}, method: Methods = 'POST') => {
      const newOpts = conbineOptions(opts, data, method)
      const res = await HTTP.request(newOpts)
      return res
    }
  }
  Object.keys(requestConfig).forEach((key) => {
    apiObj[key] = fun(requestList[key])
  })

  return apiObj
})()

export default Api as any

複製代碼
src/api/requestConfig
export default {
  getData: '/mock/5e23f600df5e86413d7f1486/example/upload', // 隨機數據 來自 easy mock
}
複製代碼

一些建議

  • 若是定義了 .d.ts 文件,請從新啓動服務讓你的服務可以識別你定義的模塊,並重啓 vscode 讓編輯器也可以識別(真的噁心)
  • 設置好你的 tsconfig ,好比記得把 strictPropertyInitialization 設爲 false,否則你定義一個變量就必須給它一個初始值。
  • 千萬管理好你的路由層級,否則到時連正則都拯救不了你 業務層面千萬作好類型檢測或者枚舉定義,這樣不只便利了開發,還能在出了問題的時候迅速定位
  • 跨模塊使用 vuex,請直接使用 rootGetters
  • 若是你須要改造某組件庫主題,請單開一個文件進行集中管理,別一個組件分一個文件去改動,否則編譯起來速度堪憂
  • 可以複用團隊其餘人開發好的東西,儘可能別去開發第二遍,否則到時浪費的可能就不是單純的開發時間,還有 code review的時間

vue 文件中 TS 上下文順序

  • data
  • @Prop
  • @State
  • @Getter
  • @Action
  • @Mutation
  • @Watch

生命週期鉤子

  • beforeCreate(按照生命週期鉤子從上到下)
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • activated
  • deactivated
  • beforeDestroy
  • destroyed
  • errorCaptured(最後一個生命週期鉤子)

路由鉤子

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave

computed

methods


分享不易,喜歡的話必定別忘了點💖!!!

只關注不點💖的都是耍流氓,只收藏也不點💖的也同樣是耍流氓。

結束👍👍👍。


參考

axios封裝(戳窩)

加速vue項目開發速度(戳窩)

TypeScript + 大型項目實戰

Typescript+Vue大型後臺管理系統實戰

vue-cli3.0 搭建項目模版教程

相關文章
相關標籤/搜索