前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端系列12集-全局API,组合式API,选项式API的使用

前端系列12集-全局API,组合式API,选项式API的使用

作者头像
达达前端
发布2023-10-08 19:05:58
3640
发布2023-10-08 19:05:58
举报
文章被收录于专栏:达达前端达达前端

The setup() hook serves as the entry point for Composition API usage in components in the following cases: 在以下情况下, setup() 钩子用作组件中 Composition API 使用的入口点:

  1. Using Composition API without a build step; 在没有构建步骤的情况下使用 Composition API;
  2. Integrating with Composition-API-based code in an Options API component. 在 Options API 组件中与基于 Composition-API 的代码集成。

If you are using Composition API with Single-File Components, [<script setup>] is strongly recommended for a more succinct and ergonomic syntax. 如果您将 Composition API 与单文件组件一起使用,强烈建议使用 <script setup> 以获得更简洁和符合人体工程学的语法。

We can declare reactive state using [Reactivity APIs]and expose them to the template by returning an object from setup(). The properties on the returned object will also be made available on the component instance (if other options are used): 我们可以使用 Reactivity API 声明反应状态,并通过从 setup() 返回一个对象将它们暴露给模板。返回对象的属性也将在组件实例上可用(如果使用其他选项):

代码语言:javascript
复制
<script>
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)

    // expose to template and other options API hooks
    return {
      count
    }
  },

  mounted() {
    console.log(this.count) // 0
  }
}
</script>

<template>
  <button @click="count++">{{ count }}</button>
</template>

[refs] returned from setup are [automatically shallow unwrapped] when accessed in the template so you do not need to use .value when accessing them. They are also unwrapped in the same way when accessed on this. 当在模板中访问时,从 setup 返回的引用会自动浅层展开,因此您在访问它们时不需要使用 .value 。在 this 上访问时,它们也以相同的方式解包。

setup() itself does not have access to the component instance - this will have a value of undefined inside setup(). You can access Composition-API-exposed values from Options API, but not the other way around. setup() 本身无权访问组件实例 - this 将在 setup() 中具有 undefined 的值。您可以从 Options API 访问 Composition-API 公开的值,但反之则不行。

setup() should return an object synchronously. The only case when async setup() can be used is when the component is a descendant of a [Suspense] component. setup() 应该同步返回一个对象。可以使用 async setup() 的唯一情况是组件是 Suspense 组件的后代。

The first argument in the setup function is the props argument. Just as you would expect in a standard component, props inside of a setup function are reactive and will be updated when new props are passed in. setup 函数中的第一个参数是 props 参数。正如您在标准组件中所期望的那样, setup 函数中的 props 是响应式的,并且会在传入新 props 时更新。

代码语言:javascript
复制
export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
  }
}

Note that if you destructure the props object, the destructured variables will lose reactivity. It is therefore recommended to always access props in the form of props.xxx. 请注意,如果您解构 props 对象,解构的变量将失去反应性。因此,建议始终以 props.xxx 的形式访问道具。

If you really need to destructure the props, or need to pass a prop into an external function while retaining reactivity, you can do so with the [toRefs()]and [toRef()] utility APIs: 如果你真的需要解构 props,或者需要在保持反应性的同时将 prop 传递给外部函数,你可以使用 toRefs() 和 toRef() 实用程序 API 来实现:

代码语言:javascript
复制
import { toRefs, toRef } from 'vue'

export default {
  setup(props) {
    // turn `props` into an object of refs, then destructure
    const { title } = toRefs(props)
    // `title` is a ref that tracks `props.title`
    console.log(title.value)

    // OR, turn a single property on `props` into a ref
    const title = toRef(props, 'title')
  }
}

The second argument passed to the setup function is a Setup Context object. The context object exposes other values that may be useful inside setup: 传递给 setup 函数的第二个参数是一个设置上下文对象。 context 对象公开了在 setup 中可能有用的其他值:

代码语言:javascript
复制
export default {
  setup(props, context) {
    // Attributes (Non-reactive object, equivalent to $attrs)
    console.log(context.attrs)

    // Slots (Non-reactive object, equivalent to $slots)
    console.log(context.slots)

    // Emit events (Function, equivalent to $emit)
    console.log(context.emit)

    // Expose public properties (Function)
    console.log(context.expose)
  }
}

The context object is not reactive and can be safely destructured: 上下文对象不是反应式的,可以安全地解构:

代码语言:javascript
复制
export default {
  setup(props, { attrs, slots, emit, expose }) {
    ...
  }
}

attrs and slots are stateful objects that are always updated when the component itself is updated. This means you should avoid destructuring them and always reference properties as attrs.x or slots.x. Also note that, unlike props, the properties of attrs and slots are not reactive. If you intend to apply side effects based on changes to attrs or slots, you should do so inside an onBeforeUpdate lifecycle hook. attrs 和 slots 是有状态对象,它们总是在组件本身更新时更新。这意味着您应该避免解构它们并始终将属性引用为 attrs.x 或 slots.x 。另请注意,与 props 不同, attrs 和 slots 的属性不是反应性的。如果您打算根据对 attrs 或 slots 的更改应用副作用,您应该在 onBeforeUpdate 生命周期挂钩中这样做。

