很久不寫博客了,最近在使用ts和tsx開發vue類項目,網上資料比較少,順便記錄一下方便同樣開發的人互相學習共同進步。
本篇文章儘量不遺漏重要環節,本着真正分享的心態,不做標題黨
下面進入正題:
由於現在vue的官方腳手架已經非常完善我們就不單獨配置webpack了,節省大量的時間成本。
首先使用@vue/cli創建一個vue模版項目(記得是@vue/cli不是vue-cli還不知道的人可以點此傳送門進入先導學習站)。
在自己覺得合適的目錄下打開命令行輸入如下代碼,創建一個名爲vue-tsx的項目
vue create vue-tsx
接下來的步驟vue的cli會給出相應的配置提示,着重配置已截圖
第一步選擇自定義配置
第二步選擇如圖的配置
剩下的按個人喜好自己選擇就可以了
創建完成後的項目結構如圖所示
從圖上看出這是一個普通的vue模版項目,使用typescript語言開發
默認使用的仍然是vue的template進行渲染
正常在這種情況下就可以開發直接寫代碼了,模版項目所提供的示例代碼已經很良心了
不過今天要介紹的是使用tsx語法進行開發vue項目
首先介紹一下什麼事tsx
其實他就是typescript的jsx語法
那麼什麼是jsx呢?從這裏介紹的話又變成無腦長文了,所以直接掠過,想了解jsx的人可以先去看一下react的開發文檔5分鐘上手
,但是必須要說的是爲什麼要使用tsx來寫vue項目?vue提供的自帶模版不香嗎?網友對vue和react的爭論喋喋不休到現在,我在這裏給的答案其實很簡單,vue和react之間沒有好壞之分,論性能差距在使用上已經近乎55開,論生態各自都很完善了,這兩個框架並存的原因很簡單,vue的作者在自己的文章中曾經也提過,創造vue項目只不過是想有一個“自己用起來順手的框架”。答案就在這句話上,所以我覺得沒必要爭論哪個好,其實沒有可比性,只是喜歡的人各自會覺得對方好而已。
所以今天介紹tsx開發vue項目其實原因很簡單,就是讓適應了jsx語法的人能無縫從react過渡到vue上。就是給用起來舒服的人準備了一個方案而已。
所以繼續我們的項目搭建
接下來先運行一下剛纔的模版項目
在vue-tsx目錄下打開命令行輸入
npm run serve
出現如下圖片證明以上操作全部沒問題
以上操作全部通過後可以關閉服務器了,我們下一步要做的是修改項目的目錄結構
首先
刪除views文件夾,
刪空components文件夾的內容保留文件夾,
刪除App.vue文件
項目結構與圖片一樣即可,其他地方暫時不要動
首先將router文件夾中的index.ts文件內容修改爲如下代碼
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes:any = []
const router:VueRouter = new VueRouter({
routes
})
export default router
然後修改main.ts中的代碼爲如下
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
並在src下新建名爲App.tsx的文件內容爲
import { Vue ,Component } from 'vue-property-decorator';
@Component
export default class App extends Vue{
render(){
return (
<div>I am the first module of tsx for the Vue Project! </div>
)
}
}
其他地方暫時不需要改造
然後重新使用npm run serve啓動項目訪問默認地址
當界面出現如上圖情況的歡迎語,說明我們已經成功的在vue項目中使用tsx模版語法了
與react一樣tsx在vue項目中也是使用render方法混合html模版來實現界面渲染,用法與react一樣,他在vue項目中會被解析成vue的render:h => h()形式去渲染頁面,所以使用tsx模版開發vue帶來的負面影響是我們犧牲了vue自帶的很多語法糖,如最基本的v-if,v-for,prop.sync等等,不過他帶來的好處是我們可以使用tsx語法更自由的去處理這些問題,並且使用tsx可以進行更好的抽象以及工程化的去處理前端項目,各有千秋。
我們在下文會詳細的介紹相關內容,首先還是繼續進行下去
光使用tsx實現了App.vue相當於沒有解決任何問題。
我們下一步要改造的是VueRouter
所以
第一步在src下創建一個名爲pages的文件夾
第二步在pages下分別創建Index.tsx,以及Login.tsx兩個文件
成功後如下圖
分別在兩個文件中輸入默認代碼
Login.tsx
import { Vue , Component } from 'vue-property-decorator'
@Component
export default class Login extends Vue{
render(){
return (
<div>login.tsx</div>
)
}
}
Index.tsx
import { Vue , Component } from 'vue-property-decorator'
@Component
export default class Index extends Vue{
render(){
return (
<div>index.tsx</div>
)
}
}
然後我們把這兩個路由的頁面加入到router中
在router文件夾下的index.ts中加入如下代碼
import Vue from 'vue'
import VueRouter from 'vue-router'
import Index from '@/pages/Index'
Vue.use(VueRouter)
const routes:Array<any> = [
{
path:'/',name:'index',component:Index
},
{
path:'/login',name:'login',component:() => import('@/pages/Login')
}
]
const router:VueRouter = new VueRouter({
routes
})
export default router
這裏我們子啊router中使用了直接引用和懶加載兩種方式去加載路由界面來測試vue對tsx的兼容性
最後一步修改App.tsx,在文件中加入路由的容器,代碼如下
import { Vue ,Component } from 'vue-property-decorator';
@Component
export default class App extends Vue{
render(){
return (
<div>
<router-view></router-view>
</div>
)
}
}
以上步驟嚴格按照說明編寫後,無需重啓服務我們訪問默認路徑就可以看到變化(如中途遇見問題可重啓服務)
在訪問http://localhost:8080/#/,以及http://localhost:8080/#/login兩個地址分別顯示如下圖就說明成功了,如有問題請自行檢查
到這裏我們已經實現了所有頁面使用tsx來替換vue模版
到目前渲染數據都沒有問題,但是我們還沒有設置頁面的樣式,下面就介紹一下如何在tsx中使用css樣式
由於我在創建項目的時候使用的是node-sass來加載sass-loader所以這裏我們使用的scss模版來編寫css
以App.tsx爲例,介紹 一下如何使用scss在tsx中
第一步在src下創建一個名爲app.module.scss的文件(中間一定要加.module,這個是vue腳手架的規範,如果想自由命名請熟讀@vue/cli的官方文檔,本文我們暫時採取默認方式)在其中設置如下樣式
.app{
background: lightblue;
}
第二步改造App.tsx的代碼給根標籤加一個class="app"
import { Vue ,Component } from 'vue-property-decorator';
//以模塊的形式引入當前的樣式文件
import style from './app.module.scss';
@Component
export default class App extends Vue{
render(){
return (
//這裏代表將app.module.scss中的.app這個class注入到標籤中
<div class={style.app} >
<router-view></router-view>
</div>
)
}
}
我們會發現當前的網頁背景顏色會變成我們設置的light-blue如圖所示
但是這裏有一個問題,觀察當前node命令行窗口會發現Cannot find module './app.module.scss'.錯誤
這個錯誤是由於當前的項目默認是不認識scss語法的,我們需要在項目src下的shims-vue.d.ts文件中加入如下代碼並重啓服務
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
//解決scss文件報錯問題
declare module '*.scss'{
const sass:any
export default sass
}
執行此方法後就不會報錯了
接下來我們來測試一下如何使用tsx中的scss
首先我們試試可不可以在樣式中對html和body標籤進行操作,app.module.scss修改爲如下
html,body{
width: 100%;
height: 100%;
margin: 0;
color: red;
}
.app{
background: lightblue;
height: 100%;
}
當我們重新訪問頁面時出現下圖結果
接下來我們給Index.tsx中的代碼修改一下,測試一下可不可以用app.module.scss影響他的子組件,加入index-page的className
import { Vue , Component } from 'vue-property-decorator'
@Component
export default class Index extends Vue{
render(){
return (
<div class="index-page">index.tsx</div>
)
}
}
然後在app.module.scss中設置
html,body{
width: 100%;
height: 100%;
margin: 0;
color: red;
}
.app{
background: lightblue;
height: 100%;
.index-page{
text-align: center;
color: blue;
}
}
訪問頁面後,index.tsx的字體並沒有居中也沒有變色,查看控制檯發現樣式並沒有注入進來,看來當前的模塊引入影響的只是當前組件本身,不過在vue的模版中的style標籤下可以通過/deep/的方式來實現樣式的跨組件穿透,如果我一定要在app.module.scss中設置其他組件的公共樣式,把它當成一個基礎樣式組件來用呢?當然有解決方案,我們只需要做一個簡單的修改
html,body{
width: 100%;
height: 100%;
margin: 0;
color: red;
}
.app{
background: lightblue;
height: 100%;
/*穿透效果*/
:global(.index-page){
text-align: center;
color: blue;
}
}
使用:global就可以實現/deep/的功能
到這裏樣式的基本使用介紹完畢。
接下來是核心環節,就是關於自定義組件以及在vue中如何使用ts開發
修改index.tsx的內容爲
import { Vue , Component } from 'vue-property-decorator'
@Component
export default class Index extends Vue{
//相當於js中的data中的其中一個屬性
private title?:string = '我是標題'
private author?:string = 'LeoZhang'
//相當於computed中的函數
get authorComputed(){
return `作者是:${this.author}`
}
render(){
return (
<div class="index-page">
<h2>{this.title}<small>by {this.author}</small></h2>
<p>{this.authorComputed}</p>
</div>
)
}
}
當前內容輸入成功說明你已經習慣了ts與js的區別,這個結構體現出了ts更加清晰的結構化代碼
接下來我們測試一下methods與v-model如何實現
Index.tsx修改爲如下內容
import { Vue , Component } from 'vue-property-decorator'
import style from './index.module.scss';
@Component
export default class Index extends Vue{
//相當於js中的data中的其中一個屬性
private title?:string = '我是標題'
private author?:string = 'LeoZhang'
//相當於computed中的函數
get authorComputed(){
return `作者是:${this.author}`
}
//生命週期函數
created(){
console.log('我是默認的生命週期')
}
//相當於methods
handleClick(arg:string):void{
this.title = arg;
}
render(){
return (
<div class="index-page">
<h2>{this.title}<small>by {this.author}</small></h2>
<p>{this.authorComputed}</p>
<button class={style['p-btn']} onClick={this.handleClick.bind(this,'我是新標題')}>改變標題</button>
<br/>
測試v-model改變author
<input v-model={this.author}/>
</div>
)
}
}
測試結果爲上圖內容
可以看出當前的tsx語法中v-model還是被繼續支持的不過v-on和v-bind都有相應的變化這裏首先看到的是v-on變成了on事件名的寫法,而且給事件傳參數使用的是.bind這裏與vue自帶的template是完全不一樣的
接下來我們在login.tsx中引入一個自定義組件來看一下自定義組件的參數和一些內容是否有變化
首先在components中聲明一個Test組件
組件代碼如下
import {
Vue,
Prop,
Watch,
Emit,
Model,
Component
} from 'vue-property-decorator'
@Component
export default class Test extends Vue{
//代表js的props屬性可在註解中設置類型是否必填默認值等
@Prop({required:false,type:String,default:'我是默認值'})
private msg?:string;
render(){
return (
<div class="test">
{this.msg}
</div>
)
}
}
然後在Login.tsx中做如下修改
import { Vue , Component } from 'vue-property-decorator'
import Test from '@/components/Test';
@Component
export default class Login extends Vue{
private title?:string = '我是Login頁面'
render(){
return (
<div>
{this.title}<br/>
<Test></Test>
</div>
)
}
}
此時訪問http://localhost:8080/#/login
如果顯示爲如下圖就說明已經配置成功一個基礎組件了
我們測試給定義的組件msg傳入一個參數
import { Vue , Component } from 'vue-property-decorator'
import Test from '@/components/Test';
@Component
export default class Login extends Vue{
private title?:string = '我是Login頁面'
private msg:string = '我是login傳入的msg'
render(){
return (
<div>
{this.title}<br/>
<Test msg={this.msg}></Test>
</div>
)
}
}
之後頁面的值如果變爲如圖,說明成功
不過此處會在node控制檯報錯,錯誤說明是檢測不到有msg這個參數類型因爲這個參數是我們後創建的vue在默認的組件中是檢測不到的所以爲了讓框架能不管我們自己創建的參數我們需要在shims-tsx.d.ts文件中加入如下代碼
declare module "vue/types/options" {
interface ComponentOptions<V extends Vue> {
[propName: string]: any;
}
}
之後重啓服務,這樣我們自定義組件的參數就不會出現報錯了
之後我們再測試一下給組件綁定v-model如何實現雙向綁定並監聽參數
將Test.tsx的代碼修改爲如下,代碼註釋已經添加到代碼中了
import {
Vue,
Prop,
Watch,
Emit,
Model,
Component
} from 'vue-property-decorator'
@Component
export default class Test extends Vue{
//代表js的props屬性可在註解中設置類型是否必填默認值等
@Prop({required:false,type:String,default:'我是默認值'})
private msg?:string;
//Model裝飾器相當於model屬性參數相當於給event賦值,裝飾器設置的屬性相當於設置prop屬性
@Model('cc')
@Prop({required:false,type:String,default:'我是雙向綁定的默認值'})
private value?:string;
//相當於調用this.$emit('cc',val)
@Emit('cc')
sendValue(val:string){}
//相當於watch下監聽value屬性的變化
@Watch('value')
handleWatchValue(newVal:string,oldVal:string){
console.log(newVal,oldVal);
}
handleInput(event:InputEvent){
//相當於調用this.$emit('cc',val)
this.sendValue(event.target.value);
}
get getValue(){
return `組件內部的value:${this.value}`
}
render(){
return (
<div class="test">
{this.msg}<br/>
<input value={this.value}
onInput={this.handleInput}/>
<br/>
{this.getValue}
</div>
)
}
}
然後在Login.tsx中修改爲如下代碼
import { Vue , Component } from 'vue-property-decorator'
import Test from '@/components/Test';
@Component
export default class Login extends Vue{
private title?:string = '我是Login頁面'
private msg:string = '我是login傳入的msg'
private value:string = '我是外部傳入的value'
get getValue(){
return `組件外部的value:${this.value}`
}
render(){
return (
<div>
{this.title}<br/>
{this.getValue}
<Test msg={this.msg} v-model={this.value}></Test>
</div>
)
}
}
成功後會得到如下結果
可以看到在Test組件中創建的input標籤可以觸發value屬性的變化,並同時通知了外部傳入的value屬性進行變更,大量使用ts的裝飾器(與java中的註解原理類似)這樣可以更加直觀的進行邏輯歸納,適合結構化開發。
到這裏vue+tsx的基本入門關已經過了,掌握本文的技巧之後便開啓了vue的tsx之旅。
至於本文所寫的代碼我已經上傳到了碼雲倉庫
本文代碼地址:https://gitee.com/LeoZhang1989/vue-tsx.git
自己未創建成功的人可以down一下源代碼簡單的參考一下
本文只是簡單的介紹一下vue和tsx整合的起步姿勢,並沒有交代ts原理@vue/cli的原理
所以關於相關內容不清楚的需要對應的去補補課尤其是ts原理,本文是建立在會使用ts的基礎上進行下去的所以如果覺得ts有看不懂的地方要去typescript的官方網站查閱文檔
至於如果想在tsx項目中使用vuex和axios等相關的整合如果我之後有時間會繼續寫,爲了避免沒時間
我寫了一個 vue+vant+vuex+axios+mock+tsx的整合版本
項目地址在這裏https://github.com/keaderzyp/vue-vant-tsx.git
如果想全面瞭解整個生態圈,需要各位同學去所有相關的技術棧研讀核心的文檔,去學習原理,這樣能幫助大家理解框架爲什麼這麼搭建,怎麼搭建對自己是最合理的。
尾聲:
對於初學者的一些忠告,不要追求“唯一解”,編程是沒有標準答案的,所以你會發現十個大神實現一樣的功能會有10+種開發方式和框架組合爲什麼是10+,這是因爲同一個人在不同時期實現同樣的功能也會寫出n個版本,至於爲什麼aab,abb,aaa的這麼搭配,只是每個人在不同時期會使用自己覺得最舒服最合理的解決方式進行開發,所以,不要糾結寫法固定,要明白原理,懂原理之後自然會知道怎麼使用纔是最適合自己,最適合當前項目的,舉個最簡單的例子,同樣的一個app或應用,大公司和小公司的開發流程和代碼規模是差距巨大的,最後實現的結果其實是一樣的,深究的話差別主要在性能和可維護性上,至於爲什麼小公司不按照bat等大廠規範去做,其實是迫於現實,他們選擇的並不是最優解而是最適合自己當前場景的解決方案比如一個商城的架構,在人員配置極少,工期極短,服務器資源有限的情況,小公司一定會選取門檻低週期短的半成品框架去進行開發,最大的去降低項目風險和成本,所以在開發前端項目的時候面臨市場沒特定規範,沒有統一的開發工具沒有統一的框架使用規範等等情況的時候,先想最後自己想要的結果是什麼,優先級最高的是哪方面,這樣技術選型和開發方式自然就出來了,不過當然推薦的是在可控範圍內,要做到高質量,高可用,今天就寫到這裏,有緣的話會抽空寫下一篇文章,不過下一篇文章很可能與之前的又接不上了哈哈哈哈