Vue2 的缺点
Vue 作为一款 MVVM 框架,因为其响应式数据的特性,避免了开发者频繁操作 DOM 元素,大幅提高了页面的性能,然鹅,Vue2 在做数据响应式时,只是使用了 Object.defineProperty() API,对数据属性或者某些方法进行拦截与 getter,setter 重写,这种代理仍然是有缺陷的,比如数组中某一项内容的删除、对象中某一个属性的直接定义,如 a.b.c = 'x',存在修改了内容但并不能使页面数据响应的情况,需要辅助 API 配合才能监听到 由于 Vue2 中是将当前页所有数据、方法都写进 data、methods 中,在复杂业务的页面下,庞大的变量或方法会使得代码阅读难度大大提升,修改某处内容容易造成开发者在代码里上下反复横跳的情况。
Vue3 的出现
使用 Proxy 代理,对对象的属性值进行读写、添加、删除并进行劫持,通过 Reflect(反射)动态对被代理的对象属性进行特定的操作,由于 Proxy 和 Reflect 不支持 IE 浏览器,这也是 Vue3 不支持 IE 浏览器的主要原因之一 推出了 SetUp,setup 是 Composition API 的入口,可以实现数据与方法的代码捆绑在一块,大大提高了复杂业务逻辑中代码的可阅读性,它在 beforeCreate 之前执行一次,beforeCreate 这个钩子 的任务就是初始化,在它之前执行,那么 this 就没有被初始化 this = undefined 这样就不能通过 this 来调用方法 和 获取属性(注:Vue3 的生命周钩子都比 Vue2 同级的生命钩子执行的早) Vue3 的好处远不止以上两点,许多新特性还需要一步一步学
创建 Vue3 项目
推荐使用 Vite,Vite 是一个 web 开发构建工具,由于其原生 ES 模块导入方式,可以实现闪电般的冷服务器启动
通过在终端中运行以下命令,可以使用 Vite 快速构建 Vue 项目
# npm 6.x
$ npm init vite@latest <project-name> --template vue
# npm 7+,需要加上额外的双短横线
$ npm init vite@latest <project-name> -- --template vue
$ cd <project-name>
$ npm install
$ npm run dev
Composition API
前面提到,Vue2 使用的是 OptionAPI,即 data、methods、watch、computed..,给后期维护造成了一定的压力,Vue3 提供了 Compotion API,将一个逻辑里的代码都收集在一起,统一写入一个 hook,然后再引入,这样管理起来开发者就不用再上下横跳了
setup 是 Composition API 的入口
<template>
<p>姓名{{ name }}</p>
<p @click="say>年龄{{ age }}</p>
</template>
<script>
export default {
name: 'App',
setup(){
let name = '流星'
let age = 18
//方法
function say(){
console.log(`我叫${name},今年${age}岁`)
}
//返回一个对象
return {
name,
age,
say
}
}
}
</script>
Vue3 中,template 可以不用再用一个根元素来包裹内容了~~ 因为 Vue3 中会将多个标签包含在一个
Fragment
虚拟元素中 , 还有一点,如果是在<script setup>
语法糖中引入的组件,可以不用在 components 中注册即可直接在 template 中使用
我们需要 setup 返回数据那么它肯定就不能使用 async 修饰,这样返回 promise 是我们不想看见情况,如果我们硬要用 async 修饰,我们就得用的在它的父组件外层需要嵌套一个 suspense(不确定)内置组件,里面放置一些不确定的操作,比如我们就可以把异步组件放入进去
<!-- 子组件 -->
<template>
{{ res }}
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Son',
async setup() {
const res = await axios.get('地址')
return {
res,
}
},
})
</script>
<!-- 父组件 -->
<template>
<Suspense>
<!-- 子组件-->
<Son></Son>
</Suspense>
</template>
参数 : setup (props, context)
-
第一个参数是 props,它接收父组件传递的值,是的就是父子组件信息传递的 props
-
第二个参数是 context ,里面包含 3 个属性 { attrs, slots, emit },分别对应 this.$attrs,this.$slots,this.$emit,因为 setup 中不存在 this ,Vue3 直接将 setup 中的 this 设为 undefined 了
-
props 的内容为响应式的,不能直接使用 ES6 解构,因为它会消除 props 的响应性,但是可以通过 toRefs 来完成此操作
import { toRefs } from 'vue'
setup(props) {
const { name, age } = toRefs(props)
console.log(name.value)
console.log(age.value)
}
如上图代码所示,setup 中的响应式数据在使用时,需要通过 .value 来获取/修改,可以通过在控制台打印来观察响应式数据与普通数据的区别 ,另外,如果需要在 template 中使用 setup 中的变量的话,需要在 setup 中将使用到的变量 return 出去,亦或者,使用 Vue3 的新语法糖
<script setup>
,此语法糖 相当于在编译运行是把代码放到了 setup 函数中运行,然后把导出的变量定义到上下文中,并包含在返回的对象中 ,就不用再专门去做 return 操作了
toRefs 和 toRef
toRefs 是将某个普通数据转换为响应式数据,toRef 和它差不多,差别是它只能对对象的
某一个
属性变为响应式数据
export default {
setup(props) {
useSomeFeature(toRef(props, 'foo'))
}
}
function useSomeFeature(foo: Ref) {
// ...
}
拷贝了一份新的数据值单独操作,更新时相互不影响
如何建立响应式数据呢
ref
我们可以通过 ref()来建立响应式数据,并且在修改响应式数据时,应该通过 .value 来进行操作
<template>
<div class="home">
<h1>姓名:{{ name }}</h1>
<h1>年龄:{{ age }}</h1>
<button @click="say">修改</button>
<button @click="say2">修改</button>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
name: 'Home',
setup() {
let name = ref('燕儿');
let age = ref(18);
console.log(name);
console.log(age);
//方法
function say() {
name = '苒苒';
age = 20;
}
function say2() {
name.value = '苒苒';
age.value = 20;
}
return {
name,
age,
say,
say2,
};
},
};
</script>
执行上面代码,你会发现点击第一个修改,页面视图并没有发生变化,此时再点击第二个按钮控制台还会报错,刷新页面后重新点击第二个按钮,此时页面视图发生变化,注意,虽然在操作响应式数据时,需要通过 .value 来操作其值,但是在 template 中使用变量时并不用加上 .value,此时 Vue3 已经帮我们做处理了
那么用 ref 创建对象呢,如何修改响应式对象的属性值呢
<template>
<div class="home">
<h1>姓名:{{ name }}</h1>
<h1>年龄:{{ job.age }}</h1>
<h2>职业:{{ job.occupation }}</h2>
<button @click="say">修改</button>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
name: 'Home',
setup() {
let name = ref('燕儿');
let job = ref({
occupation: '程序员',
age: 18,
});
console.log(name);
console.log(job);
//方法
function say() {
job.value.age = 28;
}
return {
name,
job,
say,
};
},
};
</script>
但是我们打印 job.value 会发现,它不再是 RefImpl 实例对象,变成了 Proxy 实例对象,他只是 vue3 底层,把对象都变成了 Proxy 实例对象,对于基本数据类型就是按照 Object.defineProperty 里面的 get 和 set 进行数据劫持然后进行响应式,但是如果是对象类型的话,是用到的 Proxy ,但是 vue3 把它封装在新函数 reactive 里,就相当于,ref 中是对象,自动会调用 reactive
reactive
reactive 的用法与 ref 差不多,需要注意的是 reactive 只能定义对象类型的响应式数据
<template>
<div class="home">
<h1>姓名:{{name}}</h1>
<h2>职业:{{job.occupation}}
年龄:{{job.age}}</h2>
<h3>爱好:{{hobby[0]}},{{hobby[1]}},{{ hobby[2] }}</h3>
<button @click="say">修改</button>
</div>
</template>
<script>
import {ref,reactive} from 'vue'
export default {
name: 'Home',
setup(){
let name = ref('燕儿')
let job = reactive({
occupation:'程序员',
age:18
})
let hobby=reactive(['刷剧','吃鸡','睡觉'])
console.log(name)
console.log(job)
//方法
function say(){
job.age=28
hobby[0]='学习'
}
return {
name,
job,
say,
hobby
}
}
}
</script>
可以发现,此时修改对象的属性值,不用再用到 .value 了,因为 reactive 把 Object 转为了 Proxy,介是个深层次的响应式 ,通过打印出的数据也可以看出一些区别,注意,数组也是可以用 reactive 来创建响应式的,这样就不用每次都用 ref 来创建响应式数据了,节约 N 个写 .value 代码的时间
ref 和 reactive 的区别
- ref 可以用来定义一些简单类型的数据:字符串、数字
- ref 通过 Object.defineProperty()来实现响应式,即 Vue2 的响应式实现方式
- 对于 ref 创建的数据,操作需要通过 .value,尽管创建对象、数组时,会将其转为 reactive 来创建响应式数据,但是在操作时,也需要通过 xx.value.xx = xx 来操作
- reactive 用来定义对象、数组类型的数据
- reactive 通过 Proxy 代理来实现响应式,通过 Reflect 来操作源代码内部的数据,操作、读取数据不需要用到 .value
前面说过,Vue2 通过 Object.defineProperty()来实现数据操作拦截,但并不能对属性的删除新增来做出反应,Vue3 通过 Proxy 代理来实现了此功能(也使用了 Windows 上的内置对象 Reflect)
- 通过 Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等
- 通过 Reflect(反射): 对源对象的属性进行操作
const p=new Proxy(data, {
// 读取属性时调用
get (target, propName) {
return Reflect.get(target, propName)
},
//修改属性或添加属性时调用
set (target, propName, value) {
return Reflect.set(target, propName, value)
},
//删除属性时调用
deleteProperty (target, propName) {
return Reflect.deleteProperty(target, propName)
}
})
判断某个数据是否是响应式
- isRef
- isReactive
- isReadonly
- isProxy
isRef 检查一个值是否为一个 ref 对象
isReactive 检查一个对象是否是由
reactive
创建的响应式代理isReadonly 检查一个对象是否是由
readonly
创建的只读代理isProxy 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
自己配置一个响应式呢
Vue3 给提供了一个 customRef 来让我们实现自定义 Ref,实现原理是对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收
track
和trigger
函数作为参数,并应返回一个带有get
和set
的对象
<template>
<h2>App</h2>
<input v-model="keyword"/>
<p>{{keyword}}</p>
</template>
<script lang="ts">
import { customRef } from 'vue'
// 不确定类型所以这里使用泛型
function useDebouncedRef<T>(value: T, delay = 200) {
// 定时器
let timeout: number
return customRef((track, trigger) => {
return {
get() {
// 告诉Vue追踪数据
track()
return value
},
set(newValue: T) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
// 告诉Vue去触发界面更新
trigger()
}, delay)
}
}
})
}
export default {
setup () {
const keyword = useDebouncedRef('')
return {
keyword
}
},
}
</script>
shallowReactive 与 shallowRef
- 创建浅层次的响应式,比如对象中第一层的数据变为响应式,深层次的不做响应式操作
shallowRef
如果定义的是基本类型的数据,那么它和ref
是一样的不会有什么改变,但是要是定义的是对象类型的数据,那么它就不会进行响应式,之前提到如果ref
定义的是对象,那么它会自动调用reactive
变为Proxy
,但是要是用到的是shallowRef
那么就不会调用reactive
去进行响应式(这个 api 很怪,感觉基本用不上)
readonly 与 shallowReadonly
- 类似 const,但是更强大的地方是数据深层也是只读,比如对象的属性或数组内容不能修改
- shallowReadonly 只对第一层进行只读
取消数据的响应式 toRaw 与 markRaw
toRaw
其实就是将一个由reactive
生成的响应式对象转为普通对象。如果是ref
定义的话,是没有效果的(包括ref
定义的对象)如果在后续操作中对数据进行了添加的话,添加的数据为响应式数据,当然要是将数据进行markRaw
操作后就不会变为响应式,和readonly
不一样的地方是,readonly
是根本没办法改,但markRaw
是不转化为响应式,但是数据还会发生改变
祖孙组件传值 provide 与 inject
在 Vue2 中,如果想要实现祖孙组件的传值,在不考虑存入状态管理中的情况下,只能通过父子组件一层一层传值,但是在 Vue3 中,提供了一种可实现组祖孙组件传值的方法,不论组件层次结果有多深,父组件都是可以作为其所有子组件的以来提供者,只要让父组件提供 provide 选项提供数据,子组件提供 inject 选项来使用这些数据
//父
import { provide } from 'vue'
setup(){
let userInfo = reactive({id:'007',name:'靓仔'})
provide('userInfo',userInfo) //给自己的后代组件传递数据
return {...toRefs(userInfo)}
}
//后代
import {inject} from 'vue'
setup(){
let userInfo = inject('userInfo')
return {userInfo}
}
关于 router
Vue3 的路由跳转是定义了一个
vue-router
,然后引入其useRoute
、useRouter
来实现 Vue2 中的 this.$route和this.$router
import {useRouter,useRoute} from "vue-router";
setup(){
const router= useRouter()
const route= useRoute()
function fn(){
this.$router.push('/about')
}
onMounted(()=>{
console.log(route.query.code)
})
return{
fn
}
}
另外,Vue3 移除了 filter
嗯,可以通过自定义字典方法实现此功能,无伤大雅
watch 和 computed
Vue3 中的 watch 和 Vue2 中差别不大,引入即可
import { ref,watch } from 'vue'
setup() {
let name = ref('小浪')
let age = ref(21)
let watchName = ref('')
watch(name, (newName, oldName) => {
watchName.value = `我是新姓名${newName},我是老姓名${oldName}`
})
return {
name,
age,
watchName,
}
}
Vue3 中的 computed 比之前稍有不同
import { ref,computed } from 'vue'
setup() {
let name = ref('小浪')
let age = ref(21)
//计算属性
let getInfo = computed(() => {
return `我的名字:${name.value},今年${age.value},请多多指教`
})
return {
name,
age,
getInfo,
}
}
watchEffect
个人感觉更类似于 Vue2 中的 computed
let user = reactive({
name: '小浪',
age: 21,
})
// 只有 user.name 发生改变这个就会执行
watchEffect(() => {
console.log(user.name)
});
Teleport
看了文档,感觉像是吃鸡里面的定点空投,可以把组件进行传送到指定的位置去
<teleport v-if="flag" to=".test">
<div class="dog">狗子</div>
</teleport>
to
是目标的地址body
,#XXX
,.XXX
这些都是css
选择器
小结:第一篇关于 Vue3 的学习笔记,暂时记录到此,本篇主要是记录并实验了 Vue3 中出现的新的语法和特性,并大概的了解了其功能实现原理的皮毛,对于程序眼来说,现阶段使用到的任何的语言、框架,对其理解如果只停留于 api 的使用,不管是对于工作还是自身技术发展,都是远远不够的,还是得多学习其功能实现源码才行..
参考链接vue3保姆级教程 - 掘金