expose is a function that can be used to explicitly limit the properties exposed when the component instance is accessed by a parent component via [template refs]: expose 是一个函数,可用于显式限制父组件通过模板引用访问组件实例时公开的属性:

代码语言:javascript
复制
export default {
  setup(props, { expose }) {
    // make the instance "closed" -
    // i.e. do not expose anything to the parent
    expose()

    const publicCount = ref(0)
    const privateCount = ref(0)
    // selectively expose local state
    expose({ count: publicCount })
  }
}

setup can also return a [render function] which can directly make use of the reactive state declared in the same scope: setup 还可以返回一个渲染函数,它可以直接使用在同一范围内声明的反应状态:

代码语言:javascript
复制
import { h, ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return () => h('div', count.value)
  }
}

Returning a render function prevents us from returning anything else. Internally that shouldn't be a problem, but it can be problematic if we want to expose methods of this component to the parent component via template refs. 返回渲染函数会阻止我们返回任何其他东西。在内部这应该不是问题,但如果我们想通过模板引用将此组件的方法公开给父组件,则可能会出现问题。

We can solve this problem by calling [expose()]: 我们可以通过调用 expose() 来解决这个问题:

代码语言:javascript
复制
import { h, ref } from 'vue'

export default {
  setup(props, { expose }) {
    const count = ref(0)
    const increment = () => ++count.value

    expose({
      increment
    })

    return () => h('div', count.value)
  }
}

The increment method would then be available in the parent component via a template ref. 然后, increment 方法将通过模板引用在父组件中可用。

Takes an inner value and returns a reactive and mutable ref object, which has a single property .value that points to the inner value. 获取一个内部值并返回一个反应式和可变的 ref 对象,它有一个指向内部值的属性 .value 。

  • Type 类型
代码语言:javascript
复制
function ref<T>(value: T): Ref<UnwrapRef<T>>

interface Ref<T> {
  value: T
}

The ref object is mutable - i.e. you can assign new values to .value. It is also reactive - i.e. any read operations to .value are tracked, and write operations will trigger associated effects. ref 对象是可变的 - 即您可以为 .value 分配新值。它也是反应性的——即任何对 .value 的读操作都会被跟踪,而写操作将触发相关的效果。

If an object is assigned as a ref's value, the object is made deeply reactive with [reactive()]. This also means if the object contains nested refs, they will be deeply unwrapped. 如果一个对象被指定为 ref 的值,则该对象会被 reactive() 深度响应。这也意味着如果对象包含嵌套的引用,它们将被深度解包。

To avoid the deep conversion, use [shallowRef()]instead. 为避免深度转换,请改用 shallowRef() 。

代码语言:javascript
复制
const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

Takes a getter function and returns a readonly reactive [ref] object for the returned value from the getter. It can also take an object with get and set functions to create a writable ref object. 接受一个 getter 函数并为 getter 的返回值返回一个只读的反应式 ref 对象。它还可以使用带有 get 和 set 函数的对象来创建可写的ref 对象。

代码语言:javascript
复制
// read-only
function computed<T>(
  getter: () => T,
  // see "Computed Debugging" link below
  debuggerOptions?: DebuggerOptions
): Readonly<Ref<Readonly<T>>>

// writable
function computed<T>(
  options: {
    get: () => T
    set: (value: T) => void
  },
  debuggerOptions?: DebuggerOptions
): Ref<T>

Creating a readonly computed ref: 创建一个只读计算引用:

代码语言:javascript
复制
const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // error

Creating a writable computed ref: 创建一个可写的计算引用:

代码语言:javascript
复制
const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0

Debugging: 调试:

代码语言:javascript
复制
const plusOne = computed(() => count.value + 1, {
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})

Returns a reactive proxy of the object. 返回对象的反应代理。

Type 类型

代码语言:javascript
复制
function reactive<T extends object>(target: T): UnwrapNestedRefs<T>

The reactive conversion is "deep": it affects all nested properties. A reactive object also deeply unwraps any properties that are [refs]while maintaining reactivity. 反应式转换是“深度”的:它影响所有嵌套的属性。反应式对象还会在保持反应性的同时深入解包任何引用的属性。

It should also be noted that there is no ref unwrapping performed when the ref is accessed as an element of a reactive array or a native collection type like Map. 还应该注意的是,当 ref 作为反应数组的元素或像 Map 这样的本机集合类型访问时,不会执行 ref 解包。

To avoid the deep conversion and only retain reactivity at the root level, use [shallowReactive()]instead. 为避免深度转换并仅在根级别保留反应性,请改用 shallowReactive() 。

The returned object and its nested objects are wrapped with [ES Proxy] and not equal to the original objects. It is recommended to work exclusively with the reactive proxy and avoid relying on the original object. 返回对象及其嵌套对象被 ES Proxy 包裹,不等于原始对象。建议专门使用反应式代理并避免依赖原始对象。

Ref unwrapping: 参考展开:

代码语言:javascript
复制
const count = ref(1)
const obj = reactive({ count })

// ref will be unwrapped
console.log(obj.count === count.value) // true

