这篇文章是我在 vue官网 以及一些其余地方学习v3的一些学习记录与心得,若是有理解不到地方欢迎你们指正。javascript
在v3版本中将data的返回值进行了标准化,只接受返回Object的Function, 而在v2版本中同时支持返回Object和返回Object的Function。html
v3版本中关于Object的合并是覆盖,而不是合并。前端
const mixinObject = {
data(){
return {
user:{
id:1,
name:'jack'
}
}
}
}
const CompA = {
mixins: [mixinObject],
data() {
return {
user: {
id: 2
}
}
}
}
// 结果是
user: {
id: 2
}
复制代码
在v2版本中,一般使用$on, $off来实现全局事件总线,使用$once绑定监听一个自定义事件,其实这些在实际项目中用到的也不会特别多,在v3版本移除后,能够经过一些外部库来实现相关需求。例如 mittvue
v3版本中删除了过滤器, {{ calcNum | filter }}
将不在支持,官方建议使用computed
属性替换过滤器(well done ~)。java
在v3版本中支持了能够有多个根节点的组件,能够减小节点层级深度。但也但愿开发者可以明确语义。react
<template>
<header></header>
<main></main>
<footer></footer>
</template>
复制代码
在将v3版的变化以前咱们先来回顾一下v2版本的函数式组件,它有两种建立方式: functional
attribute 和 { functional : true }
选项,代码分别以下git
// attribute 方式
<template functional>
<img :src="src" />
</template>
<script> ... </script>
// 选项方式
export default = {
functional:true,
props:{
...
},
render:function(createElement,context){
...
/** context传递了一些参数: props,slots,parent,listeners,injections **/
}
}
复制代码
v2版本中组件有两种组件类型:有状态组件和函数式组件(无状态组件),相对于有状态组件,函数式组件不须要被实例化,无状态,没有生命周期钩子,它们的渲染速度远高于有状态组件。每每一般被适用于功能单一的静态展现组件从而优化性能。除此以外,函数式组件还有返回多个根节点的能力。github
在v3版本中,有状态组件和函数式组件之间的性能差别已经大大减小,在大部分场景下几乎能够忽略不计。因此函数式组件惟一的优点就在一返回多节点的能力,但这种一般运用比较少,且组件每每比较简单,具体语法糖以下:vue-cli
// 函数建立
import { h } from 'vue'
const DynamicHeading = (props, context) => {
// context是一个包含 attrs,slots,emit的对象
return h(`h${props.level}`, context.attrs, context.slots)
}
DynamicHeading.props = ['level']
export default DynamicHeading
// 单文件组件(SFC)
<template>
<component v-bind:is="`h${$props.level}`" v-bind="$attrs" />
</template>
<script> export default { props: ['level'] } </script>
复制代码
v3版本中新增了一个新的全局APIcreateApp
,经过ES模块的方式引入。调用createApp
返回一个应用实例,该应用实例暴露出来全局API,这是Vue3的新概念,主要解决了不一样"app"之间可以共享资源(配置,全局组件,指令等等)。咱们来对比一下v3和v2分别建立应用的改变。typescript
// v2 建立
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
// v3 建立
import { createApp } from 'vue'
import App from './App.vue'
var app = createApp(App);
app.mount('#app')
复制代码
在"app"之间共享配置的一种方式是工厂模式:
import { createApp } from 'vue'
import App1 from './App1.vue'
import App2 from './App2.vue'
const createMyApp = options => {
const app = createApp(options)
app.directive('focus' /* ... */)
return app
}
createMyApp(App1).mount('#foo')
createMyApp(App2).mount('#bar')
复制代码
v3将能够全局改变Vue行为的API从原来的Vue构造函数上转移到了实例上了。列表以下
v2全局API | v3全局API |
---|---|
Vue.config | app.config |
Vue.config.productionTip | 移除 |
Vue.config.ignoredElements | app.config.ignoredElement |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
除此以外,还有一些全局API和内部组件都作了重构,考虑到tree-shaking,只能经过ES模块的方式导入,意味着当你的应用程序中没用到的组件和接口都不会被打包。受影响的API列表以下:
// 错误使用
import Vue from 'vue'
Vue.nextTick(() => {
// 一些和DOM有关的东西
})
// 正确使用
import { nextTick } from 'vue'
nextTick(() => {
// 一些和DOM有关的东西
})
复制代码
重头戏!!!
这是v3中的新概念,vue给咱们提供了一些新的api,这些新的api在一个地方使用,而这个地方就是vue给咱们新提供了一个从组件初始化到销毁的过程当中咱们可以作一些事情的地方,我将之理解为钩子函数——Setup
,在Setup里,你能够作本来你能在vue组件里面能作的全部事情,好比你在created
,mounted
,computed
,watch
,methods
里面作的全部事情,都能在Setup
里面完成,那要怎么才能作到呢,就是要经过vue提供的那些新的API。
那这个东西主要解决什么事情呢?咱们都知道组件的做用就是对功能的提取以及代码的复用。这使得咱们的程序在灵活性和可维护性上能走的更远,可是这还不够,当一个组件内部的逻辑很复杂的时候,咱们将逻辑分别散落在 created
,mounted
,methods
,computed
,watch
里面,而后整个文件代码长达好几百行。这对于没有编写这个组件的人来讲,尝试去理解这些逻辑代码无疑是最头疼的事情。而Setup
正是解决了这个事情,将全部的逻辑都集中起来在Setup
中处理。从今之后,当你想制造一辆汽车的时候,你不再用去全世界进口各类零件,你在Setup
工厂中就能完成。
让咱们来见识一下Setup
和那些新的API
的使用以及做用(据说这种东西才被称之为干货???):
若是你听懂了我上面所说的,那我开局这么写你应该也能理解了:
<template>
<div class="demo"> </div>
</template>
<script> export default { name:"Demo", data(){ return {} }, setup () { console.log('run'); } } </script>
// 当运行起来打印了run
复制代码
Setup
能够返回一个对象,你能够在组件的其余地方访问这个对象中的属性。
注意:在执行
Setup
的时候还没有建立组件实例,因此在Setup
中没有this
。不过它提供了两个接收参数——props
和context
。在Setup
中没法访问组件中其余的任何属性。
// 调用Demo组件
<Demo :name="'jac" a="1" b="2"></Demo>
// Demo 组件
<template> <div class="demo"> </div> </template>
<script> export default { name:"Demo", props:{ name:String, }, data(){ return {} }, setup (props,context) { console.log('props',props); // {name:jac} console.log('attrs', context.attrs); // {a:1,b:2} console.log('slots', context.slots); // {} console.log('emit', context.emit); // function let shareProp = 'hallo,v3'; // 返回两个属性 shareProp, changeProp return { shareProp } }, mounted() { this.shareProp = 'changed'; }, } </script>
复制代码
咱们会发现试图并无更新,咱们发现setup
返回的对象不是响应式的,响应式咱们应该不陌生,在data()选项中的property都是响应式的,那是应为Vue在组件初始化的过程当中就已经对data()中的property建立了依赖关系。因此当property发生变化时,视图即会自动更新,这就是响应式。那怎么让它变成响应式的呢?
咱们能够经过ref
,reactive
建立响应式状态,
咱们使用ref
能够建立基础数据类型和复杂数据类型的响应式状态,使用reactive
只能建立复杂数据类型的响应式状态,若是使用建立数据类型不正确,控制台会给出对应的警告value cannot be made reactive: **
。那ref
和reactive
的区别到底在哪里呢?
const refNum = ref(10);
const refObj = ref({
name:'jac',
age:20
});
const reactiveNum = reactive(10);
const reactiveObj = reactive({
name: 'jac',
age: 20
})
console.log('refNum',refNum);
console.log('refObj',refObj);
console.log('reactiveNum', reactiveNum);
console.log('reactiveObj', reactiveObj);
复制代码
结果以下:
当ref
建立的是复杂数据类型的时候内部其实也是用reactive建立的。因此ref
也是能够建立复杂数据类型的响应状态的,只是在setup
中写法会有所不一样。
setup(){
const refObj = ref({
name:'jac',
age:20
});
const reactiveObj = reactive({
name: 'jac',
age: 20
})
// ref 方式的更新
const changeRefObj = () => {
refObj.value.name="mac"
}
// reactive 方式的更新
const changeReactiveObj = () => {
reactiveObj.name = 'mac'
}
return {
...
}
}
复制代码
注意: 经过
ref
对值进行了包裹,在Setup
中你须要使用变量.value的方式进行访问和设置值,从Setup
中暴露出去的对象你能够直接经过this.变量访问。
<template>
...
</template>
<script> import { ref,reactive } from 'vue'; export default { ... setup (props,context) { ... let shareProp = ref('hallo,v2'); let info = reactive({name:'jac'}); const changeProp = ()=>{ shareProp.value = 'hallow,v3'; } return { shareProp, ... } }, mounted() { console.log(this.shareProp) }, } </script>
复制代码
小伙伴能够根据本身的编码习惯选择运用。
有时候咱们想经过解构的方式从一个复杂的响应式变量中剥离出一些变量时,咱们的代码多是这样的:
<template>
<button @click="changeObj">my name is {{info.name}}</button>
</template>
<script> import { ref, provide, reactive } from 'vue'; export default { name: 'Demo', setup(){ const info = reactive({name:'jac',sex:'男',age:18}); return { info } }, methods:{ changeObj(){ let { name,sex } = this.info; name = 'make' // 视图不会更新 } } } </script>
复制代码
这样会使咱们解构出来的两个property失去响应性,这个时候咱们须要使用toRef
和toRefs
从中解构出来,toRef
用于解构出单个property的响应式变量,toRefs
是将源对象中全部property都建立响应式变量,在经过解构的方式建立咱们对应的变量。
<template>
<button @click="changeObj">my name is {{info.name}}</button>
</template>
<script> import { ref, provide, reactive, toRef, toRefs} from 'vue'; export default { name: 'Demo', setup(){ const info = reactive({name:'jac',sex:'男',age:18}); return { info } }, methods:{ changeObj(){ // toRef // let name = toRef(this.info,'name'); // arg1: 源对象, arg2: 属性名称 // name.value = 'make' // toRefs // let { name } = toRefs(this.info); // arg1: 源对象 // name.value = 'make' // 视图成功刷新 } } } </script>
复制代码
在Setup
中咱们还能watch
属性,建立独立的computed
,还能够注册各类生命周期钩子,因为Setup
执行的阶段是围绕beforeCreate
和created
和进行的,因此本来在这两个生命周期中作的事情都可以放在Setup
中处理。
<template>
...
</template>
<script> import { ref, toRefs, watch, computed, onMounted } from 'vue'; export default { name:"Demo", props:{ name:String, }, ... setup (props,context) { console.log('我首先执行'); // 经过toRefs方法建立props的响应式变量 const { name: listenName } = toRefs(props); const count = ref(0); const changCount = ()=>{ count.value++; } const totalCount = computed(() => { return `总计数:${count.value}` }) // watch 会监听listenName变量,当listenName变量改变的时候会触发回调方法 watch(listenName,(newValue, oldValue)=>{ console.log(listenName.value); }) onMounted(()=>{ console.log('onMounted也执行了,结果输出以下'); console.log(count.value); changCount(); console.log(totalCount.value) }) return { count, changCount, totalCount, } }, created() { console.log('created执行了'); }, mounted() { console.log('mounted执行了'); }, } </script>
// 输出结果以下:
我首先执行
created执行了
onMounted也执行了,结果输出以下
0
总计数:1
mounted执行了
复制代码
看的出来,Setup
中注册的生命周期钩子函数要比外面注册的钩子函数先执行!
provide
和inject
一般状况下咱们在业务场景中使用很少,但在写插件或组件库的时候仍是有用的,provide
和inject
用于在"祖"组件提供属性,在子组件中注入使用。在setup中怎么使用呢?
// demo组件中提供
<template>
<Test />
</template>
<script> import { provide } from 'vue'; export default { name:"Demo", setup (props,context) { provide('name','jac') }, } </script>
// test组件中注入使用
<template> ... </template>
<script> import { inject } from 'vue'; export default { name:"Test", setup (props,context) { console.log(inject('name')); // jac }, } </script>
复制代码
以上是setup
的基本应用,须要好好的感觉它还须要在多种场景下去实际运用,才能更好的理解它棒在哪里,好比下面这段代码:
// a.js
import { ref, onMounted } from 'vue'
export default function doSomething(refName,fn){
const a = ref(0);
const b = ref(0);
// ...,
onMounted(() => {
fn();
})
return {
a,
b
}
}
//b.js
import doSomething from './a.js';
setup(){
const {a,b} = doSomething('box',()=>{
console.log('执行');
})
}
复制代码
我随便举的一个例子,假设你有一段逻辑在多个页面都须要用到,那咱们能够将这段逻辑抽离出来,这样让咱们的代码更精简,咱们不只可让组件复用,在也能更大幅度的提升一些业务代码的复用,还能集中处理业务代码,我相信对于咱们的开发体验仍是代码质量,都大有裨益。
v3中改变了v-model默认属性值和触发方法,value
=>modelVale
,input
=>update
。在自定义的组件中,容许咱们同时设置多个v-model。
// Demo.vue
export default {
name: "Demo",
props: {
title:String,
label:String
},
methods: {
titleEmit(value) {
this.$emit('update:title', value)
},
labelEmit(value) {
this.$emit('update:label', value)
}
},
}
// 调用Demo.vue
<Demo v-model:title="" v-model:label="" />
复制代码
v3推荐使用Typescript
,Typescript
不只支持ES6
的特性,还具有类型推导,能够帮助咱们在开发的过程当中就避免不少类型错误,在前端愈来愈复杂的如今,Typescript
可以支撑应用走的更远,在可维护性和扩展性上都更有优秀,拥抱改变。
怎么建立vue3+typescript的应用呢?
1.用vue-cli
yarn global add @vue/cli@next
# OR
npm install -g @vue/cli@next
// 下面循序渐进的建立
复制代码
2.采用vite(我的推荐,简单省事)
yarn create vite-app my-vue-ts --template vue-ts
复制代码
这一节咱们就从应用层面简单的了解了一下v3的一些改变,没有深挖原理与实现。
v3 一镜999次 卡!