Vue 3.0 于 2020-09-18 发布了,使用了 Typescript 进行了大规模的重构,带来了 Composition API RFC 版本,类似 React Hook 一样的写 Vue,可以自定义自己的 hook ,让使用者更加的灵活。
为什么推出3.0?
- 对 TypeScript 支持不友好(所有属性都放在了 this 对象上,难以推倒组件的数据类型)
- 大量的 API 挂载在 Vue 对象的原型上,难以实现 TreeShaking
- TreeShaking:当我引入一个模块的时候,我不引入这个模块的所有代码,我只引入我需要的代码
- 架构层面对跨平台 dom 渲染开发支持不友好
- CompositionAPI。受 ReactHook 启发
- 更方便的支持了 jsx
- Vue 3 的 Template 支持多个根标签,Vue 2 不支持
- 对虚拟 DOM 进行了重写、对模板的编译进行了优化操作
一、setup
为我们使用 vue3 的 Composition API 新特性提供了统一的入口, 有些文章说setup 函数会在 beforeCreate 之后、created 之前执行但实践证明这个说法不正确如下图打印顺序, vue3 也是取消了这两个钩子,统一用 setup 代替, 该函数相当于一个生命周期函数,vue 中过去的 data,methods,watch 等全部都用对应的新增 api 写在 setup()函数中。
1、setup参数
- props: 组件传入的属性
- context:attrs,emit,slots
props
setup中接受的props
是响应式的, 当传入新的props 时,会及时被更新。由于是响应式的, 所以不可以使用ES6解构,解构会消除它的响应式。需要结构可以用toRefs()
context
setup
中不能访问Vue2中最常用的this
对象,所以context
中就提供了this
中最常用的三个属性:attrs
、slot
和emit
,分别对应Vue2.x中的 $attr
属性、slot
插槽 和$emit
发射事件。
简单用法
2、reactive、ref与toRefs
在vue2.x中, 定义数据都是在data
中, 但是Vue3.x 可以使用reactive
和ref
来进行数据定义。
区别
reactive
用于处理对象的双向绑定,ref
则处理js基础类型的双向绑定,reactive
不能代理基本类型,例如字符串、数字、boolean等。
简单用法
<template> <div> <span>{{ year }}</span ><span>{{ user.name }}</span ><span>{{ user.label }}</span> </div> </template> <script> import { reactive, ref } from "vue"; export default { props: { data: { type: Object, default: { id: 1, name: "匹配", }, }, }, components: {}, setup(props, con) { const year = ref(10); const user = reactive({ name: "夏利", label: "", });
//这里ref取值需要加value if (year.value > 5) { user.label = "牛逼"; } else { user.label = "一般"; } return { year, user, }; }, }; </script>
不能直接对user
进行结构, 这样会消除它的响应式, 这里就和上面我们说props
不能使用ES6直接解构就呼应上了。那我们就想使用解构后的数据怎么办,解决办法就是使用toRefs
。
return { year, // 使用reRefs ...toRefs(user) }
3、生命周期
我们可以看到beforeCreate
和created
被setup
替换了(但是Vue3中你仍然可以使用, 因为Vue3是向下兼容的, 也就是你实际使用的是vue2的)。其次,钩子命名都增加了on
; Vue3.x还新增用于调试的钩子函数onRenderTriggered
和onRenderTricked
<script> import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked, onRenderTriggered, } from "vue"; export default defineComponent({ // beforeCreate和created是vue2的 beforeCreate() { console.log("------beforeCreate-----"); }, created() { console.log("------created-----"); }, setup() { console.log("------setup-----"); // vue3.x生命周期写在setup中 onBeforeMount(() => { console.log("------onBeforeMount-----"); }); onMounted(() => { console.log("------onMounted-----"); }); // 调试哪些数据发生了变化 onRenderTriggered((event) => { console.log("------onRenderTriggered-----", event); }); }, }); </script>
4、全局定义属性 globalProperties
在项目中往往会全局定义公共属性或方法,方便我们组件间调用。
import { createApp } from 'vue' import App from './App.vue' import utils from './utils' // vue2.0写法 let vue=new Vue() vue.prototype.utils=utils // vue3.0写法 const app=createApp(App) app.config.globalProperties.utils=utils
app.mount('#app')
vue3.0中使用全局定义的属性
<script>
import { getCurrentInstance } from "vue";
export default { components: {}, setup(props, con) {
const { ctx } = getCurrentInstance(); console.log(ctx.utils);
}, }; </script>
5、use与plugin
定义一个组件
通过use引入
6、readonly 只读属性
在使用readonly后重新进行复制是不允许修改的,这个api不常用
7、computed计算api
这里引入了proxy内的类似写法,有了set与get的内容写法,该函数用来创造计算属性,和过去一样,它返回的值是一个 ref 对象。里面可以传方法,或者一个对象,对象中包含 set()、get()方法。
7.1 创建只读的计算属性
<template> <div> <p>refCount: {{refCount}}</p> <p>计算属性的值computedCount : {{computedCount}}</p> <button @click="refCount++">refCount + 1</button> </div> </template> <script> import { computed, ref } from '@vue/composition-api' export default { setup() { const refCount = ref(1) // 只读 let computedCount = computed(() => refCount.value + 1) //2 console.log(computedCount) return { refCount, computedCount } } }; </script>
7.2 通过 set()、get()方法创建一个可读可写的计算属性
<template> <div> <p>refCount: {{refCount}}</p> <p>计算属性的值computedCount : {{computedCount}}</p> <button @click="refCount++">refCount + 1</button> </div> </template> <script> import { computed, ref } from '@vue/composition-api' export default { setup() { const refCount = ref(1) // 可读可写 let computedCount = computed({ // 取值函数 get: () => refCount.value + 1, // 赋值函数 set: val => { refCount.value = refCount.value -5 } })
//触发get函数 console.log(computedCount.value)
// 为计算属性赋值的操作,会触发 set 函数 computedCount.value = 10 console.log(computedCount.value) // 触发 set 函数后,count 的值会被更新 console.log(refCount.value)
return { refCount, computedCount } } }; </script>
8、watch与watchEffect
8.1watch
watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。默认情况是惰性的,也就是说仅在侦听的源数据变更时才执行回调。
<script> import { ref, watch } from "vue"; export default { components: {}, setup(props, con) { const count = ref(0); const name = ref("iuuggu"); setTimeout(() => { (count.value = 1), (name.value = "这一行山"); }, 200); watch( () => { /* * 1、需要一个明确的数据资源 * 2、需要有一个回调函数 * 3、等到改变才会执行函数 * */ return count.value; }, () => {} ); // 4、ref简写 ,reactive不可以 watch(count, () => {}); // 5、监听多个 watch( () => { return [count, name]; }, ([newv, oldv], [newv, oldv]) => {} ); // 6、onTrack,onTrigger watch( () => { return [count, name]; }, ([newv, oldv], [newv, oldv]) => {}, { onTrack(e) {}, onTrigger(e) {}, } ); }, }; </script>
8.1.1 监听数据源
reactive
setup(props, context) { const state = reactive<Person>({ name: 'vue', age: 10 }) watch( () => state.age, (age, preAge) => { console.log(age); // 100 console.log(preAge); // 10 } ) // 修改age 时会触发watch 的回调, 打印变更前后的值 state.age = 100 return { ...toRefs(state) } }
ref
setup(props, context) { const age = ref<number>(10); watch(age, () => console.log(age.value)); // 100 // 修改age 时会触发watch 的回调, 打印变更后的值 age.value = 100 return { age } }
8.1.2监听多个
setup(props, context) { const state = reactive<Person>({ name: 'vue', age: 10 }) watch( [
() => state.age,
() => state.name
], ([newName, newAge], [oldName, oldAge]) => { console.log(newName); console.log(newAge); console.log(oldName); console.log(oldAge); } ) // 修改age 时会触发watch 的回调, 打印变更前后的值, 此时需要注意, 更改其中一个值, 都会执行watch的回调 state.age = 100 state.name = 'vue3' return { ...toRefs(state) } }
8.1.3stop 停止监听
在 setup() 函数内创建的 watch 监视,会在当前组件被销毁的时候自动停止。如果想要明确地停止某个监视,可以调用 watch() 函数的返回值即可,语法如下:
setup(props, context) { const state = reactive<Person>({ name: 'vue', age: 10 }) const stop = watch( [() => state.age, () => state.name], ([newName, newAge], [oldName, oldAge]) => { console.log(newName); console.log(newAge); console.log(oldName); console.log(oldAge); } ) // 修改age 时会触发watch 的回调, 打印变更前后的值, 此时需要注意, 更改其中一个值, 都会执行watch的回调 state.age = 100 state.name = 'vue3' setTimeout(()=> { stop() // 此时修改时, 不会触发watch 回调 state.age = 1000 state.name = 'vue3-' }, 1000) // 1秒之后讲取消watch的监听 return { ...toRefs(state) } }
8.2 watchEffect
该函数有点像update函数,但他执行在update与beforeuodate之前。
* 1、首次加载会立即执行
* 2、响应的最终所有依赖监听变化(数据改变)
* 3、在卸载onUnmounte时自动停止
* 4、执行stop就会停止监听,否则一直监听
* 5、异步函数先执行再去监听改变
<script> import { watchEffect, ref, onMounted } from "vue"; export default { components: {}, //con==context(attrs,emit,slots) setup(props, con) { const count = ref(0); setTimeout(() => { count.value = 1; }, 2000); const stop = watchEffect( () => { /* * 1、首次加载会立即执行 * 2、响应的最终所有依赖监听变化(数据改变) * 3、在卸载onUnmounte时自动停止 * 4、执行stop就会停止监听,否则一直监听 * 5、异步函数先执行再去监听改变 */ }, { // 6、在update之后执行 flush: "post", // 同步执行 flush: "async", } ); setTimeout(() => { stop(); }, 4000); // 7、组件挂在ref const myRef = ref(null); // 避免监听时先见听到null 在监听到h1 onMounted(() => { watchEffect(() => { console.log(myRef.value); }); }); // 8、debugging 开发模式使用 watchEffect( () => { console.log(count.value); }, { onTrack(e) { // 监听到count和改变count }, onTrigger(e) { // count改变了会触发 }, } ); return { myRef, count, }; }, }; </script>
9、ref,torefs,isref,unref
9.1ref
ref() 函数用来根据给定的值创建一个响应式的数据对象,ref() 函数调用的返回值是一个对象,这个对象上只包含一个 .value 属性:
ref数据
<template> <div class="mine"> {{count}} // 10 </div> </template> <script > import {ref } from 'vue'; export default { setup() { const count = ref(10) // 在js 中获取ref 中定义的值, 需要通过value属性 console.log(count.value); return { count } } } </script>
ref 访问dom
通过 refs 来回去真实 dom 元素, 这个和 react 的用法一样,为了获得对模板内元素或组件实例的引用,我们可以像往常一样在 setup()中声明一个 ref 并返回它
还是跟往常一样,在 html 中写入 ref 的名称
在steup 中定义一个 ref
steup 中返回 ref的实例
onMounted 中可以得到 ref的RefImpl的对象, 通过.value 获取真实dom
<template> <!--第一步:还是跟往常一样,在 html 中写入 ref 的名称--> <div class="mine" ref="elmRefs"> <span>1111</span> </div> </template> <script > import { set } from 'lodash'; import { onMounted, ref } from 'vue'; export default{ setup(props, context) { // 获取真实dom const elmRefs = ref(null); onMounted (() => { console.log(elmRefs.value); // 得到一个 RefImpl 的对象, 通过 .value 访问到数据 }) return { elmRefs } } } </script>
在 reactive 对象中访问 ref 创建的响应式数据
当把 ref() 创建出来的响应式数据对象,挂载到 reactive() 上时,会自动把响应式数据对象展开为原始的值,不需通过 .value 就可以直接被访问,例如:
<template> <div class="mine"> {{count}} -{{t}} // 10 -100 </div> </template> <script > import reactive, ref, toRefs } from 'vue'; export default { setup() { const count = ref(10) const obj = reactive({ t: 100, count }) // 通过reactive 来获取ref 的值时,不需要使用.value属性 console.log(obj.count); return { ...toRefs(obj) } } } </script>
9.2 toRefs() 函数
toRefs() 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据
<template> <div> <p>{{ count }} - {{ name }}</p> <button @click="count += 1">+1</button> <button @click="add">+1</button> </div> </template> <script> import { reactive, toRefs } from "@vue/composition-api"; export default { setup() { // 响应式数据 const state = reactive({ count: 0, name: "zs" }); // 方法 const add = () => { state.count += 1; }; return { // 非响应式数据 // ...state, // 响应式数据 ...toRefs(state), add }; } }; </script>
9.3 isref
isRef() 用来判断某个值是否为 ref() 创建出来的对象
setup(props, context) { const name: string = 'vue' const age = ref(18) console.log(isRef(age)); // true console.log(isRef(name)); // false return { age, name } }
9.4 unref
unRef() 用来判断某个值是否为 ref() 创建出来的对象有抛出该对象
setup(props, context) { const name: string = 'vue' const age = ref(18) console.log(unRef(age)); // 18 console.log(unRef(name)); // vue return { age, name } }