// it will update `obj.count`
count.value++
console.log(count.value) // 2
console.log(obj.count) // 2

// it will also update `count` ref
obj.count++
console.log(obj.count) // 3
console.log(count.value) // 3

Note that refs are not unwrapped when accessed as array or collection elements: 请注意,当作为数组或集合元素访问时,refs 不会被解包:

代码语言:javascript
复制
const books = reactive([ref('Vue 3 Guide')])
// need .value here
console.log(books[0].value)

const map = reactive(new Map([['count', ref(0)]]))
// need .value here
console.log(map.get('count').value)

When assigning a [ref]to a reactive property, that ref will also be automatically unwrapped: 将 ref 分配给 reactive 属性时,该 ref 也会自动展开:

代码语言:javascript
复制
const count = ref(1)
const obj = reactive({})

obj.count = count

console.log(obj.count) // 1
console.log(obj.count === count.value) // true

Takes an object (reactive or plain) or a [ref]and returns a readonly proxy to the original. 获取一个对象(反应式或普通的)或一个 ref 并将只读代理返回给原始对象。

Type 类型

代码语言:javascript
复制
function readonly<T extends object>(
  target: T
): DeepReadonly<UnwrapNestedRefs<T>>

Details 细节

A readonly proxy is deep: any nested property accessed will be readonly as well. It also has the same ref-unwrapping behavior as reactive(), except the unwrapped values will also be made readonly. 只读代理很深:访问的任何嵌套属性也将是只读的。它还具有与 reactive() 相同的 ref-unwrapping 行为,除了解包后的值也将变为只读。

To avoid the deep conversion, use [shallowReadonly()] instead. 为避免深度转换,请改用 shallowReadonly() 。

Example 例子

代码语言:javascript
复制
const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // works for reactivity tracking
  console.log(copy.count)
})

// mutating original will trigger watchers relying on the copy
original.count++

// mutating the copy will fail and result in a warning
copy.count++ // warning!

Runs a function immediately while reactively tracking its dependencies and re-runs it whenever the dependencies are changed. 立即运行一个函数,同时反应性地跟踪它的依赖关系,并在依赖关系发生变化时重新运行它。

Type 类型

代码语言:javascript
复制
function watchEffect(
  effect: (onCleanup: OnCleanup) => void,
  options?: WatchEffectOptions
): StopHandle

type OnCleanup = (cleanupFn: () => void) => void

interface WatchEffectOptions {
  flush?: 'pre' | 'post' | 'sync' // default: 'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

type StopHandle = () => void

The first argument is the effect function to be run. The effect function receives a function that can be used to register a cleanup callback. The cleanup callback will be called right before the next time the effect is re-run, and can be used to clean up invalidated side effects, e.g. a pending async request. 第一个参数是要运行的效果函数。效果函数接收可用于注册清理回调的函数。清理回调将在下次重新运行效果之前调用,可用于清理无效的副作用,例如一个挂起的异步请求

The second argument is an optional options object that can be used to adjust the effect's flush timing or to debug the effect's dependencies. 第二个参数是一个可选的选项对象,可用于调整效果的刷新时间或调试效果的依赖项。

By default, watchers will run just prior to component rendering. Setting flush: 'post' will defer the watcher until after component rendering. See [Callback Flush Timing]for more information. In rare cases, it might be necessary to trigger a watcher immediately when a reactive dependency changes, e.g. to invalidate a cache. This can be achieved using flush: 'sync'. However, this setting should be used with caution, as it can lead to problems with performance and data consistency if multiple properties are being updated at the same time. 默认情况下,观察者将在组件渲染之前运行。设置 flush: 'post' 会将观察者推迟到组件渲染之后。有关详细信息,请参阅回调刷新时间。在极少数情况下,可能需要在反应依赖项发生变化时立即触发观察者,例如使缓存无效。这可以使用 flush: 'sync' 来实现。但是,应谨慎使用此设置,因为如果同时更新多个属性,它可能会导致性能和数据一致性问题。

The return value is a handle function that can be called to stop the effect from running again. 返回值是一个句柄函数,可以调用它来停止效果再次运行。

Example 例子

代码语言:javascript
复制
const count = ref(0)

watchEffect(() => console.log(count.value))
// -> logs 0

count.value++
// -> logs 1

Side effect cleanup: 副作用清理:

代码语言:javascript
复制
watchEffect(async (onCleanup) => {
  const { response, cancel } = doAsyncWork(id.value)
  // `cancel` will be called if `id` changes
  // so that previous pending request will be cancelled
  // if not yet completed
  onCleanup(cancel)
  data.value = await response
})

Stopping the watcher: 停止观察者:

代码语言:javascript
复制
const stop = watchEffect(() => {})

// when the watcher is no longer needed:
stop()

Options: 选项:

代码语言:javascript
复制
watchEffect(() => {}, {
  flush: 'post',
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})

Alias of [watchEffect()]with flush: 'post' option. 带有 flush: 'post' 选项的 watchEffect() 的别名。

