setup
setup 是 Vue3.x 新增的一个选项, 他是组件内使用 Composition API的入口。
setup是一个函数,组件中用到的数据、方法都要配置在setUp中,它会在beforeCreate之前执行一次
- 若返回一个对象,那么对象中的属性、方法在模版中都可以直接使用
- 返回一个渲染函数、可以自定义渲染内容
import { ref, reactive } from 'vue'
export default {
name: 'Home',
setup(props, context) {
const title = ref('标题')
const data = reactive({
value: '哈哈哈'
})
return {
title,
data
}
}
}
- props: 值为对象,包含组件外部传递过来,且组件内部声明接收了的属性
- context: 上下文对象
- attrs: 值为对象,包含组件外部传递过来,且没有在props配置中声明的属性,等同于this.$attrs
- slots: 收到的插槽内容,相当于this.$slots
- emit: 分发自定义事件的函数,相当于this.$emit
- v2配置(data,message,computed。。)中可以访问到setup中的属性和方法,但反之不行,如果不同名,setup优先
reactive、ref、和toRefs
在 vue2.x 中, 定义数据都是在data中, 但是 Vue3.x 可以使用reactive和ref来进行数据定义。 那么ref和reactive他们有什么区别呢?
- 使用ref可以创建一个包含响应式数据的引用对象(简称ref对象),可以是基本类型、也可以是对象
const xxx = ref(value)
//使用
xxx.value
// 在模版中
<div>{{xxx}}</div>
- 使用reactive定义一个对象类型的响应式数据,但是不能代理基本数据类型
const xxx = reactive({
yy: ''
})
xxx.yy
- toRef创建ref
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
// 传递props
export default {
setup(props) {
useSomeFeature(toRef(props, 'foo'))
}
}
- toRefs响应式转换
- 将响应式对象转换成普通对象
- shallowReactive响应式外层转换, 适用于一个对象,结构比较深,但变化时只是外层属性变化
const state = shallowReactive({
foo: 1,
bar: {
foo1: 2
}
})
- shallowRef基本数据响应式, 不进行对象的响应式处理。适用于一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象
- readonly 让一个数据变成只读的,应用不希望数据被修改时
const readOnlyData = readonly({
xxx: 'hello' // 只读
aa: {
bb: 2 // 只读
}
})
- shallowReadOnly响应式变只读(浅只读)
const shallowData = shallowReadOnly({
foo: 1, //只读
nested: {
bar: 2 // 非只读
}
})
- toRaw 将响应式转换成非响应式
- customRef 依赖更新控制
import { customRef } from 'vue'
export default {
name: 'Home',
setup() {
const fn = function() {
return customRef((track, trigger)) {
return {
get() {
track()
return value
},
set(newValue) {
trigger()
}
}
}
}
}
}
- 单Ref
import { ref } from 'vue'
setup() {
const node = ref(null)
onMounted(() => {
console.log(node.value)
})
}
- 循环中的ref,需要自行绑定
setup() {
const itemsRef = []
const setItemRef = el => {
if (el) {
itemsRef.push(el)
}
}
}
- computed
import { computed } from 'vue';
setup() {
let fullName = computed(() => {
return person.firsName + '-' + person.lastName
})
let fullName = computed(() => {
get() {
return person.firsName + '-' + person.lastName
},
set(value) {
const nameArr = value.split('-');
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
return fullName
}
- watch
import { watch } from 'vue'
watch(sum, (newValue, oldValue) => {
}, {
immediate: true,
deep: false
})
- watchEffect不需要那个指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个,不需要写返回值
watchEffet(() => {
const x1 = sum.value
const x2 = person.age
console.log('watchEffect')
})
生命周期


自定义 Hooks
我们可以将一些公共逻辑封装成一个 hook, 我们约定这些「自定义 Hook」以 use 作为前缀,和普通的函数加以区分。
import { ref、 Ref、computed } from 'vue'
export dafault function useCount(initValue = 1) {
const count = ref(initValue);
const multiple = computed(() =>count.value * 2)
const increase = (delta) => {
count.value +=1;
}
const decrease = (delta) => {
count.value -= 1;
}
return {
count,
increase,
decrease,
multiple
}
}
<template>
<p>倍数: {{ multiple }}</p>
<div>
<button @click="increase()">加一</button>
<button @click="decrease()">减一</button>
</div>
</template>
<script>
import useCount from './useCount';
setup() {
const { count, increase, decrease, multiple } = useCount(10)
return {
count,
increase,
decrease,
multiple
}
}
</script>
简单对比 vue2.x 与 vue3.x 响应式
- Object.defineProperty只能劫持对象的属性, 而 Proxy 是直接代理对象
由于Object.defineProperty只能劫持对象属性,需要遍历对象的每一个属性,如果属性值也是对象,就需要递归进行深度遍历。但是 Proxy 直接代理对象, 不需要遍历操作 - Object.defineProperty对新增属性需要手动进行Observe
因为Object.defineProperty劫持的是对象的属性,所以新增属性时,需要重新遍历对象, 对其新增属性再次使用Object.defineProperty进行劫持。也就是 Vue2.x 中给数组和对象新增属性时,需要使用$set才能保证新增的属性也是响应式的, $set内部也是通过调用Object.defineProperty去处理的。
Teleport
Teleport 就像是哆啦 A 梦中的「任意门」,任意门的作用就是可以将人瞬间传送到另一个地方。有了这个认识,我们再来看一下为什么需要用到 Teleport 的特性呢,看一个小例子: 在子组件Header中使用到Dialog组件,我们实际开发中经常会在类似的情形下使用到 Dialog ,此时Dialog就被渲染到一层层子组件内部,处理嵌套组件的定位、z-index和样式都变得困难。 Dialog从用户感知的层面,应该是一个独立的组件,从 dom 结构应该完全剥离 Vue 顶层组件挂载的 DOM;同时还可以使用到 Vue 组件内的状态(data或者props)的值。简单来说就是,即希望继续在组件内部使用Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中。 此时就需要 Teleport 上场,我们可以用包裹Dialog, 此时就建立了一个传送门,可以将Dialog渲染的内容传送到任何指定的地方。
<body>
<div id="app"></div>
<div id="dialog"></div>
</body>
<template>
<telpport to="#dialog"></telpport>
...
</template>
Suspense
Suspense 只是一个带插槽的组件,只是它的插槽指定了default 和 fallback 两种状态。
vue2写法
<div v-if="!loading">{{data}}</div>
<div v-else>{{loading... }}</div>
vue3写法
<Suspense>
<template #default>
<async-component></async-component>
</template>
<template #fallback>
<div>loading...</div>
</template>
</Suspense>
片段(Fragment)
vue2中一个组件只能定义一个根节点
<template>
<div></div>
</template>
在vue3中
<template>
<div></div>
<div></div>
</template>
更好的 Tree-Shaking
受影响的 API
- Vue.nextTick
- Vue.observable(用 Vue.reactive 替换)
- Vue.version
- Vue.compile(仅限完整版本时可用)
- Vue.set(仅在 2.x 兼容版本中可用)
- Vue.delete(与上同)
内置工具
变更
slot 具名插槽语法
vue2
<slot name="title"></slot>
<template slot="title">
<h1>插槽</h1>
</template>
vue3
<template #content="scoped">
<div v-for="item in scoped.data"></div>
</template>
自定义指令
vue2
Vue.directive('focus', {
inserted: function(el) {
el.focus()
}
})
vue3
const { createApp } from 'vue'
const app = createApp({})
app.directive('focus', {
mounted(el) {
el.focus()
}
})
v-model 升级
异步组件
Vue3中使用defineAsyncComponent 定义异步组件
<template>
<AsyncPage/>
</template>
<sciprt>
import { defineAsyncComponent } from 'vue';
components: {
AsyncPage: defineAsyncComponent(() => import ("./Page.vue"))
AsyncPageOptions: defineAsyncComponent({
loader: () => import ("./Page.vue"),
dealu: 200,
timeout: 3000,
errorComponent: () => import("./Error.vue"),
loadinComponent: () => import("./Loading.vue")
})
}
</script>
Vue3设计思想以及响应式源码
Vue2缺点
- 对TS支持不友好(所有属性都在this上,难以推倒数据类型)
- 全局API都在Vue对象的原型上,无法实现TreeShaking
- 对跨平台渲染支持不友好
-
Vue3设计
- Vue3模块耦合度低,可以独立使用
- Vue3框架更小,更易扩展
- 基于monorepo管理项目,实现模块拆分
vue3源码
paclages
- reactivity: 响应式系统
- runtime-core: 与平台(web用的最多的是)无关的运行时核心
- runtime-dom: 针对浏览器的运行时,包括DOM API,属性。事件处理
- runtime-test: 测试文件
- server-renderer: 服务端渲染
- compiler-core: 与平台无关的编译器核心
- compiler-dom: 针对浏览器的编译模块
- compiler-ssr: 服务端渲染
scripts
Vue3在开发环境自己使用的,开发环境使用esbuild打包,生成环境使用rollup打包
Vue3中Reactivity模块
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
if (!isObject(target)) {
if (
__DEV__
) {
warn(
value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String( target, )}
,
)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// only specific value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
proxyMap.set(target, proxy)
return proxy
}

1565

被折叠的 条评论
为什么被折叠?



