[TOC]
响应性
响应性原理
Proxy
响应性基础
reactive
JavaScript 对象
创建响应式状态,可以使用 reactive 方法
import { reactive } from 'vue'
// 响应式状态
const state = reactive({
count: 0
})
reactive
相当于 Vue 2.x 中的Vue.observable()
API- 该响应式转换是“深度转换”——它会影响嵌套对象传递的所有 property。
- Vue 响应性系统的本质。当从组件中的
data()
返回一个对象时,它在内部交由reactive()
使其成为响应式对象。模板会被编译成能够使用这些响应式 property 的渲染函数。
ref
独立的原始值 (例如,一个字符串)创建响应式状态,可以使用 ref
方法
import { ref } from 'vue'
const count = ref(0)
ref
会返回一个可变的响应式对象,该对象作为它的内部值——一个响应式的引用- 此对象只包含一个名为
value
的 property - 某些情况会自动展开
- 当 ref 作为渲染上下文 (从 setup() 中返回的对象) 上的 property 返回并可以在模板中被访问时,它将自动展开为内部值。
```vue
title
- 当 ref 作为渲染上下文 (从 setup() 中返回的对象) 上的 property 返回并可以在模板中被访问时,它将自动展开为内部值。
```vue
`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 变量 (