  • [ref()]
  • [computed()]
  • [reactive()]
  • [readonly()]
  • [watchEffect() 观察效果()]
  • [watchPostEffect()]
  • [watchSyncEffect() 观察同步效果()]
  • [watch() ]

Alias of [watchEffect()]with flush: 'sync' option. 带有 flush: 'sync' 选项的 watchEffect() 的别名。

Watches one or more reactive data sources and invokes a callback function when the sources change. 监视一个或多个响应式数据源,并在数据源发生变化时调用回调函数。

Type 类型

代码语言:javascript
复制
// watching single source
function watch<T>(
  source: WatchSource<T>,
  callback: WatchCallback<T>,
  options?: WatchOptions
): StopHandle

// watching multiple sources
function watch<T>(
  sources: WatchSource<T>[],
  callback: WatchCallback<T[]>,
  options?: WatchOptions
): StopHandle

type WatchCallback<T> = (
  value: T,
  oldValue: T,
  onCleanup: (cleanupFn: () => void) => void
) => void

type WatchSource<T> =
  | Ref<T> // ref
  | (() => T) // getter
  | T extends object
  ? T
  : never // reactive object

interface WatchOptions extends WatchEffectOptions {
  immediate?: boolean // default: false
  deep?: boolean // default: false
  flush?: 'pre' | 'post' | 'sync' // default: 'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

Types are simplified for readability. 类型被简化以提高可读性。

watch() is lazy by default - i.e. the callback is only called when the watched source has changed. watch() 默认是惰性的 - 即回调仅在监视的源发生变化时调用。

The first argument is the watcher's source. The source can be one of the following: 第一个参数是观察者的来源。源可以是以下之一:

  • A getter function that returns a value 返回值的 getter 函数
  • A ref 参考
  • A reactive object 反应对象
  • ...or an array of the above. ...或以上的数组。

The second argument is the callback that will be called when the source changes. The callback receives three arguments: the new value, the old value, and a function for registering a side effect cleanup callback. The cleanup callback will be called right before the next time the effect is re-run, and can be used to clean up invalidated side effects, e.g. a pending async request. 第二个参数是当源更改时将调用的回调。回调接收三个参数:新值、旧值和用于注册副作用清理回调的函数。清理回调将在下次重新运行效果之前调用,可用于清理无效的副作用,例如挂起的异步请求

When watching multiple sources, the callback receives two arrays containing new / old values corresponding to the source array. 观看多个源时,回调接收两个包含与源数组对应的新/旧值的数组。

The third optional argument is an options object that supports the following options: 第三个可选参数是一个选项对象,它支持以下选项:

  • immediate: trigger the callback immediately on watcher creation. Old value will be undefined on the first call. immediate :在观察者创建时立即触发回调。第一次调用时旧值将是 undefined 。
  • deep: force deep traversal of the source if it is an object, so that the callback fires on deep mutations. See [Deep Watchers]. deep :如果源是一个对象,则强制对其进行深度遍历,以便在深度突变时触发回调。见深度观察者。
  • flush: adjust the callback's flush timing. See [Callback Flush Timing] and [watchEffect()]. flush : 调整回调的刷新时间。请参阅回调刷新时间和 watchEffect() 。
  • onTrack / onTrigger: debug the watcher's dependencies. See [Watcher Debugging]. onTrack / onTrigger : 调试观察者的依赖关系。请参阅监视程序调试。

Compared to [watchEffect()], watch() allows us to: 与 watchEffect() 相比, watch() 允许我们:

  • Perform the side effect lazily; 懒惰地执行副作用;
  • Be more specific about what state should trigger the watcher to re-run; 更具体地说明什么状态应该触发观察者重新运行;
  • Access both the previous and current value of the watched state. 访问监视状态的先前值和当前值。

Watching a getter: 观看吸气剂:

代码语言:javascript
复制
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

Watching a ref: 观看参考:

代码语言:javascript
复制
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})

When watching multiple sources, the callback receives arrays containing new / old values corresponding to the source array: 观看多个源时,回调接收包含与源数组对应的新/旧值的数组:

代码语言:javascript
复制
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

When using a getter source, the watcher only fires if the getter's return value has changed. If you want the callback to fire even on deep mutations, you need to explicitly force the watcher into deep mode with { deep: true }. Note in deep mode, the new value and the old will be the same object if the callback was triggered by a deep mutation: 当使用 getter 源时,watcher 只有在 getter 的返回值发生变化时才会触发。如果您希望回调甚至在深度突变时触发,您需要使用 { deep: true } 明确强制观察者进入深度模式。注意在深度模式下,如果回调是由深度突变触发的,新值和旧值将是同一个对象:

代码语言:javascript
复制
const state = reactive({ count: 0 })
watch(
  () => state,
  (newValue, oldValue) => {
    // newValue === oldValue
  },
  { deep: true }
)

When directly watching a reactive object, the watcher is automatically in deep mode: 当直接观察反应对象时,观察者自动进入深度模式:

代码语言:javascript
复制
const state = reactive({ count: 0 })
watch(state, () => {
  /* triggers on deep mutation to state */
})

watch() shares the same flush timing and debugging options with [watchEffect()]: watch() 与 watchEffect() 共享相同的刷新时间和调试选项:

