本篇文章尽量不遗漏重要环节,本着真正分享的心态,不做标题党
下面进入正题:
由于现在vue的官方脚手架已经非常完善我们就不单独配置webpack了,节省大量的时间成本。
首先使用@vue/cli创建一个vue模版项目(记得是@vue/cli不是vue-cli还不知道的人可以点此传送门进入先导学习站)。
在自己觉得合适的目录下打开命令行输入如下代码,创建一个名为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之旅。