[TOC]

响应性

响应性原理

官方文档

Proxy

响应性原理

响应性基础

reactive

JavaScript 对象 创建响应式状态,可以使用 reactive 方法

import { reactive } from 'vue'

// 响应式状态
const state = reactive({
  count: 0
})
  1. reactive 相当于 Vue 2.x 中的 Vue.observable() API
  2. 该响应式转换是“深度转换”——它会影响嵌套对象传递的所有 property。
  3. Vue 响应性系统的本质。当从组件中的 data() 返回一个对象时,它在内部交由 reactive() 使其成为响应式对象。模板会被编译成能够使用这些响应式 property 的渲染函数。

ref

独立的原始值 (例如,一个字符串)创建响应式状态,可以使用 ref 方法

import { ref } from 'vue'

const count = ref(0)
  • ref 会返回一个可变的响应式对象,该对象作为它的内部值——一个响应式的引用
  • 此对象只包含一个名为 value 的 property
  • 某些情况会自动展开
    • 当 ref 作为渲染上下文 (从 setup() 中返回的对象) 上的 property 返回并可以在模板中被访问时,它将自动展开为内部值。 ```vue
``` - 当 `ref` 作为响应式对象的 property 被访问或更改时,为使其行为类似于普通 property,它会自动展开内部值 ```javascript 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) ``` ```javascript const count = ref(0) const state = reactive({ count }) console.log(state.count) // 0 state.count = 1 console.log(count.value) // 1 ``` - Ref 展开仅发生在被响应式 `Object` 嵌套的时候。当从 `Array` 或原生集合类型如 `map` 访问 ref 时 - 新的 ref 赋值给现有 ref 的 property,将会替换旧的 ref ```javascript const otherCount = ref(2) state.count = otherCount console.log(state.count) // 2 console.log(count.value) // 1 ``` ### toRefs ```javascript import { reactive } from 'vue' const book = reactive({ author: 'Vue Team', year: '2020', title: 'Vue 3 Guide', description: 'You are reading this book right now ;)', price: 'free' }) let { author, title } = book ``` - 解构的两个 property 的响应性都会丢失 - 需要响应式对象转换为一组 ref,这些 `ref` 将保留与源对象的响应式关联 ```javascript let { author, title } = toRefs(book) title.value = 'Vue 3 Detailed Guide' // 我们需要使用 .value 作为标题,现在是 ref console.log(book.title) // 'Vue 3 Detailed Guide' ``` ### readonly 防止在应用程序的某个位置更改它,例如 `provide/inject` 时候 ```javascript const original = reactive({ count: 0 }) const copy = readonly(original) // 在copy上转换original 会触发侦听器依赖 original.count++ // 转换copy 将导失败并导致警告 copy.count++ // 警告: "Set operation on key 'count' failed: target is readonly." ``` ## 响应式计算和监听 ### computed 接受 getter 函数并为 getter 返回的值返回一个不可变的响应式 `ref` 对象 ```javascript const count = ref(1) const plusOne = computed(() => count.value++) console.log(plusOne.value) // 2 plusOne.value++ // error ``` 带有 `get` 和 `set` 函数的对象 ```javascript computed({ get: () => count.value + 1, set: val => { count.value = val - 1 } }) ``` ### watchEffect 立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数
`watchEffect` 与 `watch` 区别 - 不需要手动传入依赖 - 每次初始化时会执行一次回调函数来自动获取依赖 - 无法获取到原值,只能得到变化后的值 ```javascript const count = ref(0) watchEffect(() => console.log(count.value)) // -> logs 0 setTimeout(() => { count.value++ // -> logs 1 }, 100) ``` - 失效回调 - 传入的函数可以接收一个 `onInvalidate` 函数作入参,用来注册清理失效时的回调 - 副作用即将重新执行时 - 侦听器被停止 (如果在 `setup()` 或生命周期钩子函数中使用了 `watchEffect`,则在组件卸载时) ```javascript watchEffect(onInvalidate => { const token = performAsyncOperation(id.value) onInvalidate(() => { // id has changed or watcher is stopped. // invalidate previously pending async operation token.cancel() }) }) ``` - 会在所有的组件 `update` **前**执行
### watch 数据源可以是返回值的 getter 函数,也可以直接是 `ref`
现在不支持用 字符串访问对象属性 作为数据源,可以用 `computed` 或函数代替 ```javascript // 侦听一个 getter const state = reactive({ count: 0 }) watch( () => state.count, (count, prevCount) => { /* ... */ } ) // 直接侦听ref const count = ref(0) watch(count, (count, prevCount) => { /* ... */ }) // 使用数组同时侦听多个源 watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { /* ... */ }) ``` # 组合式API [官方文档](https://v3.cn.vuejs.org/guide/composition-api-introduction.html#什么是组合式-api) ## 概述 v2中,处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块,**同一个逻辑关注点相关的代码配置在一起**会更好。 **复用、聚合逻辑** ## setup - 在**创建组件之前执行**,一旦 props 被解析,并充当合成 API 的入口点 - 由于在执行 `setup` 时尚未创建组件实例,因此在 `setup` 选项中没有 `this`。这意味着,除了 `props` 之外,你将无法访问组件中声明的任何属性——**本地状态**、**计算属性**或**方法**。 - `setup` 选项应该是一个接受 `props` 和 `context` 的函数 - props是响应式的,不能解构,要用 `toRefs` 来完成 - `context` 是一个普通的 JavaScript 对象,它暴露三个组件的 property, `context` 不是响应式的可以解构,但是 `attrs/slots/emit` 是响应式的,解构需要 `toRefs` ```javascript export default { setup(props, context) { // Attribute (非响应式对象) console.log(context.attrs) // 插槽 (非响应式对象) console.log(context.slots) // 触发事件 (方法) console.log(context.emit) } } ```
```javascript // src/components/UserRepositories.vue `setup` function import { fetchUserRepositories } from '@/api/repositories' import { ref } from 'vue' // in our component setup (props) { const repositories = ref([]) const getUserRepositories = async () => { repositories.value = await fetchUserRepositories(props.user) } return { repositories, getUserRepositories } } ``` ## 生命周期 在 `setup` 中注册生命周期钩子
![image.png](https://cdn.nlark.com/yuque/0/2021/png/2453125/1614927055626-27e81eb7-fac6-45c8-aa6e-244a26e0b290.png#align=left&display=inline&height=374&margin=%5Bobject%20Object%5D&name=image.png&originHeight=471&originWidth=364&size=48496&status=done&style=shadow&width=289) ```javascript export default { setup() { // mounted onMounted(() => { console.log('Component is mounted!') }) } } ``` ## watch ```javascript import { ref, watch } from 'vue' // setup export default { setup() { const counter = ref(0) watch(counter, (newValue, oldValue) => { console.log('The new counter value is: ' + counter.value) }) } } // 等效api export default { data() { return { counter: 0 } }, watch: { counter(newValue, oldValue) { console.log('The new counter value is: ' + this.counter) } } } ``` ## computed `computed` 函数返回一个作为 `computed` 的第一个参数传递的 getter 类回调的输出的一个_只读_的**响应式引用**。为了访问新创建的计算变量的 **value**,我们需要像使用 `ref` 一样使用 `.value` property ## **组合式函数(例子)** **hook1** ```javascript // src/composables/useUserRepositories.js import { fetchUserRepositories } from '@/api/repositories' import { ref, onMounted, watch } from 'vue' export default function useUserRepositories(user) { const repositories = ref([]) const getUserRepositories = async () => { repositories.value = await fetchUserRepositories(user.value) } onMounted(getUserRepositories) watch(user, getUserRepositories) return { repositories, getUserRepositories } } ``` **hook2** ```javascript // src/composables/useRepositoryNameSearch.js import { ref, computed } from 'vue' export default function useRepositoryNameSearch(repositories) { const searchQuery = ref('') const repositoriesMatchingSearchQuery = computed(() => { return repositories.value.filter(repository => { return repository.name.includes(searchQuery.value) }) }) return { searchQuery, repositoriesMatchingSearchQuery } } ``` **组件中使用它们** ```javascript // src/components/UserRepositories.vue import useUserRepositories from '@/composables/useUserRepositories' import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch' import { toRefs } from 'vue' export default { components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList }, props: { user: { type: String, required: true } }, setup (props) { const { user } = toRefs(props) const { repositories, getUserRepositories } = useUserRepositories(user) const { searchQuery, repositoriesMatchingSearchQuery } = useRepositoryNameSearch(repositories) return { // 因为我们并不关心未经过滤的仓库 // 我们可以在 `repositories` 名称下暴露过滤后的结果 repositories: repositoriesMatchingSearchQuery, getUserRepositories, searchQuery, } }, data () { return { filters: { ... }, // 3 } }, computed: { filteredRepositories () { ... }, // 3 }, methods: { updateFilters () { ... }, // 3 } } ``` # Teleport ## 片段 多个根节点 # 单文件组件组合式 API 语法糖 ( ``` ## With TypeScript ```html ``` # 单文件组件状态驱动的 CSS 变量 (