据说Vue3的更新对typescript做了更好的适配,所以正好借此机会学习一下typescript基础。
确保vue/cli版本高于4.5运行vue create project-name
后选择Manually select features
及手动选择需要的特性,这里注意选择TypeScript支持,并确保选择Vue版本时选择的时3版本。我这里还选择了Vue Rotuer
(这里注意该项目demo代码是根据vue-class-component这个插件的语法编写的,vue3官方文档中找不到相应文档,这里针对该方式编写代码也只进行基础功能的测试)
入门
通过vue/cli创建的typescript vue3项目的main.ts和js开发模式基本无异所以直接打开Home组件查看系统生成的代码。
import { Options, Vue } from 'vue-class-component';
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
@Options({
components: {
HelloWorld,
},
})
export default class Home extends Vue {}
可以看到系统默认生成的代码中包含两个重要部分,@Options
和export default class Home extends Vue
。
@Options
可以在这里生命所有Vue实例拥有的属性,props,name,data,components等。
export default class Home extends Vue
任何再该类中声明的普通变量都可视为data中的内容。
任何在该类中定义的普通方法都可视为methods中的内容(与生命周期钩子函数同名除外)。
任何与生命周期同名的函数都可视为Vue生命周期钩子函数。
该类的get方法可当作computed属性使用。
下面代码是上面所述功能的demo
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
<p style="color: red">{{ name }}</p>
<p style="color: red">{{ nameWithAge }}</p>
<button @click="getName">Button</button>
</div>
</template>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src
@Options({
components: {
HelloWorld,
},
})
export default class Home extends Vue {
name = "wxm";
getName() {
console.log("getName invoked")
}
get nameWithAge(): string {
return this.name +":"+ 18;
}
created() {
console.log("created");
}
}
</script>
很简单,看着也很舒服但vue3官网对于这部分的文档已经删除。所以这种方式就暂且放一放,当看到这种形式的代码不至于完全不懂就行。下面删除系统默认生成的demo代码,跟着Vue3文档一步一步搭建新的Hello World。
Vue3使用defineComponent
方法定义组件它看起来是下面这样的
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
},
})
defineComponent
函数接收一个对象类型参数,在该参数内可以像过往一样编写props,data,methods等属性。暂且不管setup
函数,这个是vue3的新特性。并且现在编写的是typescript代码所以data中属性一旦被成功赋值也即是说typescript帮我们自动推断出给变量的类型值。当我们尝试对一个类型变量调用它不存在的方法或属性时编译器会拒绝编译。
export default defineComponent({
props:{
age:{
type:Number,
default:18
}
},
data(){
return{
name:"wxm",
count:0
}
},
methods:{
getName(){
window.console.log("getName invoked")
this.count.split("")//这里会报编译错误
}
},
setup() {
console.log(this)
},
})
当然ts的所有语法都是适用的。
组合式API
vue2开发时难免会遇到下面这种问题。
当编写一个重量级组件时单个vue文件会特别长。现在有这样一个功能需要实现:根据用户名查询设备,需要把用户名传递给后台。此时正在methods内编写该方法,但碰巧忘记了data中如何定义这个变量名,可能是name也可能是username。如果ide没给出提示就得像上翻看这个变量名,这使得代码变得不易阅读。Vue3组合试API其中一项优化就是解决这类问题。
setup()
函数
这里直接引用官方文档:
setup
选项应该是一个接受props
和context
的函数。此外,我们从setup
返回的所有内容都将暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。
setup函数就是解决该类问题最重要的一环,通过将数据与操作逻辑聚拢在一起以提高代码的可读性。随后还可以将相同逻辑代码抽离出到单个ts/js文件进而复用代码。
需要注意,虽然setup
返回的内容可以被组件访问但是返回的普通变量并不是响应式的。也就是说修改变量值不会触发页面刷新。所以需要使用vue提供的ref函数来创建这类变量。
import { defineComponent ,ref} from "vue";
export default defineComponent({
methods: {
getName() {
window.console.log(this.count++);
},
},
setup() {
const count = ref(5)
return{
count
}
},
});
因为数据与逻辑的聚拢,很多时候data属性不再使用,但不代表不能使用。data属性依旧是组件的可用属性。接下来将获取用户设备也集成到setup中。
import { defineComponent, ref } from "vue";
interface User {
name: string;
age: number;
}
export default defineComponent({
methods: {
getName() {
this.getDevByUser({
name: "wxm",
age: 18,
});
},
},
setup() {
const devs = ref<Array<string>>([]);
const getDevByUser = (user: User) => {
if (user.name == "wxm") {
devs.value=['dev1','dev2']
}
};
return {
devs,
getDevByUser,
};
},
});
值得注意的是,在setup
函数内需要通过变量名.value
的方式操作变量,外部则不需要但当将此类变量打印在控制台时可以发现实际上除了基本数据类型这些对象都是代理对象,不过无妨使用起来都是一样的。
setup
内注册生命周期钩子
setup
内还可以注册生命周期钩子前提是借助vue提供的几个新函数。他们的命名多是在原钩子函数前加上on并遵循驼峰命名,例如mounted的名字是onMounted
并且这些工具函数是从vue中导出的。他们接收一个函数类型参数将在生命周期钩子执行时被调用。它看起来是下面这样的
import { defineComponent, ref ,onMounted} from "vue";
export default defineComponent({
setup() {
onMounted(()=>{
console.log("mounted invoked")
})
return {};
},
});
类似的vue能够导出的新函数内还有一个watch
函数,不难猜到它是为了在setup
内设置watch功能而设计的。它接收量个参数:检测的属性,属性变更的回调函数。
import { defineComponent, ref, watch } from "vue";
export default defineComponent({
methods: {
countIncrement() {
this.count++
},
},
setup() {
const count = ref(0);
watch(count, (n, o) => {
console.log(n, o);
});
return {
count,
};
},
});
当然computed属性也能在函数外创建,对应的从vue中导出computed
函数,将其返回值暴露给组件。如下
import { defineComponent, ref, computed } from "vue";
export default defineComponent({
setup() {
const count = ref(1);
const computeCount = computed(() => {
return count.value * 2 + 5;
});
return {
computeCount
};
},
});
setup
使用注意事项
setup接收两个参数,props和context。其中不应该直接对props进行解构。因为props是响应式的解构props时使用vue提供的另一个工具函数toRefs
。并且setup执行时组件实例还没有被创建。任何组件实例内的属性都不能在setup内访问更不要试图使用this访问组件实例。如果输出this会发现此时this指向undefine。
从setup中抽离函数
回到最初的例子
import { defineComponent, ref } from "vue";
interface User {
name: string;
age: number;
}
export default defineComponent({
methods: {
getName() {
this.getDevByUser({
name: "wxm",
age: 18,
});
},
},
setup() {
const devs = ref<Array<string>>([]);
const getDevByUser = (user: User) => {
if (user.name == "wxm") {
devs.value= ["dev1","dev2"]
}
};
return {
devs,
getDevByUser,
};
},
});
现在又多个页面可能会用到设备相关功能,完全可以将其抽离到单独的文件中供其他内容访问。功能应该类似与之前的mixin
但远比mixin
强大。现在新建一个util.ts
文件
//util.ts
import { ref } from 'vue'
interface User {
name: string;
age: number;
}
export default function getDevs() {
const devs = ref<Array<string>>([]);
const getDevByUser = (user: User) => {
if (user.name == "wxm") {
devs.value = ["dev1", "dev2"]
}
};
return {
devs,
getDevByUser
}
}
将设备相关功能抽离出来,然后在原页面引用
//Home.vue
import { defineComponent } from "vue";
import getDev from './util'
export default defineComponent({
methods: {
getName() {
this.getDevByUser({
name: "wxm",
age: 18,
});
},
},
setup() {
const {devs,getDevByUser} = getDev()
return {
devs,
getDevByUser,
};
},
});
相比与mixin
复用相同的函数,组合式API将同一关注点的大部分代码分离复用。
相较于vue2的改动
一些平台禁止外链
https://vue3js.cn/docs/zh/guide/migration/introduction.html#%E6%A6%82%E8%A7%88
总结
之前没用过Typescript用过之后只能说:真香,或者说VS Code + Typescript真香。
最后 大幻梦森罗万象狂气断罪眼\ (•◡•) /