看很多公司还是需要vue3优先的,最近把vue3的组合式api学习了一下并整理的笔记,大家可以看看自己有多少掌握的。
会在组件的生命周期函数之前执行
没有this,所以在 setup 中是不能拿到组件的其他的信息。但是在setup 外(methods,差值表达式)可以拿到setup 中的数据
可以return 一个对象作为data,和原来的data 方法一样。并且两者可以共存,在组件作用能同时能拿到两个对象里面的数据
两个data和methods 中有与setup 中的重名,那么会优先使用是setup函数中的变量
接收两个参数,props和context,且两个参数的内容都是只读的
steup (props, context) {
return {
props,
context
}
}
用来声明一些响应式数据,可以是any 类型的响应式数据,访问和更改数据需要
.value
const count = ref(10) const refObj = ref({ id: 2, key: 1 }) console.log(count.value); // 10 console.log(refObj); // RefImpl console.log(refObj.value); // Proxy {id: 2, key: 1} console.log(refObj.value.id); // 2 count.value ++ // 11
在模板中可以直接使用,不需要使用
.value
<p>{{ refObj.id }}</p> <p>{{ refObj.key }}</p>
总结: 声明响应式的数据,返回值不展开,需要
.value
,深层次递归嵌套的属性,可能导致性能问题
创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。ustomRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 track 和 trigger 两个函数作为参数,并返回一个带有 get 和 set 方法的对象。
ustomRef()
预期接收一个工厂函数作为参数,这个工厂函数接受track
和trigger
两个函数作为参数,并返回一个带有get
和set
方法的对象。import { customRef } from 'vue' export function useDebouncedRef(value, delay = 200) { let timeout return customRef((track, trigger) => { return { get() { track() return value }, set(newValue) { clearTimeout(timeout) timeout = setTimeout(() => { value = newValue trigger() }, delay) } } }) }
用来声明一些响应式对象数据,复杂类型,如果用来声明基本类型,将会警告,因为返回的值是展开的,而基本类型是无法展开
const reactiveNumber = reactive(2) // value cannot be made reactive: 2 // 部分源码 if (!isObject(target)) { if ((process.env.NODE_ENV !== 'production')) { console.warn(`value cannot be made reactive: ${String(target)}`); } return target; }
深层次的监听数据的变化,所有嵌套的属性改变都会触发视图的更新(响应式)。基于ES6的proxys实现,返回一个proxy 对象。返回的对象和原来的对象是不相等的。
const student = { id: 1, name: '阿木' } const people =reactive(student) student === people // false
reactive 声明的对象会自动展开解构,因此不需要使用
.value
去访问和更改他们的值people.id // 1 people.name // '阿木'
注意当访问到某个响应式数组或
Map
这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包:const books = reactive([ref('Vue 3 Guide')]) // 这里需要 .value console.log(books[0].value) const map = reactive(new Map([['count', ref(0)]])) // 这里需要 .value console.log(map.get('count').value
接受一个源对象,并返回proxy对象,更改proxy对象,那么源对象也会改变
// 修改proxy对象 people people.name = '忘喧' // 源对象也会改变 console.log(student); // 忘喧
**总结:**声明响应式的数据,返回值展开,不需要
.value
,深层次递归,会嵌套的属性,可能导致性能问题,返回的proxy 对象是对源对象的引用,浅复制。reactive 是对ref 的解包。
和
ref
类似,只有跟属性才是响应式,不会主动展开,需要用到.value
去访问和更新值。当去改变嵌套的属性时,数据会改变但是视图没有更新
const teacher = { name: '小毅', teach: { teach: { id: 666, hobiy: "swimming" } } } // 声明一个只监听跟属性改变是响应式数据 let shallowRefParam = shallowRef(teacher) // 改变嵌套的属性,数据改变,但是视图没有更新 function changeShallowRefData() { shallowRefParam.value.teacher.teach = { id: 5555, hobiy: "running" } }
嵌套属性发生改变的时候,
triggerRef(shallowRefObj)
可以使用手动更新视图function changeShallowRefUI() { triggerRef(shallowRefParam) }
shallowRef 只是浅复制对象,在改变数据的时候,原来的对象也会发生改变
console.log( teacher.teach.teach.hobiy) // running
shallowRef 是特殊的 ref ,多了一个 ShallowRefMarker来标识是否进行深层次的监听
export declare type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true; };
和 shallowRef类似,只有跟属性才会响应式,不会主动展开,需要用到
.value
去访问和更新值。嵌套属性更改不会触发视图的更新。const user = { id: 1, name: '阿木' } const shallowReactiveParam = shallowReactive(user); shallowReactiveParam.value.name // 阿木
只是对源对象浅复制,更新的时候和影响源对象
**注意:**没有 triggerReactive
基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然
const state = reactive({ foo: 1, bar: 2 }) const fooRef = toRef(state, 'foo') // 更改该 ref 会更新源属性 fooRef.value++ console.log(state.foo) // 2 // 更改源属性也会更新该 ref state.foo++ console.log(fooRef.value) // 3
将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef创建的.
toRefs
在调用时只会为源对象上可以枚举的属性创建 ref。如果要为可能还不存在的属性创建 ref,请改用 toReffunction useFeatureX() { const state = reactive({ foo: 1, bar: 2 }) // ...基于状态的操作逻辑 // 在返回时都转为 ref return toRefs(state) } // 可以解构而不会失去响应性 const { foo, bar } = useFeatureX()
toRaw() 可以返回由 reactive()、readonly()、shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象。
const foo = {} const reactiveFoo = reactive(foo) console.log(toRaw(reactiveFoo) === foo) // true
将一个对象标记为不可被转为代理。返回该对象本身。
有时候会不知道是不是需要
.value
去访问一个ref的值,可以使用这个判断是不是refif(isRef(refData)){ return refData.value }else { refData }
如果参数是 ref,则返回内部值,否则返回参数本身。这是
val = isRef(val) ? val.value : val
计算的一个语法糖。function useFoo(x: number | Ref<number>) { const unwrapped = unref(x) // unwrapped 现在保证为 number 类型 }
接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。
只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive()
相同,但解包得到的值是只读的。
要避免深层级的转换行为,请使用 shallowReadonly 作替代。
和 readonly() 不同,这里没有深层级的转换:只有根层级的属性变为了只读。属性的值都会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了。
const state = shallowReadonly({ foo: 1, nested: { bar: 2 } }) // 更改状态自身的属性会失败 state.foo++ // ...但可以更改下层嵌套对象 isReadonly(state.nested) // false // 这是可以通过的 state.nested.bar++
检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。
检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。
检查一个对象是否是由 readonly() 或 shallowReadonly() 创建的代理。
检查一个对象是不是由 shallowRef或shallowReactive,shallowReadonly创建的代理
const aa = shallowReactive({ a: 'aa' }) const bb = shallowRef('bb') const cc = shallowReadonly({ cc: 'cc' }) console.log(isShallow(aa)); // true console.log(isShallow(bb)); // true console.log(isShallow(cc)); // true
j模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护,可以computed
// 一个计算属性 ref const publishedBooksMessage = computed(() => { return author.books.length > 0 ? 'Yes' : 'No' })
计算属性有缓存,计算属性值会基于其响应式依赖被缓存
可以写的计算属性
const fullName = computed({ // getter get() { return firstName.value + ' ' + lastName.value }, // setter set(newValue) { // 注意:我们这里使用的是解构赋值语法 [firstName.value, lastName.value] = newValue.split(' ') } })
监听依赖项发生改变,就执行副作用
const count = ref(0) const stop = watchEffect(() => console.log(count.value)) // -> 输出 0 count.value++ // -> 输出 1 // 当不再需要此侦听器时: stop()
第二个参数是一个可选的选项,可以用来调整副作用的刷新时机或调试副作用的依赖。
interface WatchEffectOptions { flush?: 'pre'(侦听器将在组件渲染之前执行) | 'post' (将会使侦听器延迟到组件渲染之后再执行)| 'sync'(在响应式依赖发生改变时立即触发侦听器) // 默认:'pre' onTrack?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void }
watchEffect() 使用 flush: ‘post’ 选项时的别名
watchSyncEffect()
第一个参数是侦听器的源。这个来源可以是以下几种:
- 一个函数,返回一个值
- 一个 ref
- 一个响应式对象
- …或是由以上类型的值组成的数组
const count = ref(0) watch(count, (count, prevCount) => { /* ... */ })
创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。
const scope = effectScope() scope.run(() => { const doubled = computed(() => counter.value * 2) watch(doubled, () => console.log(doubled.value)) watchEffect(() => console.log('Count: ', doubled.value)) }) // 处理掉当前作用域内的所有 effect scope.stop()
如果有的话,返回当前活跃的 effect 作用域。
当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。
注册一个钩子,在组件被挂载之前被调用。
注册一个回调函数,在组件挂载完成后执行。
组件在以下情况下被视为已挂载:
- 其所有同步子组件都已经被挂载 (不包含异步组件或 `` 树内的组件)。
- 其自身的 DOM 树已经创建完成并插入了父容器中。注意仅当根容器在文档中时,才可以保证组件 DOM 树也在文档中。
注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。
这个钩子可以用来在 Vue 更新 DOM 之前访问 DOM 状态。在这个钩子中更改状态也是安全的。
注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用。
父组件的更新钩子将在其子组件的更新钩子之后调用
// 性能问题
这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的,如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。
onUpdated(() => { // 文本内容应该与当前的 `count.value` 一致 console.log(document.getElementById('count').textContent) }) </script>
不要这个这个钩子中更改状态,会死循环
注册一个钩子,在组件实例被卸载之前调用。
当这个钩子被调用时,组件实例依然还保有全部的功能。
注册一个回调函数,在组件实例被卸载之后调用。
一个组件在以下情况下被视为已卸载:
- 其所有子组件都已经被卸载。
- 所有相关的响应式作用 (渲染作用以及
setup()
时创建的计算属性和侦听器) 都已经停止
注册一个钩子,在捕获了后代组件传递的错误时调用
注册一个调试钩子,当组件渲染过程中追踪到响应式依赖时调用。
这个钩子仅在开发模式下可用,且在服务器端渲染期间不会被调用
注册一个调试钩子,当响应式依赖的变更触发了组件渲染时调用。
这个钩子仅在开发模式下可用,且在服务器端渲染期间不会被调用
注册一个回调函数,若组件实例是 `` 缓存树的一部分,当组件被插入到 DOM 中时调用。
注册一个回调函数,若组件实例是 `` 缓存树的一部分,当组件从 DOM 中被移除时调用。
注册一个异步函数,在组件实例在服务器上被渲染之前调用
果这个钩子返回了一个 Promise,服务端渲染会在渲染该组件前等待该 Promise 完成。
这个钩子仅会在服务端渲染中执行,可以用于执行一些仅存在于服务端的数据抓取过程。
提供一个值,可以被后代组件注入
provide() 必须在组件的 setup() 阶段同步调用
// 提供静态值 provide('foo', 'bar') // 提供响应式的值 const count = ref(0) provide('count', count)
注入一个由祖先组件或整个应用 (通过
app.provide()
) 提供的值// 注入值的默认方式 const foo = inject('foo') // 注入响应式的值 const count = inject('count') // 通过 Symbol 类型的 key 注入 const foo2 = inject(fooSymbol) // 注入一个值,若为空则使用提供的默认值 const bar = inject('foo', 'default value') // 注入一个值,若为空则使用提供的工厂函数 const baz = inject('foo', () => new Map()) // 注入时为了表明提供的默认值是个函数,需要传入第三个参数 const fn = inject('function', () => {}, false)
定义接受的父组件的props
//父组件调用子组件 <ChildrenCom :count="count" @chage="changeCount" /> // 子组件 // 接受父组件传入的props const props = defineProps(['name', 'count']) // 使用,可以直接使用 <h2>{{ count }}</h2> // props只读,不能直接在自组件中修改props function changeCount() { props.count = 20 // Set operation on key "count" failed: target is readonly } // 可以使emits 修改props的值
接受组件组件的传入的函数
// 父组件调用children <ChildrenCom :count="count" @chage="changeCount" /> // 子组件使用defineEmits const emits = defineEmits(['chage']) // 触发父组件传入的修改count的事件 function emitsChangeCount() { emits('chage') } // 也可以传入参数 function emitsChangeCount(value) { emits('chage',value) }
接受没有在props中声明的方法或者属性
//父组件调用子组件 <ChildrenCom :count="count" @chage="changeCount" age="1" :chageClick="changeCount" /> // 子组件 // 接受父组件传入的props const props = defineProps(['name', 'count']) const attrs = useAttrs() console.log('attrs', attrs); // chageClick age
attrs 只读
使用
的组件是**默认关闭**的——即通过模板引用或者 `$parent` 链获取到的组件的公开实例,**不会**暴露任何在
中声明的绑定。// 子组件 const name = ref('忘喧') const age = ref(10) const changeAge = () => { age.value = age.value + 1 } defineExpose({ name, age }) // 父组件 const childInstance = ref() function getChildInstance() { console.log(childInstance.value.name) // 忘喧 } <ChildrenCom ref="childInstance" />
认情况下,父组件传递的,但没有被子组件解析为 props 的 attributes 绑定会被“透传”。这意味着当我们有一个单根节点的子组件时,这些绑定会被作为一个常规的 HTML attribute 应用在子组件的根节点元素上。当你编写的组件想要在一个目标元素或其他组件外面包一层时,可能并不期望这样的行为。我们可以通过设置
inheritAttrs
为false
来禁用这个默认行为。这些 attributes 可以通过$attrs
这个实例属性来访问,并且可以通过
v-bind
来显式绑定在一个非根节点的元素上。在一个组件的
透传 attribute
const slots = useSlots()
参考:https://cn.vuejs.org/api/composition-api-setup.html#setup-context