TypeScript 已經出來好久了,不少大公司不少大項目也都在使用它進行開發。上個月,我這邊也正式跟進一個對集團的大型運維類項目。javascript
項目要作的事情大體分爲如下幾個大模塊css
每個模塊要作的事情也不少,因爲牽扯到公司業務,具體要作的一些事情這裏我就不一一列舉了,反正項目總體規模仍是很大的。前端
在作了一些技術調研後,再結合項目以後的開發量級以及維護成本。最終我和同事在技術選型上得出一致結論,最終選型定爲 Vue 最新全家桶 + TypeScript。vue
那麼問題來了,爲何大型項目非得用 TypeScript 呢,ES六、7 不行麼?java
其實也沒說不行,只不過我我的更傾向在一些協做開發的大型項目中使用 TypeScript 。下面我列一些我作完調研後本身的一些見解node
首先,TypeScript 具備類型系統,且是 JavaScript 的超集。 JavaScript 能作的,它能作。JavaScript 不能作的,它也能作。python
其次,TypeScript 已經比較成熟了,市面上相關資料也比較多,大部分的庫和框架也讀對 TypeScript 作了很好的支持。webpack
而後,保證優秀的前提下,它還在積極的開發完善之中,不斷地會有新的特性加入進來ios
JavaScript 是弱類型而且沒有命名空間,致使很難模塊化,使得其在大型的協做項目中不是很方便git
vscode、ws 等編輯器對 TypeScript 支持很友好
TypeScript 在組件以及業務的類型校驗上支持比較好,好比
// 定義枚舉 const enum StateEnum { TO_BE_DONE = 0, DOING = 1, DONE = 2 } // 定義 item 接口 interface SrvItem { val: string, key: string } // 定義服務接口 interface SrvType { name: string, key: string, state?: StateEnum, item: Array<SrvItem> } // 而後定義初始值(若是不按照類型來,報錯確定是避免不了的) const types: SrvType = { name: '', key: '', item: [] }
配合好編輯器,若是不按照定義好的類型來的話,編輯器自己就會給你報錯,而不會等到編譯纔來報錯
命令空間 + 接口申明更方便類型校驗,防止代碼的不規範
好比,你在一個 ajax.d.ts 文件定義了 ajax 的返回類型
declare namespace Ajax { // axios 返回數據 export interface AxiosResponse { data: AjaxResponse } // 請求接口數據 export interface AjaxResponse { code: number, data: object | null | Array<any>, message: string } }
而後在請求的時候就能進行使用
this.axiosRequest({ key: 'idc' }).then((res: Ajax.AjaxResponse) => { console.log(res) })
可使用 泛型 來建立可重用的組件。好比你想建立一個參數類型和返回值類型是同樣的通用方法
function foo<T> (arg: T): T { return arg } let output = foo('string') // type of output will be 'string'
再好比,你想使用泛型來鎖定代碼裏使用的類型
interface GenericInterface<T> { (arg: T): T } function foo<T> (arg: T): T { return arg } // 鎖定 myFoo 只能傳入 number 類型的參數,傳其餘類型的參數則會報錯 let myFoo: GenericInterface<number> = foo myFoo(123)
總之,還有不少使用 TypeScript 的好處,這裏我就不一一列舉了,感興趣的小夥伴能夠本身去查資料
我這邊使用的是最新版本腳手架 vue-cli 3 進行項目初始化的,初始化選項以下
生成的目錄結構以下
├── public // 靜態頁面 ├── src // 主目錄 ├── assets // 靜態資源 ├── components // 組件 ├── views // 頁面 ├── App.vue // 頁面主入口 ├── main.ts // 腳本主入口 ├── registerServiceWorker.ts // PWA 配置 ├── router.ts // 路由 ├── shims-tsx.d.ts // 相關 tsx 模塊注入 ├── shims-vue.d.ts // Vue 模塊注入 └── store.ts // vuex 配置 ├── tests // 測試用例 ├── .postcssrc.js // postcss 配置 ├── package.json // 依賴 ├── tsconfig.json // ts 配置 └── tslint.json // tslint 配置
顯然這些是不可以知足正常業務的開發的,因此我這邊作了一版基礎建設方面的改造。改造完後項目結構以下
├── public // 靜態頁面 ├── scripts // 相關腳本配置 ├── src // 主目錄 ├── assets // 靜態資源 ├── filters // 過濾 ├── lib // 全局插件 ├── router // 路由配置 ├── store // vuex 配置 ├── styles // 樣式 ├── types // 全局注入 ├── utils // 工具方法(axios封裝,全局方法等) ├── 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 配置
接下來,我將介紹項目中部分模塊的改造
這裏使用了 webpack 的按需加載 import,將相同模塊的東西放到同一個 chunk 裏面,在 router/index.ts
中寫入
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'home', component: () => import(/* webpackChunkName: "home" */ 'views/home/index.vue') } ] })
在 utils/config.ts
中寫入 axios
相關配置(只列舉了一小部分,具體請小夥伴們本身根據自身業務進行配置)
import http from 'http' import https from 'https' import qs from 'qs' import { AxiosResponse, AxiosRequestConfig } from 'axios' const axiosConfig: AxiosRequestConfig = { baseURL: '/', // 請求後的數據處理 transformResponse: [function (data: AxiosResponse) { return data }], // 查詢對象序列化函數 paramsSerializer: function (params: any) { return qs.stringify(params) }, // 超時設置s timeout: 30000, // 跨域是否帶Token withCredentials: true, responseType: 'json', // xsrf 設置 xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', // 最多轉發數,用於node.js maxRedirects: 5, // 最大響應數據大小 maxContentLength: 2000, // 自定義錯誤狀態碼範圍 validateStatus: function (status: number) { return status >= 200 && status < 300 }, // 用於node.js httpAgent: new http.Agent({ keepAlive: true }), httpsAgent: new https.Agent({ keepAlive: true }) } export default axiosConfig
接下來,須要在 utils/api.ts
中作一些全局的攔截操做,這裏我在攔截器裏統一處理了取消重複請求,若是你的業務不須要,請自行去掉
import axios from 'axios' import config from './config' // 取消重複請求 let pending: Array<{ url: string, cancel: Function }> = [] const cancelToken = axios.CancelToken const removePending = (config) => { for (let p in pending) { let item: any = p let list: any = pending[p] // 當前請求在數組中存在時執行函數體 if (list.url === config.url + '&request_type=' + config.method) { // 執行取消操做 list.cancel() // 從數組中移除記錄 pending.splice(item, 1) } } } const service = axios.create(config) // 添加請求攔截器 service.interceptors.request.use( config => { removePending(config) config.cancelToken = new cancelToken((c) => { pending.push({ url: config.url + '&request_type=' + config.method, cancel: c }) }) return config }, error => { return Promise.reject(error) } ) // 返回狀態判斷(添加響應攔截器) service.interceptors.response.use( res => { removePending(res.config) return res }, error => { return Promise.reject(error) } ) export default service
爲了方便,咱們還須要定義一套固定的 axios 返回的格式,這個咱們直接定義在全局便可。在 types/ajax.d.ts
文件中寫入
declare namespace Ajax { // axios 返回數據 export interface AxiosResponse { data: AjaxResponse } // 請求接口數據 export interface AjaxResponse { code: number, data: any, message: string } }
接下來,咱們將會把全部的 axios
放到 vuex
的 actions
中作統一管理
store 下面,一個文件夾表明一個模塊,store 大體目錄以下
├── home // 主目錄 ├── index.ts // vuex state getters mutations action 管理 ├── interface.ts // 接口管理 └── index.ts // vuex 主入口
在 home/interface.ts
中管理相關模塊的接口
export interface HomeContent { name: string m1?: boolean } export interface State { count: number, test1?: Array<HomeContent> }
而後在 home/index.ts
定義相關 vuex
模塊內容
import request from '@/service' import { State } from './interface' import { Commit } from 'vuex' interface GetTodayWeatherParam { city: string } const state: State = { count: 0, test1: [] } const getters = { count: (state: State) => state.count, message: (state: State) => state.message } const mutations = { INCREMENT (state: State, num: number) { state.count += num } } const actions = { async getTodayWeather (context: { commit: Commit }, params: GetTodayWeatherParam) { return request.get('/api/weatherApi', { params: params }) } } export default { state, getters, mutations, actions }
而後咱們就能在頁面中使用了啦
<template> <div class="home"> <p>{{ count }}</p> <el-button type="default" @click="INCREMENT(2)">INCREMENT</el-button> <el-button type="primary" @click="DECREMENT(2)">DECREMENT</el-button> <el-input v-model="city" placeholder="請輸入城市" /> <el-button type="danger" @click="getCityWeather(city)">獲取天氣</el-button> </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator' import { State, Getter, Mutation, Action } from 'vuex-class' @Component export default class Home extends Vue { city: string = '上海' @Getter('count') count: number @Mutation('INCREMENT') INCREMENT: Function @Mutation('DECREMENT') DECREMENT: Function @Action('getTodayWeather') getTodayWeather: Function getCityWeather (city: string) { this.getTodayWeather({ city: city }).then((res: Ajax.AjaxResponse) => { const { low, high, type } = res.data.forecast[0] this.$message.success(`${city}今日:${type} ${low} - ${high}`) }) } } </script>
至於更多的改造,這裏我就再也不介紹了。接下來的小節將介紹一下 ts 在 vue 文件中的一些寫法
這裏單頁面組件的書寫採用的是 vue-property-decorator 庫,該庫徹底依賴於 vue-class-component ,也是 vue 官方推薦的庫。
單頁面組件中,在 @Component({})
裏面寫 props
、data
等調用起來極其不方便,而 vue-property-decorator
裏面包含了 8 個裝飾符則解決了此類問題,他們分別爲
@Emit
指定事件 emit,可使用此修飾符,也能夠直接使用 this.$emit()
@Inject
指定依賴注入)@Mixins
mixin 注入@Model
指定 model@Prop
指定 Prop@Provide
指定 Provide@Watch
指定 Watch@Component
export from vue-class-component
舉個
import { Component, Prop, Watch, Vue } from 'vue-property-decorator' @Component export class MyComponent extends Vue { dataA: string = 'test' @Prop({ default: 0 }) propA: number // watcher @Watch('child') onChildChanged (val: string, oldVal: string) {} @Watch('person', { immediate: true, deep: true }) onPersonChanged (val: Person, oldVal: Person) {} // 其餘修飾符詳情見上面的 github 地址,這裏就不一一作說明了 }
解析以後會變成
export default { data () { return { dataA: 'test' } }, props: { propA: { type: Number, default: 0 } }, watch: { 'child': { handler: 'onChildChanged', immediate: false, deep: false }, 'person': { handler: 'onPersonChanged', immediate: true, deep: true } }, methods: { onChildChanged (val, oldVal) {}, onPersonChanged (val, oldVal) {} } }
vuex-class 是一個基於 Vue、Vuex、vue-class-component 的庫,和 vue-property-decorator
同樣,它也提供了4 個修飾符以及 namespace,解決了 vuex 在 .vue 文件中使用上的不便的問題。
copy 一個官方的
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'] } }
到這裏,ts 在 .vue 文件中的用法介紹的也差很少了。我也相信小夥伴看到這,對其大體的語法糖也有了必定的瞭解了
.d.ts
文件,請從新啓動服務讓你的服務可以識別你定義的模塊,並重啓 vscode 讓編輯器也可以識別(真的噁心)tsconfig
,好比記得把 strictPropertyInitialization
設爲 false,否則你定義一個變量就必須給它一個初始值。rootGetters
諸如此類的還有一堆,但更多的得大家本身去探尋。接下來,我將談談大型項目中團隊協做的一些規範
一個大的項目,確定是多人一塊兒並行,裏面不只有前端團隊的合做,還有與產品同窗的需求探(si)討(bi),以及和後端同窗的聯調,甚至於還須要本身或者依靠 SRE 進行一些服務的配置。
既然項目是基於 vue + ts 的且是多人協做,那麼開發規範確定是必須的,這樣可讓並行開發變的容易起來。下面,我從當時我制定的規範中抽出一些給小夥伴們作個參考(僅作參考哈)
i. 頁面開發擺放順序
<template> </template> <script lang="ts"> </script> <style lang="scss"> </style>
ii. CSS 規則(使用 BEM 命名規則避免樣式衝突,不使用 scoped)
<template> <div class="home"> <div class="home__count">{{ count }}</div> <div class="home__input"></div> </div> </template> <style lang="scss"> .home { text-align: center; &__count {} &__input {} } </style>
iii. 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
組件引用,mixins,filters 等放在 @Component 裏面
<script lang="ts"> @Component({ components: { HelloWorld }, mixins: [ Emitter ] }) export default class Home extends Vue { city: string = '上海' @Prop({ type: [ Number, String ], default: 16 }) size: number | string @State('state') state: StateInterface @Getter('count') count: Function @Action('getTodayWeather') getTodayWeather: Function @Mutation('DECREMENT') DECREMENT: Function @Watch('count') onWatchCount (val: number) { console.log('onWatchCount', val) } // computed get styles () {} created () {} mounted () {} destroyed () {} // methods getCityWeather (city: string) {} } </script>
iv. vuex 模塊化管理
store 下面一個文件夾對應一個模塊,每個模塊都有一個 interface 進行接口管理,具體例子上文中有提到
v. 路由引入姿式
路由懶加載,上文中也有例子
vi. 文件命名規範
單詞小寫,單詞之間用 '-' 分隔,如圖
名詞在前,動詞在後,如圖
相同模塊描述在前,不一樣描述在後
千萬記住如下三點:
要有禮貌的探(si)討(bi)
要頗有禮貌的探(si)討(bi)
要很是有禮貌的探(si)討(bi)
具體細節我曾在知乎裏面有過回答,這裏不贅述了。傳送門:先後端分離,後臺返回的數據前端無法寫,怎麼辦?
上一個點,談了一下開發層面的的協做。這裏,談一談人效提高。
你們都知道,一個項目是否可以在預約的期限中完成開發 + 聯調 + 測試 + 上線,最重要的由於就是每一個人作事的效率。咱們不能保證你們效率都很高,但咱們得保障本身的開發效率。
需求一下來,首先咱們得保證的就是本身對需求的認知。通常對於老手來講,把需求過一遍內心就大體清楚作完這個需求大概須要多少時間,而新手則永遠對完成時間沒有一個很好的認知。
那麼,如何提高本身的開發效率呢?
文章到這也差很少了。聊了聊項目立項前的選型,也聊了聊項目初期的基礎建設,還聊了聊 ts 在 .vue 中的使用,甚至項目開發中團隊協做的一些事情也有聊。但畢竟文筆有限,不少點並不能娓娓道來,大多都是點到爲止。若是以爲文章對小夥伴們有幫助的話,請不要吝嗇你手中的贊
若是小夥伴大家想了解更多的話,歡迎加入鄙人的交流羣:731175396
我的準備從新撿回本身的公衆號了,以後每週保證一篇高質量好文,感興趣的小夥伴能夠關注一波。
文章的最後,給團隊打波廣告:
美團 基礎研發平臺/前端技術中心 上海側招人啦 ~~~
前端開發 高級/資深
崗位福利: 15.5薪,15.5寸Mac,薪資25K-45K,股票期權。
工做職責:
職位要求:
加分項:
對以上職位感興趣的同窗歡迎先加羣:731175396,後聯繫我