• Vue组合式 api 的常用知识点


    Vue组合式 api 的常用知识点

    看很多公司还是需要vue3优先的,最近把vue3的组合式api学习了一下并整理的笔记,大家可以看看自己有多少掌握的。

    setup 函数

    会在组件的生命周期函数之前执行

    没有this,所以在 setup 中是不能拿到组件的其他的信息。但是在setup 外(methods,差值表达式)可以拿到setup 中的数据

    可以return 一个对象作为data,和原来的data 方法一样。并且两者可以共存,在组件作用能同时能拿到两个对象里面的数据

    两个data和methods 中有与setup 中的重名,那么会优先使用是setup函数中的变量

    接收两个参数,props和context,且两个参数的内容都是只读的

    steup (props, context) {
        return {
            props,
            context
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    响应式数据API

    ref

    用来声明一些响应式数据,可以是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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在模板中可以直接使用,不需要使用.value

    <p>{{ refObj.id }}</p>
    <p>{{ refObj.key }}</p>
    
    • 1
    • 2

    总结: 声明响应式的数据,返回值不展开,需要.value,深层次递归嵌套的属性,可能导致性能问题

    customRef()

    创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。ustomRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 track 和 trigger 两个函数作为参数,并返回一个带有 get 和 set 方法的对象。

    ustomRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 tracktrigger 两个函数作为参数,并返回一个带有 getset 方法的对象。

    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)
          }
        }
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    reactive

    用来声明一些响应式对象数据,复杂类型,如果用来声明基本类型,将会警告,因为返回的值是展开的,而基本类型是无法展开

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    深层次的监听数据的变化,所有嵌套的属性改变都会触发视图的更新(响应式)。基于ES6的proxys实现,返回一个proxy 对象。返回的对象和原来的对象是不相等的。

    const student = {
    id: 1,
    name: '阿木'
    }
    const people  =reactive(student)
    student ===  people // false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    reactive 声明的对象会自动展开解构,因此不需要使用.value去访问和更改他们的值

    people.id //   1
    people.name //  '阿木'
    
    • 1
    • 2

    注意当访问到某个响应式数组或 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    接受一个源对象,并返回proxy对象,更改proxy对象,那么源对象也会改变

    // 修改proxy对象 people
    people.name = '忘喧'
    // 源对象也会改变
    console.log(student); // 忘喧
    
    • 1
    • 2
    • 3
    • 4

    **总结:**声明响应式的数据,返回值展开,不需要.value,深层次递归,会嵌套的属性,可能导致性能问题,返回的proxy 对象是对源对象的引用,浅复制。reactive 是对ref 的解包。

    shallowRef + triggerRef

    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"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    嵌套属性发生改变的时候,triggerRef(shallowRefObj)可以使用手动更新视图

    function changeShallowRefUI() {
      triggerRef(shallowRefParam)
    }
    
    • 1
    • 2
    • 3

    shallowRef 只是浅复制对象,在改变数据的时候,原来的对象也会发生改变

    console.log( teacher.teach.teach.hobiy) // running
    
    • 1

    shallowRef 是特殊的 ref ,多了一个 ShallowRefMarker来标识是否进行深层次的监听

    export declare type ShallowRef<T = any> = Ref<T> & {
        [ShallowRefMarker]?: true;
    };
    
    • 1
    • 2
    • 3
    shallowReactive

    和 shallowRef类似,只有跟属性才会响应式,不会主动展开,需要用到.value去访问和更新值。嵌套属性更改不会触发视图的更新。

    const user = {
      id: 1,
      name: '阿木'
    }
    const shallowReactiveParam = shallowReactive(user);
    shallowReactiveParam.value.name // 阿木
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    只是对源对象浅复制,更新的时候和影响源对象

    **注意:**没有 triggerReactive

    toRef

    基于响应式对象上的一个属性,创建一个对应的 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    toRefs

    将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef创建的.

    toRefs 在调用时只会为源对象上可以枚举的属性创建 ref。如果要为可能还不存在的属性创建 ref,请改用 toRef

    function useFeatureX() {
      const state = reactive({
        foo: 1,
        bar: 2
      })
    
      // ...基于状态的操作逻辑
    
      // 在返回时都转为 ref
      return toRefs(state)
    }
    
    // 可以解构而不会失去响应性
    const { foo, bar } = useFeatureX()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    toRaw()

    toRaw() 可以返回由 reactive()、readonly()、shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象。

    const foo = {}
    const reactiveFoo = reactive(foo)
    
    console.log(toRaw(reactiveFoo) === foo) // true
    
    • 1
    • 2
    • 3
    • 4
    markRaw

    将一个对象标记为不可被转为代理。返回该对象本身。

    isRef

    有时候会不知道是不是需要.value去访问一个ref的值,可以使用这个判断是不是ref

    if(isRef(refData)){
    	return	refData.value
    }else {
    		refData
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    unref

    如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 计算的一个语法糖。

    function useFoo(x: number | Ref<number>) {
      const unwrapped = unref(x)
      // unwrapped 现在保证为 number 类型
    }
    
    • 1
    • 2
    • 3
    • 4
    readonly

    接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

    只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。

    要避免深层级的转换行为,请使用 shallowReadonly 作替代。

    shallowReadonly

    和 readonly() 不同,这里没有深层级的转换:只有根层级的属性变为了只读。属性的值都会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了。

    const state = shallowReadonly({
      foo: 1,
      nested: {
        bar: 2
      }
    })
    
    // 更改状态自身的属性会失败
    state.foo++
    
    // ...但可以更改下层嵌套对象
    isReadonly(state.nested) // false
    
    // 这是可以通过的
    state.nested.bar++
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    isProxy

    检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。

    isReactive

    检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。

    isReadonly

    检查一个对象是否是由 readonly() 或 shallowReadonly() 创建的代理。

    isShallow

    检查一个对象是不是由 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    computed

    j模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护,可以computed

    // 一个计算属性 ref
    const publishedBooksMessage = computed(() => {
      return author.books.length > 0 ? 'Yes' : 'No'
    })
    
    • 1
    • 2
    • 3
    • 4

    计算属性有缓存,计算属性值会基于其响应式依赖被缓存

    可以写的计算属性

    const fullName = computed({
      // getter
      get() {
        return firstName.value + ' ' + lastName.value
      },
      // setter
      set(newValue) {
        // 注意:我们这里使用的是解构赋值语法
        [firstName.value, lastName.value] = newValue.split(' ')
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    watchEffect

    监听依赖项发生改变,就执行副作用

    const count = ref(0)
    
    const stop = watchEffect(() => console.log(count.value))
    // -> 输出 0
    
    count.value++
    // -> 输出 1
    
    // 当不再需要此侦听器时:
    stop()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    第二个参数是一个可选的选项,可以用来调整副作用的刷新时机或调试副作用的依赖。

    interface WatchEffectOptions {
      flush?: 'pre'(侦听器将在组件渲染之前执行) | 'post' (将会使侦听器延迟到组件渲染之后再执行)| 'sync'(在响应式依赖发生改变时立即触发侦听器) // 默认:'pre'
      onTrack?: (event: DebuggerEvent) => void
      onTrigger?: (event: DebuggerEvent) => void
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    watchPostEffect()

    watchEffect() 使用 flush: ‘post’ 选项时的别名

    watchSyncEffect()

    watchSyncEffect()

    watch()

    第一个参数是侦听器的。这个来源可以是以下几种:

    • 一个函数,返回一个值
    • 一个 ref
    • 一个响应式对象
    • …或是由以上类型的值组成的数组
    const count = ref(0)
    watch(count, (count, prevCount) => {
    /* ... */
    })
    
    • 1
    • 2
    • 3
    • 4
    effectScope

    创建一个 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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    getCurrentScope()

    如果有的话,返回当前活跃的 effect 作用域。

    当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。

    生命周期

    onBeforeMount()

    注册一个钩子,在组件被挂载之前被调用。

    onMounted()

    注册一个回调函数,在组件挂载完成后执行。

    组件在以下情况下被视为已挂载:

    • 其所有同步子组件都已经被挂载 (不包含异步组件或 `` 树内的组件)。
    • 其自身的 DOM 树已经创建完成并插入了父容器中。注意仅当根容器在文档中时,才可以保证组件 DOM 树也在文档中。
    onBeforeUpdate()

    注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。

    这个钩子可以用来在 Vue 更新 DOM 之前访问 DOM 状态。在这个钩子中更改状态也是安全的。

    onUpdated()

    注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用。

    父组件的更新钩子将在其子组件的更新钩子之后调用

    // 性能问题

    这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的,如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。

    onUpdated(() => {
      // 文本内容应该与当前的 `count.value` 一致
      console.log(document.getElementById('count').textContent)
    })
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    不要这个这个钩子中更改状态,会死循环

    onBeforeUnmount()

    注册一个钩子,在组件实例被卸载之前调用。

    当这个钩子被调用时,组件实例依然还保有全部的功能。

    onUnmounted

    注册一个回调函数,在组件实例被卸载之后调用。

    一个组件在以下情况下被视为已卸载:

    • 其所有子组件都已经被卸载。
    • 所有相关的响应式作用 (渲染作用以及 setup() 时创建的计算属性和侦听器) 都已经停止
    onErrorCaptured

    注册一个钩子,在捕获了后代组件传递的错误时调用

    onRenderTracked

    注册一个调试钩子,当组件渲染过程中追踪到响应式依赖时调用。

    这个钩子仅在开发模式下可用,且在服务器端渲染期间不会被调用

    onRenderTriggered

    注册一个调试钩子,当响应式依赖的变更触发了组件渲染时调用。

    这个钩子仅在开发模式下可用,且在服务器端渲染期间不会被调用

    onActivated

    注册一个回调函数,若组件实例是 `` 缓存树的一部分,当组件被插入到 DOM 中时调用。

    onDeactivated

    注册一个回调函数,若组件实例是 `` 缓存树的一部分,当组件从 DOM 中被移除时调用。

    onServerPrefetch

    注册一个异步函数,在组件实例在服务器上被渲染之前调用

    果这个钩子返回了一个 Promise,服务端渲染会在渲染该组件前等待该 Promise 完成。

    这个钩子仅会在服务端渲染中执行,可以用于执行一些仅存在于服务端的数据抓取过程。

    组件通信传递

    provide

    提供一个值,可以被后代组件注入

    provide() 必须在组件的 setup() 阶段同步调用

    // 提供静态值
    provide('foo', 'bar')
    
    // 提供响应式的值
    const count = ref(0)
    provide('count', count)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    inject

    注入一个由祖先组件或整个应用 (通过 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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    defineProps

    定义接受的父组件的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的值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    defineEmits

    接受组件组件的传入的函数

    // 父组件调用children
    <ChildrenCom :count="count"  @chage="changeCount"   />
     
    // 子组件使用defineEmits
    const emits = defineEmits(['chage'])
    
    // 触发父组件传入的修改count的事件
    function emitsChangeCount() {
      emits('chage')
    }
    
    // 也可以传入参数
    function emitsChangeCount(value) {
      emits('chage',value)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    useAttrs

    接受没有在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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    attrs 只读

    defineExpose

    使用 的组件是**默认关闭**的——即通过模板引用或者 `$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" />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    inheritAttrs

    认情况下,父组件传递的,但没有被子组件解析为 props 的 attributes 绑定会被“透传”。这意味着当我们有一个单根节点的子组件时,这些绑定会被作为一个常规的 HTML attribute 应用在子组件的根节点元素上。当你编写的组件想要在一个目标元素或其他组件外面包一层时,可能并不期望这样的行为。我们可以通过设置 inheritAttrsfalse 来禁用这个默认行为。这些 attributes 可以通过 $attrs 这个实例属性来访问,

    并且可以通过 v-bind 来显式绑定在一个非根节点的元素上。

    在一个组件的

    透传 attribute

    useSlots
    const slots = useSlots()
    
    • 1

    参考:https://cn.vuejs.org/api/composition-api-setup.html#setup-context

  • 相关阅读:
    二十一、学习Lua 面向对象
    【DL】Windiws10系统下安装CUDA和CUDNN实践教程
    23、短信登录(基于redis实现共享session登录)
    Redis键值存储数据库(高性能缓存库)
    这款 AI 代码辅助插件真不错,还能帮你发现 bug!
    Python常见的数据分析工具有哪些?
    【NGINX入门指北】 基础篇
    ​​​​​​​头歌(EduCoder)Java实训作业答案
    【基础算法Ⅰ】算法入门篇
    安卓期末大作业——售票APP源码和设计报告
  • 原文地址:https://blog.csdn.net/XiaoYige_97/article/details/127718784