代码语言:javascript
复制
watch(source, callback, {
  flush: 'post',
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})

Stopping the watcher: 停止观察者:

代码语言:javascript
复制
const stop = watch(source, callback)

// when the watcher is no longer needed:
stop()

Side effect cleanup: 副作用清理:

代码语言:javascript
复制
watch(id, async (newId, oldId, onCleanup) => {
  const { response, cancel } = doAsyncWork(newId)
  // `cancel` will be called if `id` changes, cancelling
  // the previous request if it hasn't completed yet
  onCleanup(cancel)
  data.value = await response
})
  • [isRef()]
  • [unref()]
  • [toRef()]
  • [toRefs()]
  • [isProxy()]
  • [isReactive()]
  • [isReadonly()]

Checks if a value is a ref object. 检查一个值是否是一个 ref 对象。

Type 类型

代码语言:javascript
复制
function isRef<T>(r: Ref<T> | unknown): r is Ref<T>

Note the return type is a [type predicate], which means isRef can be used as a type guard: 请注意,返回类型是类型谓词,这意味着 isRef 可以用作类型保护:

代码语言:javascript
复制
let foo: unknown
if (isRef(foo)) {
  // foo's type is narrowed to Ref<unknown>
  foo.value
}

Returns the inner value if the argument is a ref, otherwise return the argument itself. This is a sugar function for val = isRef(val) ? val.value : val. 如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 的糖函数。

Type 类型

代码语言:javascript
复制
function unref<T>(ref: T | Ref<T>): T

Example 例子

代码语言:javascript
复制
function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x)
  // unwrapped is guaranteed to be number now
}

Can be used to create a ref for a property on a source reactive object. The created ref is synced with its source property: mutating the source property will update the ref, and vice-versa. 可用于为源反应对象上的属性创建引用。创建的 ref 与其 source 属性同步:改变 source 属性将更新 ref,反之亦然。

Type 类型

代码语言:javascript
复制
function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
  defaultValue?: T[K]
): ToRef<T[K]>

type ToRef<T> = T extends Ref ? T : Ref<T>

Example 例子

代码语言:javascript
复制
const state = reactive({
  foo: 1,
  bar: 2
})

const fooRef = toRef(state, 'foo')

// mutating the ref updates the original
fooRef.value++
console.log(state.foo) // 2

// mutating the original also updates the ref
state.foo++
console.log(fooRef.value) // 3

Note this is different from: 请注意,这不同于:

代码语言:javascript
复制
const fooRef = ref(state.foo)

The above ref is not synced with state.foo, because the ref() receives a plain number value. 上面的 ref 没有与 state.foo 同步,因为 ref() 接收到一个纯数字值。

toRef() is useful when you want to pass the ref of a prop to a composable function: 当你想将 prop 的 ref 传递给可组合函数时, toRef() 很有用:

代码语言:javascript
复制
<script setup>
import { toRef } from 'vue'

const props = defineProps(/* ... */)

// convert `props.foo` into a ref, then pass into
// a composable
useSomeFeature(toRef(props, 'foo'))
</script>

When toRef is used with component props, the usual restrictions around mutating the props still apply. Attempting to assign a new value to the ref is equivalent to trying to modify the prop directly and is not allowed. In that scenario you may want to consider using [computed]with get and set instead. See the guide to [using v-model with components] for more information. 当 toRef 与组件 props 一起使用时,改变 props 的通常限制仍然适用。尝试为 ref 分配一个新值等同于尝试直接修改 prop 并且是不允许的。在这种情况下,您可能需要考虑使用 computed 和 get 以及 set 。有关详细信息,请参阅将 v-model 与组件一起使用的指南。

toRef() will return a usable ref even if the source property doesn't currently exist. This makes it possible to work with optional properties, which wouldn't be picked up by [toRefs]. toRef() 将返回可用的引用,即使源属性当前不存在。这使得使用 toRefs 不会选择的可选属性成为可能。

Converts a reactive object to a plain object where each property of the resulting object is a ref pointing to the corresponding property of the original object. Each individual ref is created using [toRef()]. 将反应对象转换为普通对象,其中结果对象的每个属性都是指向原始对象相应属性的引用。每个单独的 ref 都是使用 toRef() 创建的。

Type 类型

代码语言:javascript
复制
function toRefs<T extends object>(
  object: T
): {
  [K in keyof T]: ToRef<T[K]>
}

type ToRef = T extends Ref ? T : Ref<T>

Example 例子

代码语言:javascript
复制
const state = reactive({
  foo: 1,
  bar: 2
})

const stateAsRefs = toRefs(state)
/*
Type of stateAsRefs: {
  foo: Ref<number>,
  bar: Ref<number>
}
*/

// The ref and the original property is "linked"
state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3
代码语言:javascript
复制
function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2
  })

  // ...logic operating on state

  // convert to refs when returning
  return toRefs(state)
}

// can destructure without losing reactivity
const { foo, bar } = useFeatureX()

toRefs will only generate refs for properties that are enumerable on the source object at call time. To create a ref for a property that may not exist yet, use [toRef]instead.

