关于学习Vue3.0的一些个人心得体会,分享给同学们
创建一个Vue3.0的项目
1、npm init vite-app vue3.0
2、cd vue3.0
3、npm install
4、npm run dev
3.0的一些亮点
-
Performance
:性能比
Vue 2.0
更强。 -
Tree shaking support
:可以将无用模块剪辑,仅打包需要的。 -
Composition API
:组合
API
-
Fragment, Teleport, Suspense
:
Fragment
,
Teleport
,
Suspense
-
Better TypeScript support
:更优秀的Ts支持 -
Custom Renderer API
组合API的一些简单介绍
1、setup()
beforeCreate 、created 之前执行
setup() 函数是 vue3 中,专门为组件提供的新属性。它为我们使用 vue3 的 Composition API 新特性提供了统一的入口, vue3取消了beforeCreate 、created两个钩子,统一用setup代替, 该函数相当于一个生命周期函数,vue中过去的data,methods,watch,computed等全部都用对应的新增api写在setup()函数中
2、ref()
声明单一基础数据类型 返回值是响应式对象
创建一个响应式的数据对象,ref() 函数调用的返回值是一个对象,这个对象上只包含一个 value 属性, 只在setup函数内部访问ref函数需要加
.value
3、reactive()
声明单一对象 (注意:解构会破坏代理proxy即双向绑定)
reactive() 函数接收一个普通对象,返回一个响应式的数据对象, 想要使用创建的响应式数据也很简单,创建出来之后,在setup中return出去,直接在template中调用即可; 在reactive()函数中访问ref()函数创建的对象不用
.value
4、isRef() 判断是否是通过ref()函数创建出来的对象
5、toRefs() 从组合函数返回反应对象
toRefs() 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据
6、computed() 计算属性 可创建只读,和可读可写两种
1) 只读 let fullName = computed(() => msg.value + ‘~’ + name)
2)可读可写 let fullName2 = computed({ get: () => number.value + state.step, set: (v) => number.value = v })
7、watch()
watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源数据变更时才执行回调。
1) 监听 reactive( )声明的数据
单数据监听: watch( ( )=> reactiveV.name, (n, o)=>{ })
多数据监听: watch([( )=> reactive.name, ( )=> reactive.sex], ([name, sex], [oldName, oldSex])=> { })
2) 监听 ref( )声明的数据
单数据监听: watch(age,(n, o)=>{ })
多数据监听: watch([age, weight], ([newAge, newWeight], [oldAge, oldWeight])=> { })
3)取消监听
const stop = watch(age,(newValue, oldValue)=>{ })
setTimeout(() => { stop() }, 1000) // 一秒后取消对age的监听
选项 API 生命周期选项和组合式 API 之间的映射
setup()内部调用生命周期钩子,只需在原有钩子函数前加on
-
beforeCreate
-> use
setup()
-
created
-> use
setup()
-
beforeMount
->
onBeforeMount
-
mounted
->
onMounted
-
beforeUpdate
->
onBeforeUpdate
-
updated
->
onUpdated
-
beforeUnmount
->
onBeforeUnmount
-
unmounted
->
onUnmounted
-
errorCaptured
->
onErrorCaptured
-
renderTracked
->
onRenderTracked
-
renderTriggered
->
onRenderTriggered
refs的使用变化
<div ref="div1">hello,world</div>
复制代码
要获取div的DOM元素在vue2.x中:
this.$refs.div1
复制代码
而在vue3.0中:
import {ref} from 'vue'
setup(){
let div1 = ref(null) //括号内为初始值,可以通过.value来读取
onMounted(()=> console.log(div1)) //打印出<div data-v-xxxxxxx="">hello,world</div>
return div1
}
复制代码
global Properties
vue 2.x 通过 Vue.prototype扩展
vue3.0 通过app.config.globalProperties.xxx进行配置
例如:app.config.globalProperties.$api = ‘
www.baidu.com
‘
然后,在setUp函数中通过
getCurrentInstance()
函数获取
import {getCurrentInstance} from ‘vue’
const { ctx } = getCurrentInstance()
console.log(ctx.$api) //打印出
www.baidu.com
provide / inject
vue2.x
const app = Vue.createApp({})
app.component('grandFather', {
data() {
return {
list: ['吃饭', '睡觉']
}
},
provide: {
user: '李华'
},
template: `
<div>
{{ list.length }}
<!-- rest of the template -->
</div>
`
})
app.component('son', {
inject: ['user'],
created() {
console.log(`我收到来自祖父的数据: ${this.user}`) //我收到来自祖父的数据: 李华
}
})
复制代码
但是如果我们直接在provide中用这种方式访问data中数据的话就会报错
app.component('grandFather', {
data() {
return {
list: ['吃饭', '睡觉']
}
},
provide: {
listLenth: this.list.length //error: Cannot read property 'length' of undefined
},
template: `
<div>
{{ list.length }}
<!-- rest of the template -->
</div>
`
})
复制代码
应将provide写成一个函数返回一个对象的形式:
app.component('grandFather', {
data() {
return {
list: ['吃饭', '睡觉']
}
},
provide() {
return {
listLenth: this.list.length
}
},
template: `
<div>
{{ list.length }}
<!-- rest of the template -->
</div>
`
})
复制代码
vue3.0
1、inject()只能放在setup()生命周期里运行,不能放在别的周期里运行,也不能放在事件周期里运行。 这意味着inject只能在setup的同步代码中运行,无法在setTimeout或者Promise.then等其他异步代码中运行。
2、需要修改祖先组件中数据时,使用祖先组件提供的修改数据的方法而不能直接在子组件里修改。
祖先组件:
setup() {
let grandFatherName = ref('李华')
function changeName(val){
grandFatherName.value = val
}
provide('grandFatherName',grandFatherName)
provide('changeName',changeName) //子组件需要修改值时调用该函数即可
}
子组件:
setup() {
const grandFatherName = inject('grandFatherName') //子组件接受值
const changeName = inject('changeName') //子组件接受修改函数
changeName('韩梅梅') //子组件调用修改函数修改值
}
复制代码
3、禁止子组件修改变量可以将祖先组件中变量定义为只读的。可以是
readonly
,也可以是
shallowRef
,这样即使子组件调用修改函数修改变量也不会有反应。
祖先组件:
setup() {
let grandFatherName = shallowRef('李华') //使用shallowRef将变量定义为只读
function changeName(val){
grandFatherName.value = val
}
provide('grandFatherName',grandFatherName)
provide('changeName',changeName)
}
子组件:
setup() {
const grandFatherName = inject('grandFatherName')
const changeName = inject('changeName')
changeName('韩梅梅') //子组件调用修改函数修改值(无效,变量为只读)
}
复制代码
Performance
-
重写了虚拟
Dom
的实现(且保证了兼容性,脱离模版的渲染需求旺盛)。 - 编译模板的优化。
- 更高效的组件初始化。
-
update
性能提高 1.3~2 倍。 -
SSR
速度提高了 2~3 倍。
编译模板时节点打PatchFlag
静态节点<span>msg</span>
动态节点<span>{{msg}}</span> //会生成number(大于 0)值的PatchFlag,用作标记
复制代码
vue3.0底层,会自动识别某个节点是否是动态的,如果是动态的会自动生成标识(不同的动态会有不同的标识对应,如内容文本的动态,或者id的动态),从而在每次更新dom时,直接跳过那些静态的节点,直接定位到动态的节点,大大节省效率
事件监听缓存
cacheHandlers
cacheHandlers
vue2.x默认情况下onClick 会被视为动态绑定,所以每次都会去追踪它的变化;
vue3.0 因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可;
hoistStast 静态提升
vue2.x无论元素是否参与更新,每次都要重新创建,然后渲染;
vue3.0中对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可;
ssr渲染
vue2.x 当有大量静态的内容时,这些内容会被当做纯字符串推进一个buffer里面,
即使存在动态的绑定,会通过模板 插值嵌入进去,这样会比通过虚拟dom来渲染的快很多
vue3.0 当静态文件大到一定量的时候,会用_ceratStaticVNode方法在客户端去生成一个static node, 这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染
Tree shaking support
一种通过清除多余代码方式来优化项目打包体积的技术,专业术语叫
Dead code elimination
其基于ES6的import和exports,借助ES6模块的静态编译思想,在编译时就能确定模块的依赖关系,以及输入和输出的变量.
实际上就是做了2件事:
-
编译阶段利用
ES6 Module
判断哪些模块已经加载 - 判断那些模块和变量未被使用或者引用,进而删除对应代码
例如:
vue2.x
import Vue from 'vue'
//在组件中使用data属性
export default {
data: () => ({
count: 1,
}),
};
复制代码
项目打包后体积如下:
import Vue from 'vue'
//在组件中使用其他属性
export default {
data: () => ({
question:"",
count: 1,
}),
computed: {
double: function () {
return this.count * 2;
},
},
watch: {
question: function (newQuestion, oldQuestion) {
this.answer = 'xxxx'
}
};
复制代码
再一次打包,发现打包出来的体积并没有变化
vue3.0
import { reactive, defineComponent } from "vue";
//简单使用
export default defineComponent({
setup() {
const state = reactive({
count: 1,
});
return {
state,
};
},
});
复制代码
将项目进行打包
import { reactive, defineComponent, computed, watch } from "vue";
//引入watch和computed属性
export default defineComponent({
setup() {
const state = reactive({
count: 1,
});
const double = computed(() => {
return state.count * 2;
});
watch(
() => state.count,
(count, preCount) => {
console.log(count);
console.log(preCount);
}
);
return {
state,
double,
};
},
});
复制代码
再次对项目进行打包,可以看到在引入
computer
和
watch
之后,项目整体体积变大了
Fragment
作用:创建模板时不用写一个根元素了
//原本这样写组件会报错
<template>
<div>Hello</div>
<div>World</div>
</template>
复制代码
原因是代表任何Vue组件的Vue实例需要绑定到一个单一的DOM元素中。唯一可以创建一个具有多个DOM节点的组件的方法就是创建一个没有底层Vue实例的功能组件。 结果发现React社区也遇到了同样的问题。他们想出的解决方案是一个名为 Fragment 的虚拟元素。它看起来差不多是这样的:
class Columns extends React.Component {
render() {
return (
<React.Fragment>
<td>Hello</td>
<td>World</td>
</React.Fragment>
);
}
}
复制代码
尽管Fragment看起来像一个普通的DOM元素,但它是虚拟的,根本不会在DOM树中呈现。这样我们可以将组件功能绑定到一个单一的元素中,而不需要创建一个多余的DOM节点。 目前你可以在Vue 2中使用vue-fragments库来使用Fragments,而在Vue 3中,你将会在开箱即用!
teleport
能够直接帮助我们将组件渲染后页面中的任意地方,只要我们指定了渲染的目标对象。
官方示例是模态框是使用场景:
app.component('modal-button', {
template: `
<button @click="modalOpen = true">
Open full screen modal! (With teleport!)
</button>
<teleport to="body"> //to属性的值为queryselect选择器指定的位置
<div v-if="modalOpen" class="modal">
<div>
I'm a teleported modal!
(My parent is "body")
<button @click="modalOpen = false">
Close
</button>
</div>
</div>
</teleport>
`,
data() {
return {
modalOpen: false
}
}
})
复制代码
与组件一起使用
如果
<teleport>
包含 Vue 组件,则它仍将是
<teleport>
父组件的逻辑子组件:
app.component('parent-component', {
template: `
<h2>This is a parent component</h2>
<teleport to="#endofbody">
<child-component name="John" />
</teleport>
`
})
app.component('child-component', {
props: ['name'],
template: `
<div>Hello, {{ name }}</div>
`
})
复制代码
在这种情况下,即使
child-component
被渲染到了id为endofbody的元素上,但它仍将是
parent-component
的子级,并将从中接收
name
prop
在同一元素上使用多个 teleport
顺序是一个先来后到原则:稍后挂载将位于目标元素中较早的挂载之后
<teleport to="#modals">
<div>A</div>
</teleport>
<teleport to="#modals">
<div>B</div>
</teleport>
<!-- 渲染结果 -->
<div id="modals">
<div>A</div>
<div>B</div>
</div>
复制代码
Suspense
类似React.lazy,是一个新组件,其作用是当你在进行一个异步加载时,可以先提供一些静态组件作为显示内容,然后当异步加载完毕时再显示原有内容
//这是一个内置组件不用引入直接使用
<Suspense>
<template #default>
<AsyncComponent/>
</template>
<template #fallback>
<h1>Loading</h1>
</template>
</Suspense>
AsyncComponent组件:
import {defineComponent} from 'vue'
export default defineComponent({
name:"AsyncComponent",
setup() {
return new Promise((resolve,reject)=> {
setTimeout(()=> {
resolve({result:"yuanxi"})
},2000)
})
}
})
复制代码
根据插槽机制,来区分组件:
#default插槽里面的内容就是你需要渲染的异步组件;
#fallback就是你指定的加载中的静态组件。
需要注意的是:
AsyncComponent组件需要返回一个Promise