一说到vue3,大家可能第一想到的就是他的响应式原理以及compositionApi了,今天小编来浅谈一下这两部分以及vue3的一些更改。
响应式
vue3中实现响应的方法是使用es6引入Proxy,俗称代理。
那么问题来了,为什么要更换响应式的实现方式呢?
首先小编先说说vue2响应式的痛点
- Vue2 将遍历此data所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。当data对象很复杂,层级很多时无疑是会影响到性能的。
- 对于对象:Vue2 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在
data
对象上存在才能让 Vue 将它转换为响应式的 - 对于数组:Vue 2不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
- 当你利用索引直接设置一个数组项时,例如:
再来说说使用proxy的好处
- 首先proxy他是代理的整个对象
- Proxy 可以直接监听数组的变化;
- Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是Object.defineProperty 不具备的;
CompositionApi(组合式api)
为什么会产生组合式api呢?
当你在参与vue项目的编写的时候,有没有遇到一个组件里拥有大量的逻辑,比如说既有展示列表,还有搜索过滤,点赞,收藏等逻辑,然后你会发现这个组件异常的庞大,并且当你想去找某个逻辑的代码时,就会变得很麻烦,这就使得组件变得难以理解及维护。
什么是组合式api呢?
组合式api就是将所有同一逻辑的代码集合在一块
怎么写组合式api呢?
setup函数便是组合式api的入口
interface Data {
[key: string]: unknown
};
interface SetupContext {
attrs: Data // 属性
slots: Slots // 插槽
emit: (event: string, ...args: unknown[]) => void // 事件
expose: (exposed?: Record<string, any>) => void // 暴露出去的属性或方法 即可以被ref调用
}
/**
* @params props: 组件接收的参数
* @params content: 上下文
* return 将返回值暴露给模板
*/
setup(props:Data,context:SetupContext):Data
注意:
- setup 中没有this。
- props 是响应式的,当传入新的 props 时,它将被更新。但是,因为 props 是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性,但context只是个普通js对象。
- 如果data,props,setup都有一个同名属性,setup返回的该属性优先级最高
- 如果非要解构props,可以使用toRefs(只会将对象的第一层属性转化为ref对象)|toRef
import { toRefs,toRef } from 'vue' setup(props) { const { obj } = toRefs(props); console.log(obj.value); // 当obj可能不存在props上时,这时toRefs 将不会为 obj创建一个 ref , // 你应该使用toRef代替他 const { obj } = toRef(props) return{ obj } }
如何在setup中创建响应式对象?
使用ref 或 reactive
import {ref,reactive} from 'vue'
export default {
setup() {
const countRef = ref({count:0});
const countReactive = reactive({count:0});
console.log(countRef); // {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: 0, _value: {count:0}}
console.log(countReactive); //{count:0}
return {
countRef,
countReactive,
}
}
}
区别 :
- reactive 接受入参必须是对象形式,而 ref 可以是对象形式,也可以是一个单值。
- reactive 创建的响应式对象会自动解包(即不要调用.value)
如何在setup中使用生命周期钩子?
你可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。
下表包含如何在 setup () 内部调用生命周期钩子:
选项式 API | Hook inside setup |
---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
activated | onActivated |
deactivated | onDeactivated |
组合式api生命周期钩子接收一个回调函数,当钩子触发时,调用回调函数
import { onMounted } from 'vue';
export default {
setup() {
// mounted
onMounted(() => {
console.log('Component is mounted!')
})
}
}
在setup 中使用 watch
<script setup>
import { ref, watch,reactive } from 'vue'
/*
watch 接收三个参数
第一个参数:一个想要侦听的响应式引用或 getter 函数
第二个参数:一个回调
第三个参数:可选的配置选项
*/
const counter = ref(0)
// 直接侦听ref
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
const state = reactive({ count: 0 })
// 侦听get函数
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// 侦听多个数据源
const firstName = ref('')
const lastName = ref('')
watch([firstName, lastName], (newValues, prevValues) => {
console.log(newValues, prevValues)
})
/**
* 尽管如此,如果你在同一个函数里同时改变这些被侦听的来源,侦听器仍只会执行一次:
* 通过更改设置 flush: 'sync',我们可以为每个更改都强制触发侦听器
*/
</script>
在setup中使用计算属性
<script setup>
import { ref, computed } from 'vue'
const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)
counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2
</script>
相较于vue2其他的改动
改动有很多,小编只讲一些,想了解更多的小伙伴可以去官网查看
-
destroyed
,beforeDestroy
生命周期选项被重命名为 unmounted,beforeUnmount - Vue 3 现在正式支持了多根节点的组件,也就是片段!但是,这要求开发者显式定义 attribute 应该分布在哪里
一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应 props 或 emits 定义的 attribute。常见的示例包括<template> <header>...</header> <main v-bind="$attrs">...</main> <footer>...</footer> </template>
class
、style
和id
attribute。可以通过$attrs
property 访问那些 attribute。/** * 当组件返回单个根节点时,根节点默认继承非props属性 */ app.component('date-picker', { // inheritAttrs: false, // 当inheritAttrs为false时,根节点将不再继承非props属性 template: ` <div class="date-picker"> <input type="datetime-local" /> </div> ` }) <!-- 具有非 prop 的 attribute 的 date-picker 组件--> <date-picker data-status="activated"></date-picker> <!-- 渲染后的 date-picker 组件 --> <div class="date-picker" data-status="activated"> <input type="datetime-local" /> </div> /** * 当组件返回多个根节点时,根节点不再自动继承非props属性,如果未显式绑定 $attrs,将发出运行时 * 警告。 */ // 这将发出警告 app.component('custom-layout', { template: ` <header>...</header> <main>...</main> <footer>...</footer> ` }) // 没有警告,$attrs 被传递到 <main> 元素 app.component('custom-layout', { template: ` <header>...</header> <main v-bind="$attrs">...</main> <footer>...</footer> ` })
- 新增teleport,用将内容渲染到指定元素里面 to 指定渲染到哪个元素里
<div class="box"> <teleport to="body"> <div>A</div> </teleport> </div> <div id="container"></div> <teleport to="#container"> <div>B</div> </teleport> <teleport to="#container"> <div>C</div> </teleport>
当同时指定一个元素时,会按先后顺序依次添加在目标元素里面
- 组件可以使用多个v-model
<user-name v-model:first-name="firstName" v-model:last-name="lastName" ></user-name> app.component('user-name', { props: { firstName: String, lastName: String }, emits: ['update:firstName', 'update:lastName'], template: ` <input type="text" :value="firstName" @input="$emit('update:firstName', $event.target.value)"> <input type="text" :value="lastName" @input="$emit('update:lastName', $event.target.value)"> ` })
- 单文件组件状态驱动的 CSS 变量 (<style> 中的 v-bid)
单文件组件状态驱动的 CSS 变量 (<style> 中的 v-bid)// 单文件组件的 <style> 标签可以通过 v-bind 这一 CSS 函数将 CSS 的值关联到动态的组件状态上: <template> <div class="text">hello</div> </template> <script> export default { data() { return { color: 'red' } } } </script> <style> .text { color: v-bind(color); } </style> // 这个语法同样也适用于 <script setup>,且支持 JavaScript 表达式 (需要用引号包裹起来) <script setup> const theme = { color: 'red' } </script> <template> <p>hello</p> </template> <style scoped> p { color: v-bind('theme.color'); } </style>