toRefs 只会为调用时源对象上可枚举的属性生成引用。要为可能尚不存在的属性创建引用,请改用 toRef 。

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

示例

代码语言:javascript
复制
const state = shallowRef({ count: 1 })

// 不会触发更改
state.value.count = 2

// 会触发更改
state.value = { count: 2 }

示例

代码语言:javascript
复制
const shallow = shallowRef({
  greet: 'Hello, world'
})

// 触发该副作用第一次应该会打印 "Hello, world"
watchEffect(() => {
  console.log(shallow.value.greet)
})

// 这次变更不应触发副作用,因为这个 ref 是浅层的
shallow.value.greet = 'Hello, universe'

// 打印 "Hello, universe"
triggerRef(shallow)

示例

代码语言:javascript
复制
const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 更改状态自身的属性是响应式的
state.foo++

// ...但下层嵌套对象不会被转为响应式
isReactive(state.nested) // false

// 不是响应式的
state.nested.bar++

示例

代码语言:javascript
复制
const state = shallowReadonly({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 更改状态自身的属性会失败
state.foo++

// ...但可以更改下层嵌套对象
isReadonly(state.nested) // false

// 这是可以通过的
state.nested.bar++

示例

代码语言:javascript
复制
const foo = {}
const reactiveFoo = reactive(foo)

console.log(toRaw(reactiveFoo) === foo) // true

示例

代码语言:javascript
复制
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false

// 也适用于嵌套在其他响应性对象
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false

示例

代码语言:javascript
复制
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()

这个钩子在服务器端渲染期间不会被调用。

示例

通过模板引用访问一个元素:

代码语言:javascript
复制
<script setup>
import { ref, onMounted } from 'vue'

const el = ref()

onMounted(() => {
  el.value // <div>
})
</script>

<template>
  <div ref="el"></div>
</template>

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

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

  • 这个钩子在服务器端渲染期间不会被调用。 warning (警告) 不要在 updated (更新) 钩子中更改组件的状态,这可能会导致无限的更新循环!
  • 示例

访问更新后的 DOM

代码语言:javascript
复制
<script setup>
import { ref, onUpdated } from 'vue'

const count = ref(0)

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

<template>
  <button id="count" @click="count++">{{ count }}</button>
</template>

可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。

这个钩子在服务器端渲染期间不会被调用。

示例

代码语言:javascript
复制
<script setup>
import { onMounted, onUnmounted } from 'vue'

let intervalId
onMounted(() => {
  intervalId = setInterval(() => {
    // ...
  })
})

onUnmounted(() => clearInterval(intervalId))
</script>

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

这个钩子在服务器端渲染期间不会被调用。

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

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

这个钩子在服务器端渲染期间不会被调用。

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

这个钩子在服务器端渲染期间不会被调用。

可以在 errorCaptured() 中更改组件状态来为用户显示一个错误状态。注意不要让错误状态再次渲染导致本次错误的内容,否则组件会陷入无限循环。

这个钩子可以通过返回 false 来阻止错误继续向上传递。

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

这个钩子在服务器端渲染期间不会被调用。

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

这个钩子在服务器端渲染期间不会被调用。

示例

代码语言:javascript
复制
<script setup>
import { ref, provide } from 'vue'
import { fooSymbol } from './injectionSymbols'

// 提供静态值
provide('foo', 'bar')

// 提供响应式的值
const count = ref(0)
provide('count', count)

// 提供时将 Symbol 作为 key
provide(fooSymbol, count)
</script>

假设有一个父组件已经提供了一些值,如前面 provide() 的例子中所示:

代码语言:javascript
复制
<script setup>
import { inject } from 'vue'
import { fooSymbol } from './injectionSymbols'

// 注入值的默认方式
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)
</script>

组合式 API

[setup()]

[响应式: 核心]

[响应式: 工具]

[响应式: 进阶]

[生命周期钩子]

[依赖注入]

简易声明:

代码语言:javascript
复制
export default {
  props: ['size', 'myMessage']
}

对象声明,带有验证:

代码语言:javascript
复制
export default {
  props: {
    // 类型检查
    height: Number,
    // 类型检查 + 其他验证
    age: {
      type: Number,
      default: 0,
      required: true,
      validator: (value) => {
        return value >= 0
      }
    }
  }
}

示例

代码语言:javascript
复制
export default {
  data() {
    return { a: 1 }
  },
  computed: {
    // 只读
    aDouble() {
      return this.a * 2
    },
    // 可写
    aPlus: {
      get() {
        return this.a + 1
      },
      set(v) {
        this.a = v - 1
      }
    }
  },
  created() {
    console.log(this.aDouble) // => 2
    console.log(this.aPlus) // => 2

    this.aPlus = 3
    console.log(this.a) // => 2
    console.log(this.aDouble) // => 4
  }
}

在声明方法时避免使用箭头函数,因为它们不能通过 this 访问组件实例。

示例

代码语言:javascript
复制
export default {
  data() {
    return { a: 1 }
  },
  methods: {
    plus() {
      this.a++
    }
  },
  created() {
    this.plus()
    console.log(this.a) // => 2
  }
}

示例

代码语言:javascript
复制
export default {
  data() {
    return {
      a: 1,
      b: 2,
      c: {
        d: 4
      },
      e: 5,
      f: 6
    }
  },
  watch: {
    // 侦听根级属性
    a(val, oldVal) {
      console.log(`new: ${val}, old: ${oldVal}`)
    },
    // 字符串方法名称
    b: 'someMethod',
    // 该回调将会在被侦听的对象的属性改变时调动,无论其被嵌套多深
    c: {
      handler(val, oldVal) {
        console.log('c changed')
      },
      deep: true
    },
    // 侦听单个嵌套属性:
    'c.d': function (val, oldVal) {
      // do something
    },
    // 该回调将会在侦听开始之后立即调用
    e: {
      handler(val, oldVal) {
        console.log('e changed')
      },
      immediate: true
    },
    // 你可以传入回调数组,它们将会被逐一调用
    f: [
      'handle1',
      function handle2(val, oldVal) {
        console.log('handle2 triggered')
      },
      {
        handler: function handle3(val, oldVal) {
          console.log('handle3 triggered')
        }
        /* ... */
      }
    ]
  },
  methods: {
    someMethod() {
      console.log('b changed')
    },
    handle1() {
      console.log('handle 1 triggered')
    }
  },
  created() {
    this.a = 3 // => new: 3, old: 1
  }
}

示例

数组语法:

代码语言:javascript
复制
export default {
  emits: ['check'],
  created() {
    this.$emit('check')
  }
}

对象语法:

代码语言:javascript
复制
export default {
  emits: {
    // 没有验证函数
    click: null,

    // 具有验证函数
    submit: (payload) => {
      if (payload.email && payload.password) {
        return true
      } else {
        console.warn(`Invalid submit event payload!`)
        return false
      }
    }
  }
}

expose 选项值应当是一个包含要暴露的属性名称字符串的数组。当使用 expose 时,只有显式列出的属性将在组件实例上暴露。

expose 仅影响用户定义的属性——它不会过滤掉内置的组件实例属性。

示例

代码语言:javascript
复制
export default {
  // 只有 `publicMethod` 在公共实例上可用
  expose: ['publicMethod'],
  methods: {
    publicMethod() {
      // ...
    },
    privateMethod() {
      // ...
    }
  }
}

[beforeCreate]

[created (创建)]

[beforeMount]

[mounted (挂载)]

[beforeUpdate]

[updated (更新)]

[beforeUnmount]

[unmounted]

[errorCaptured]

[renderTracked]

[renderTriggered]

[activated]

[deactivated]

[serverPrefetch]

示例

基本使用方式:

代码语言:javascript
复制
const s = Symbol()

export default {
  provide: {
    foo: 'foo',
    [s]: 'bar'
  }
}

使用函数可以提供其组件中的状态:

代码语言:javascript
复制
export default {
  data() {
    return {
      msg: 'foo'
    }
  }
  provide() {
    return {
      msg: this.msg
    }
  }
  • [provide (提供)]
  • [inject]
  • [mixins]
  • [extends (延长)]

基本使用方式:

代码语言:javascript
复制
export default {
  inject: ['foo'],
  created() {
    console.log(this.foo)
  }
}

使用注入的值作为 props 的默认值:

代码语言:javascript
复制
const Child = {
  inject: ['foo'],
  props: {
    bar: {
      default() {
        return this.foo
      }
    }
  }
}

使用注入的值作为 data:

代码语言:javascript
复制
const Child = {
  inject: ['foo'],
  data() {
    return {
      bar: this.foo
    }
  }
}

注入项可以选择是否带有默认值:

代码语言:javascript
复制
const Child = {
  inject: {
    foo: { default: 'foo' }
  }
}

如果需要从不同名字的属性中注入,请使用 from 指明来源属性。

js

代码语言:javascript
复制
const Child = {
  inject: {
    foo: {
      from: 'bar',
      default: 'foo'
    }
  }
}

和 props 默认值类似,对于非原始数据类型的值,你需要使用工厂函数:

代码语言:javascript
复制
const Child = {
  inject: {
    foo: {
      from: 'bar',
      default: () => [1, 2, 3]
    }
  }
}

mixins 选项接受一个 mixin 对象数组。这些 mixin 对象可以像普通的实例对象一样包含实例选项,它们将使用一定的选项合并逻辑与最终的选项进行合并。举例来说,如果你的 mixin 包含了一个 created 钩子,而组件自身也有一个,那么这两个函数都会被调用。

Mixin 钩子的调用顺序与提供它们的选项顺序相同,且会在组件自身的钩子前被调用。

不再推荐

在 Vue 2 中,mixins 是创建可重用组件逻辑的主要方式。尽管在 Vue 3 中保留了 mixins 支持,但对于组件间的逻辑复用,[ composition (组成) API]是现在更推荐的方式。

mixins 选项基本用于组合功能,而 extends 则一般更关注继承关系。

同 mixins 一样,所有选项都将使用相关的策略进行合并。

在 3.2.34 或以上的版本中,使用 <script setup> 的单文件组件会自动根据文件名生成对应的 name 选项,即使是在配合 <KeepAlive> 使用时也无需再手动声明。

示例

代码语言:javascript
复制
<script>
export default {
  inheritAttrs: false,
  props: ['label', 'value'],
  emits: ['input']
}
</script>

<template>
  <label>
    {{ label }}
    <input
      v-bind="$attrs"
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    />
  </label>
</template>

示例

代码语言:javascript
复制
import Foo from './Foo.vue'
import Bar from './Bar.vue'

export default {
  components: {
    // 简写
    Foo,
    // 注册为一个不同的名称
    RenamedBar: Bar
  }
}

示例

代码语言:javascript
复制
export default {
  directives: {
    // 在模板中启用 v-focus
    focus: {
      mounted(el) {
        el.focus()
      }
    }
  }
}
代码语言:javascript
复制
<input v-focus>

从 [data]选项函数中返回的对象,会被组件赋为响应式。组件实例将会代理对其数据对象的属性访问。

表示组件当前已解析的 props 对象。

$el 直到组件[挂载完成 ( mounted (挂载) )] 之前都会是 undefined

为保持一致性,我们推荐使用[模板引用]来直接访问元素而不是依赖 $el

这个 $options 对象暴露了当前组件的已解析选项,并且会是以下几种可能来源的合并结果:

  • 全局 mixin
  • 组件 extends 的基组件
  • 组件级 mixin

它通常用于支持自定义组件选项:

代码语言:javascript
复制
const app = createApp({
  customOption: 'foo',
  created() {
    console.log(this.$options.customOption) // => 'foo'
  }
})

每一个插槽都在 this.slots 上暴露为一个函数,返回一个 vnode 数组,同时 key 名对应着插槽名。默认插槽暴露为 this.slots.default。

侦听一个属性名:

代码语言:javascript
复制
this.$watch('a', (newVal, oldVal) => {})

侦听一个由 . 分隔的路径:

代码语言:javascript
复制
this.$watch('a.b', (newVal, oldVal) => {})

对更复杂表达式使用 getter 函数:

代码语言:javascript
复制
this.$watch(
  // 每一次这个 `this.a + this.b` 表达式生成一个
  // 不同的结果,处理函数都会被调用
  // 这就好像我们在侦听一个计算属性
  // 而不定义计算属性本身。
  () => this.a + this.b,
  (newVal, oldVal) => {}
)

停止该侦听器:

代码语言:javascript
复制
const unwatch = this.$watch('a', cb)

// 之后……
unwatch()

示例

代码语言:javascript
复制
export default {
  created() {
    // 仅触发事件
    this.$emit('foo')
    // 带有额外的参数
    this.$emit('bar', 1, 2, 3)
  }
}

和全局版本的 nextTick() 的唯一区别就是组件传递给 this.$nextTick() 的回调函数会带上 this 上下文,其绑定了当前组件实例。

暴露当前所使用的 Vue 版本。

类型 string

示例

代码语言:javascript
复制
import { version } from 'vue'

console.log(version)

示例

代码语言:javascript
复制
<script>
import { nextTick } from 'vue'

export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    async increment() {
      this.count++

      // DOM 还未更新
      console.log(document.getElementById('counter').textContent) // 0

      await nextTick()
      // DOM 此时已经更新
      console.log(document.getElementById('counter').textContent) // 1
    }
  }
}
</script>

