vue3.0学习笔记
vue3转vue2:
https://vue-next-template-explorer.netlify.app/
1. Vue3.0六大两点
- Performance:性能比Vue2.x快1.2~2倍
- Tree shaking support:按需编译,体积比Vue2.x更小
- Composition API:组合API(类似React Hooks)
- Better TypeScript support:更好的 Ts 支持
- Custom Renderer API:暴露了自定义渲染API
- Fragment,Teleport(Protal),Suspense:更先进的组件
2. Vue3.0 是如何变快的?
diff方法优化:http://vue-next-template-explorer.netlify.app/
-
Vue2 中的虚拟dom是进行全量的对比
-
Vue3 新增了静态标记(PatchFlag)
在与上次虚拟节点进行对比时候,只对比带有 patch flag 的节点
并且可以通过 flag 的信息得知当前节点要对比的具体内容
在创建虚拟dom的时候,会根据DOM中的内容会不会发生变化,添加静态标记
hoistStatic 静态提升
- Vue2中无论元素是否参与更新,每次都会重新创建,然后再渲染
- Vue3中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可
cacheHandlers 事件侦听缓存
- 默认情况下 onClick 会被视为动态绑定,所以每次都会去追踪它的变化
- 但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可
ssr渲染
- 当有大量静态的内容时候,这些内容会被当做纯字符串推进一个Buffer里面,即使存在动态的绑定,会通过模板插值嵌入进去。这样会比通过虚拟dom来渲染快上很多。
- 当静态内容达到一定量级时候,会使用_createStaticVNode方法在客户端dom来渲染一个static node,这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。
什么是 Vite?
- Vite是Vue作者开发的一款意图取代Webpack的工具
- 其实现原理是利用 ES6 的 import 会发送请求去加载文件的特性,拦截这些请求,做一些预编译,省去webpack冗长的打包时间
3. Composition API
setup 函数是组合api的入口函数
setup() {
// 定义了一个count变量,初始值为0
// 该变量发生改变后,Vue会自动更新UI
let count = ref(0)
// 在组合api中,如果定义方法,不需要定义到methods,直接定义即可
function myFun() {
count.value += 1
}
// * 注意点:
// * 在组合api中定义的变量/方法,要想在外界使用,必须通过return { xxx, xxx } 暴露出去
return { count, myFun }
}
数据和业务逻辑一体化
-
{{ stu.name }}: {{ stu.age }}
模块化(业务逻辑可以抽离成单独的模块)
-
{{ stu.name }}: {{ stu.age }}
import { reactive } from 'vue'
function useAddStudent(state) {
let state2 = reactive({
stu: {
id: '',
name: '',
age: ''
}
})
function addStu(e) {
e.preventDefault()
const stu = Object.assign({}, state2.stu)
state.stus.push(stu)
state2.stu.id = ''
state2.stu.name = ''
state2.stu.age = ''
}
return { state2, addStu }
}
export default useAddStudent
import { reactive } from 'vue'
function useRemoveStudent() {
/**
* 数据和业务逻辑一体化,解决了 Vue2.x 中数据和业务逻辑分离的问题
*/
let state = reactive({
stus: [
{ id: 1, name: '张三', age: 10 },
{ id: 2, name: '李四', age: 20 },
{ id: 3, name: '王五', age: 30 }
]
})
function removeStu(index) {
state.stus = state.stus.filter((stu, idx) => idx !== index)
}
return { state, removeStu }
}
export default useRemoveStudent
Composition API 和 Option API 的混合使用
-
Composition API本质 (组合API/注入API)【本例:setup函数中,将数据(age)注入data,将方法(myFun2)注入 methods】
{{ name }}
{{ age }}
setup执行时机 & setup注意点
setup执行时机:beforeCreate -> setup -> created
-
beforeCreate: 表示组件刚刚创建出来,组件的 data 和 methods 还没有初始化好
-
created: 表示组件刚刚被创建出来,组件的 data 和 methods 已经初始化好
官方文档原文:
Because setup is run around the beforeCreate and created lifecycle hooks, you do not need to explicitly define them.
In other words, any code that would be written inside those hooks should be written directly in the setup function.
意思是:
因为 setup 是围绕 beforeCreate 和 created 的生命周期钩子运行的,所以不需要显式地去定义 beforeCreate 和 created
换句话说,在 beforeCreate 和 created 钩子中编写的任何代码都应该直接写在 setup 函数中
setup注意点
- 由于在执行setup函数的时候,还没有执行 created 生命周期方法,所以在 setup 函数中,是无法使用 data 和 methods
- 由于不能再 setup 函数中使用 data 和 methods,所以 Vue 为了避免我们的错误使用,它直接将 setup 函数中的 this 修改成了 undefined
- setup 函数只能是同步的,不能是异步的
{{ name }}
{{ age }}
reactive 函数
- 什么是 reactive?
- reactive 是 Vue3 中提供的实现响应式数据的方法
- 在 Vue2 中响应式数据是通过 defineProperty 来实现的,而在 Vue3 中响应式数据是通过 ES6 的 Proxy 来实现的
- reactive 注意点
- reactive 参数必须是对象 (json/array)
- 如果给 reactive 传递了其他对象
- 默认情况下修改对象,界面不会自动更新
- 如果想更新,可以通过重新赋值的方式
{{ state.time }}
ref 函数
- 什么是 ref?
-
ref 和 reactive 一样,也是用来实现响应式数据的方法
-
由于 reactive 必须传递一个对象,所以导致在企业开发中,如果我们只想让某个变量实现响应式的时候会非常麻烦,
所以 Vue3 就给我们提供了 ref 方法,实现对简单值的监听
- ref 本质
- ref 底层的本质其实还是 reactive,系统会自动根据我们给 ref 传入的值将它转换成 ref(xx) -> reactive({value: xx})
- ref 注意点
- 在 Vue 中使用 ref 的值不用通过 value 获取
- 在 JS 中使用 ref 的值必须通过 value 获取
{{ age }}
ref 和 reactive 的区别
- 如果在 template 里使用的是 ref 类型的数据,那么 Vue 会自动帮我们添加 .value
- 如果在 template 里使用的是 reactive 类型的数据,那么 Vue 不会自动帮我们添加 .value
- Vue 是如何决定是否需要添加 .value 的?
- Vue 在解析数据之前,会自动判断这个数据是否是 ref 类型的,如果是就自动添加 .value,如果不是就不自动添加 .value
- Vue 是如何判断当前数据是否是 ref 类型的?
- 通过当前数据的 v_isRef 来判断的
- 如果存在这个私有的属性,并且取值为 true,那么就代表是一个 ref 类型的数据
- 因为是私有属性,所以我们是访问不到 v_isRef 属性的值
- Vue 提供了 isRef 和 isReactive 方法来判断一个数据是 ref 类型还是 reactive 类型
{{ age }}
递归监听 & 非递归监听
- 递归监听
- 默认情况下,无论是通过 ref 还是 reactive 都是递归监听
- 每一层都包装成了一个 proxy 对象
- 递归监听存在的问题
- 如果数据量比较大,非常消耗性能
- 非递归监听
- shallowReactive
- 非递归监听下,第一层被包装成了 proxy
- 这意味着:只有第一层的数据发生改变,才会触发 UI界面 的更新
- shallowRef
- 如果是通过 shallowRef 创建数据,那么 Vue监听的是 .value 的变化,并不是第一层的变化
- 如果想在修改其内部数据后触发界面的更新,可以调用 triggerRef 方法
- shallowReactive
- 应用场景
- 一般情况下 使用 ref 和 reactive 即可
- 只有在需要监听的数据量比较大的时候,我们才使用 shallowRef / shallowReactive
reactive
{{ state.a }}
{{ state.gf.b }}
{{ state.gf.f.c }}
{{ state.gf.f.s.d }}
{{ state.a }}
{{ state.gf.b }}
{{ state.gf.f.c }}
{{ state.gf.f.s.d }}
ref
{{ state.a }}
{{ state.gf.b }}
{{ state.gf.f.c }}
{{ state.gf.f.s.d }}
{{ state.a }}
{{ state.gf.b }}
{{ state.gf.f.c }}
{{ state.gf.f.s.d }}
shallowRef & shallowReative
- shallowRef 本质上还是 shallowReative
- 所以如果是通过 shallowRef 创建的数据,它监听的是 .value 的变化
{{ state.a }}
{{ state.gf.b }}
{{ state.gf.f.c }}
{{ state.gf.f.s.d }}
toRaw & markRaw 函数
- ref/reactive 数据类型的特点:
- 每次修改都会被追踪,都会更新 UI 界面,但是这样是非常消耗性能的
- 所以如果我们有一些操作不需要追踪,不需要更新 UI 界面,那么这个时候,
我们就可以通过 toRaw 方法拿到它的原始数据,对数据进行修改,这样就不会被追踪,也就不会去更新 UI 界面,性能就会有所提升
{{ state }}
{{ state }}
{{ state }}
toRef & toRefs 函数
-
ref->复制:
- 修改响应式数据不会影响以前的数据
- 数据发生改变,界面就会自动更新
-
toRef->引用:
-
修改响应式数据会影响以前的数据
-
数据发生改变,界面也不会自动更新
-
-
toRef 应用场景:
- 如果想让响应式数据和以前的数据关联起来,并且更新响应式数据之后还不想更新 UI,那么就可以使用 toRef
{{ state }}
{{state}}
customRef 函数
- 返回一个 ref 对象,可以显式的控制依赖追踪和触发响应
- 异步处理服务器数据的时候可能会用到
- 需要注意的是:不能在 get 方法中发送网络请求,因为这会导致不断发送请求,不断更新界面的无限循环
{{age}}
/**
* data.json
*/
[
{"id": 1, "name": "鲁班"},
{"id": 2, "name": "虞姬"},
{"id": 3, "name": "黄忠"}
]
- {{ item.name }}
生命周期
我是div
readonly 函数
- readonly:用于创建一个只读的数据,并且是递归只读
- shallowReadonly: 用于创建一个只读数据,但是不是递归只读的
- isReadonly:对于 readonly 和 shallowReadonly 创建的数据,返回结果均为 true
- const 和 readonly 的区别:
- const: 赋值保护,不能给变量重新赋值
- readonly:属性保护,不能给属性重新赋值
{{ state.name }}
{{ state.attr.age }}
{{ state.attr.height }}
Proxy 实现数据绑定
对象
/*
1. Vue3 响应式数据的本质
+ 在 Vue2.x 中是通过 defineProperty 来实现响应式数据的
详见:手写 vue 全家桶视频
+ 在 Vue3.x 中通过 Proxy 来实现响应式数据的
*/
let obj = { name: 'wh', age: 18 }
let state = new Proxy(obj, {
get(obj, key) {
console.log(obj, key) // { name: 'wh', age: 18 } name
return obj[key]
},
set(obj, key, value) {
console.log(obj, key, value) // { name: 'wh', age: 18 } name zs
obj[key] = value
console.log('更新UI界面')
}
})
// console.log(state.name) // wh
state.name = "zs"
console.log(state) // { name: 'zs', age: 18 }
数组
/*
1. Vue3 响应式数据的本质
+ 在 Vue2.x 中是通过 defineProperty 来实现响应式数据的
详见:手写 vue 全家桶视频
+ 在 Vue3.x 中通过 Proxy 来实现响应式数据的
*/
let arr = [ 1, 3, 5 ]
let state = new Proxy(arr, {
get(obj, key) {
console.log(obj, key) // [ 1, 3, 5 ] 1
return obj[key]
},
set(obj, key, value) {
// 第1次 [ 1, 3, 5 ] 3 7
// 第2次 [ 1, 3, 5, 7 ] length 4
console.log(obj, key, value)
obj[key] = value
console.log('更新UI界面')
return true // 必须要返回 true 才会继续更新 length
}
})
// console.log(state[1]) // 3
state.push(7) // 先追加 7 ,再更新 length
手写 shallowReactive, shallowRef
/*
1. shallowReactive, shallowRef
2. shallowReadonly
3. reactive, ref
4. readonly
*/
function shallowRef(val) {
return shallowReactive({ value: val })
}
function shallowReactive(obj) {
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, value) {
obj[key] = value
console.log('更新UI界面')
return true
}
})
}
let obj = {
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd'
}
}
}
}
/*
let state = shallowReactive(obj)
// state.a = 1 // 更新UI界面
state.gf.b = 2
state.gf.f.c = 3
state.gf.f.s.d = 4
*/
let state = shallowRef(obj)
// state.value.a = 1
// state.value.gf.b = 2
// state.value.gf.f.c = 3
// state.value.gf.f.s.d = 4
state.value = {
a: '1',
gf: {
b: '2',
f: {
c: '3',
s: {
d: '4'
}
}
}
}
手写 reactive, ref
/*
1. shallowReactive, shallowRef
2. shallowReadonly
3. reactive, ref
4. readonly
*/
// let arr = [{id: 1, name: '鲁班'}, {id: 2, name: '虞姬'}]
// let obj = {a:{id: 1, name: '鲁班'}, b:{id: 2, name: '虞姬'}}
function ref(val) {
return reactive({value: val})
}
function reactive(obj) {
if(typeof obj === 'object') {
if (obj instanceof Array) {
// 如果是一个数据,那么取出数组中的每一个元素
// 判断每一个元素是否又是一个对象,如果又是一个对象,那么也需要包装成 Proxy
obj.forEach((item, index) => {
if(typeof item === 'object') {
obj[index] = reactive(item)
}
})
} else {
// 如果是一个对象,那么去除对象属性的取值
// 判断对象属性的取值是否又是一个对象,如果又是一个对象,那么也需要包装成 Proxy
for (let key in obj) {
let item = obj[key]
if (typeof item === 'object') {
obj[key] = reactive(item)
}
}
}
} else {
console.warn(`${JSON.stringify(obj)} is not object`)
}
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, value) {
obj[key] = value
console.log('更新UI界面')
return true
}
})
}
let obj = {
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd'
}
}
}
}
// let state = reactive(obj)
// state.a = 1 // 更新UI界面
// state.gf.b = 2 // 更新UI界面
// state.gf.f.c = 3 // 更新UI界面
// state.gf.f.s.d = 4 // 更新UI界面
let arr = [{id: 1, name: '鲁班'}, {id: 2, name: '虞姬'}]
let state = reactive(arr)
state[0].name = '张三' // 更新UI界面
state[0].age = 666 // 更新UI界面
state[1].id = 3 // 更新UI界面
手写 readonly, shallowReadonly
/*
1. shallowReactive, shallowRef
2. shallowReadonly
3. reactive, ref
4. readonly
*/
function readonly(obj) {
if(typeof obj === 'object') {
if (obj instanceof Array) {
// 如果是一个数据,那么取出数组中的每一个元素
// 判断每一个元素是否又是一个对象,如果又是一个对象,那么也需要包装成 Proxy
obj.forEach((item, index) => {
if(typeof item === 'object') {
obj[index] = rereadonlyactive(item)
}
})
} else {
// 如果是一个对象,那么去除对象属性的取值
// 判断对象属性的取值是否又是一个对象,如果又是一个对象,那么也需要包装成 Proxy
for (let key in obj) {
let item = obj[key]
if (typeof item === 'object') {
obj[key] = readonly(item)
}
}
}
} else {
console.warn(`${JSON.stringify(obj)} is not object`)
}
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, value) {
console.warn(`${key}是只读的,不能赋值`)
return true
}
})
}
function shallowReadonly(obj) {
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, value) {
console.warn(`${key}是只读的,不能赋值`)
}
})
}
let obj = {
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd'
}
}
}
}
// let state = shallowReadonly(obj)
// state.a = 1 // a是只读的,不能赋值
// state.gf.b = 2 // 空行
let state1 = readonly(obj)
state1.a = 1 // a是只读的,不能赋值
state1.gf.b = 2 // b是只读的,不能赋值