TypeScript 已經出來好久了,不少大公司不少大項目也都在使用它進行開發。上個月,我這邊也正式跟進一個對集團的大型運維類項目。javascript
項目要作的事情大體分爲如下幾個大模塊css
每個模塊要作的事情也不少,因爲牽扯到公司業務,具體要作的一些事情這裏我就不一一列舉了,反正項目總體規模仍是很大的。html
在作了一些技術調研後,再結合項目以後的開發量級以及維護成本。最終我和同事在技術選型上得出一致結論,最終選型定爲 Vue 最新全家桶 + TypeScript。前端
那麼問題來了,爲何大型項目非得用 TypeScript 呢,ES六、7 不行麼?vue
其實也沒說不行,只不過我我的更傾向在一些協做開發的大型項目中使用 TypeScript 。下面我列一些我作完調研後本身的一些見解java
首先,TypeScript 具備類型系統,且是 JavaScript 的超集。 JavaScript 能作的,它能作。JavaScript 不能作的,它也能作。node
其次,TypeScript 已經比較成熟了,市面上相關資料也比較多,大部分的庫和框架也讀對 TypeScript 作了很好的支持。python
而後,保證優秀的前提下,它還在積極的開發完善之中,不斷地會有新的特性加入進來webpack
JavaScript 是弱類型而且沒有命名空間,致使很難模塊化,使得其在大型的協做項目中不是很方便ios
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,後聯繫我瞭解更多,或者直接投簡歷到我郵箱 xuqiang13@meituan.com