<template>
  <button id="counter" @click="increment">{{ count }}</button>
</template>

示例

代码语言:javascript
复制
import { defineCustomElement } from 'vue'

const MyVueElement = defineCustomElement({
  /* 组件选项 */
})

// 注册自定义元素
customElements.define('my-vue-element', MyVueElement)

可以直接内联根组件:

代码语言:javascript
复制
import { createApp } from 'vue'

const app = createApp({
  /* root component options */
})

也可以使用从别处导入的组件:

代码语言:javascript
复制
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

示例

代码语言:javascript
复制
import { createApp } from 'vue'

const app = createApp(/* ... */)

app.provide('message', 'hello')

在应用的某个组件中:

代码语言:javascript
复制
export default {
  inject: ['message'],
  created() {
    console.log(this.message) // 'hello'
  }
}

若 app.use() 对同一个插件多次调用,该插件只会被安装一次。

示例

代码语言:javascript
复制
import { createApp } from 'vue'
import MyPlugin from './plugins/MyPlugin'

const app = createApp({
  /* ... */
})

app.use(MyPlugin)

Mixins 在 Vue 3 支持主要是为了向后兼容,因为生态中有许多库使用到。在新的应用中应尽量避免使用 mixin,特别是全局 mixin。

在一个插件中对版本作判断:

代码语言:javascript
复制
export default {
  install(app) {
    const version = Number(app.version.split('.')[0])
    if (version < 3) {
      console.warn('This plugin requires Vue 3')
    }
  }
}

设置此项为 true 可以在浏览器开发工具的“性能/时间线”页中启用对组件初始化、编译、渲染和修补的性能表现追踪。仅在开发模式和支持 [performance (性能) .mark] API 的浏览器中工作。

全局API,组合式API,选项式API的使用

仓库地址:https://github.com/webVueBlog/WebGuideInterview

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 组合式 API
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档