>[success] # reactive
1. `reactive `函数创建一个响应式**对象或数组**,返回对象的响应式副本,**并且他响应转换是深层的它影响所有嵌套属性**,基于[JavaScript Proxy](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy)实现,和` 2.x`的 `Vue.observable()`等同,
* **定义对象** 时候使用`reactive ` 包裹并且返回一个`proxy`对象**这个对象具有响应式**,注: **仅对对象类型有效(对象、数组和`Map`、`Set`这样的[集合类型](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects#%E4%BD%BF%E7%94%A8%E9%94%AE%E7%9A%84%E9%9B%86%E5%90%88%E5%AF%B9%E8%B1%A1)),而对`string`、`number`和`boolean`这样的[原始类型](https://developer.mozilla.org/zh-CN/docs/Glossary/Primitive)无效**。
* **深层**这个对象内部嵌套多深所有嵌套的属性都能转换为**响应式的**
* 内部基于 `ES6` 的 `Proxy `实现,**通过代理对象操作源对象内部数据都是响应式的**
* **为保证访问代理的一致性,对同一个原始对象调用`reactive()`会总是返回同样的代理对象,而对一个已存在的代理对象调用`reactive()`会返回其本身**
* `Vue `的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用,因为使用了,`Proxy`代理在 `get `和 `set `方法做了劫持 因此能在数据变化的时候可以重新渲染`template `模板,**当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行`依赖收集`**,**当数据发生改变时,所有收集到的依赖都是进行对应的`响应式操作`(比如更新界面)**
2. 官网使用`Proxy ` 收集`reactive `的一个缩写版本源码实现
~~~
const dinner = {
meal: 'tacos'
}
const handler = {
get(target, property, receiver) {
track(target, property)
return Reflect.get(...arguments)
},
set(target, property, value, receiver) {
trigger(target, property)
return Reflect.set(...arguments)
}
}
const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)
~~~
>[info] ## 案例
1. [案例](https://sfc.vuejs.org/#eNqNVslu00AYfpWRL25EG8O1SiIQL8CBG+bgJhMIJLY1dgI0ilT2LYECZWtCIXBoRddUQNONvozHcU+8Av+Md8eFOlI0M/8333z/Zk9TuKDr2UYdC9NCziiSim6igqxWarpGTNREBCtFs9LAqIXKRKshEaCirMoqvs0RJVxW6lVAyiqCR1VqeBqJwClOMhRbM7BZ1ycyPoQ9koSKmmqYaEYx8OU7Okb54KQJ8ZyYQfTznt1bGw326dLz0eKD0f4rurFof9g8XvjobG7Cik8WkrqMRa2umkDXRA2lWgcx51ArdjCdO7D2v43mH9lP5umbDt1boAcvkyw60W7fuehRBco4dybExnjtnQEd3HOpk3QljPVLjDLK1my5VHGWNx3rsOd8XQ0CT9sPnW+79OGO3Xsa6KWbu86gD64kT1JKJX6Qrx3ini9EQ+9DtSrOVrVrE6GnEcfYc+ZMaMryWIZ2iGiKavriMyg67v88/vTVGnasg0W7+8N+t0W7X6zhHrMuPwcA3f1lb/fBG/efvm67W5yjrtNv22+37M4GfbZqt++mOHdqv3iuXN1jjkVsMZ9S8+qVHs+uvdaH0qODJXtuOS3HF0yTcHH5QkyaJ58w4xWxrGnipDijEPFqFBQUSdYFwv+J5itnrwJCFmZnZ2UhigpzFs+W6f7An6D+Msg5WqDdJfAIGssazlnD716SeHXBOuIH/jlo0437kKeolW/pjLrD0frTSDuGbVjTFcL6OjUYsSyhfD4fEZ5hUS8rVSOSHJ6J48cdVk28H6zfn+z3y9bh0Whhhe5s0/UPtLcCOtz+cyUy3b0VOt/2fOOL1vCZs3UfdkUDAaXqxaI7BHzAc5LsxBshzQGT1DGrJD/yPPYgx9Xi0v9bC8Tcefyd7gz+qyJ69AlSog0bqXQC72aiJvvI7zN4hSeWw3dLwhYemTAEZZuyztolsezVzZjYFuBgmJPcbxR8omBi4ppeVUwMM47NlSoNf+xPm82INrfnUauVk1KhgdgQ4WcvN1M3TU1F54vVSvFmXhZ8B2ShwIY5yQWEpCl7fO+Egjca34XGNsXCDltj81MSRPambfOdzUlBSIVJwb0ETNUUPXvD0FS4IfA6kT2DIQvTfuXIAtwL2FwWrpumbkxLklEusnvFDSOrkWsSjLIEDq7UcBYbtakZot0yMAFiWfBKgHNIsNjAZIpgtYQJhuiezJmAjvEyWqiZltD6C2yqZQQ=)将`count `对象通过`reactive `包裹变成响应式,当操作 `addProxyCount `方法(该方法操作响应式对象`proxyCount`)数据改变页面也会改变,当操作`addCount `方法时(操作是`target `对象)页面数据不会发生改变因为`count `并没有和页面进行数据劫持
2. 如果此时先点击`addCount`**9次** 在点击`addProxyCount `页面会**显示为11**,因为`count `被`reactive `代理形成`proxyCount `,实际操作`proxyCount ` 也是从最新的`count `中获取,只是count 没有和页面形成响应式因此没变化
`new Proxy(count,{get(target,key){ ... },set(target, property, value, receiver){...}})`
3. 在`vue2 `版本,新增的`key `犹豫并没有被响应劫持因此需要使用特殊方式例如`set `将值塞回响应劫持对象中,在`vue3`更换`proxy `后就不必这样了
~~~html
<script >
import { reactive } from 'vue'
export default {
name: 'App',
setup() {
// const baseType = reactive('1') 基本类型的绑定是错误的
const count = { value: 1 }
// 值代理成响应式
const proxyCount = reactive(count)
// 深层代理
const deepProxy = reactive({})
// 操作被reactive 包裹具有响应式对象值
const addProxyCount = () => {
console.log(proxyCount)
++proxyCount.value
}
// 操作原值页面不会更新因为原始值并没有没有和页面进行数据劫持
const addCount = () => {
console.log(count.value)
++count.value
}
// 绑定深层次的属性
const deepAttr = ()=>{
const arr = ['foo','bar']
deepProxy.arr = arr
deepProxy.arr[0] = "zzz"
proxyCount
}
// reactive() 返回的是一个原始对象的 Proxy,它和原始对象是不相等的
const compare = ()=>{
console.log(count === proxyCount) // false
// 重新包裹依旧使用已存在的代理对象,在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(count) === proxyCount) // true
// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxyCount) === proxyCount) // true
}
return {
addCount,
addProxyCount,
proxyCount,
deepProxy,
deepAttr,
compare
}
},
}
</script>
<template>
<div>
<div>{{ proxyCount.value }}</div>
<div>{{deepProxy}}</div>
<button @click="deepAttr">deep</button>
<button @click="compare">compare</button>
<button @click="addProxyCount">addProxyCount</button>
<button @click="addCount">addCount</button>
</div>
</template>
~~~
>[info] ## ref 作为属性会自动解包
1. `reactiv `对深层的嵌套属性都会进行响应式代理,如果使用`ref` 包裹的值最后也是会成为一个含有`value `属性的对象,这类对象在`reactiv `属性中使用依旧如此保持响应性。但不同的是**深层地解包任何[ref](https://cn.vuejs.org/api/reactivity-core.html#ref)属性**
2. **当访问到某个响应式`数组`或`Map`这样的原生集合类型中的 `ref 元素时`,不会执行 ref 的解包**
[案例](https://sfc.vuejs.org/#eNp9VN2O0kAUfpXj3FASaGWvDIGNPgBhb7xiuCg4IEinzXTKrmma4IUaE1w02WyMP1kT467xwsQrdlcSX4bu4pWv4Jm2QIHGpGl7zjfn7/tmxicPHEcfeoyUScVti54jYZ/ynuXYQoIPgnUKgplt2RsyCKAjbAtyuDxHOeXsKFr1iHVMbyB9ygFcJj1Hy0f/AG2buxIcYR89hSrmivNofpBP41jjYLWko5UywXqrn+A+Ny1WpqRESXaemulAspazQ0BTazQoMbuMkgLG7VHSbOaTUMOA+eXrxcWXcPw8nczj656SthsqY66UyzfXseiCrGDHbD/BWC1f3U+4gJgHXRzEvUXpt6B6CsKBt9BaCsWhlqgqaQ+YPrC72rJE0mAmWP8fWFuBy68i6PePm5Or8M2xmjYczeZXZ/PZ+8X07PbTKJy8C8enf2fj8MPn+eV1zMX8+sXNq9H811ek9vbkmz40Bx6DxfE0nJxifAYhe9tM6EplBChBubaJ0HGbaTkUNKf0vEdJ0nWwoQsWV23uqKO0qaaVSfOQ6N6424y7zqtsJfjzcbQ4f7Z4+T2c/oz8GwUF7nrBV0LH8hc2ul5ZSwV3HCj4jg9lXqfBnKmq+MKH8ooRH1s8tWhIZjkDUzK0ACotT0qbw/32oIcjUxI3RkmEAjyMt+maoYoRR2RHb8RGkRsnZzP4TrG4uDgPJ2+Lxcjh+8uZggCMlIljK8/agTMHOFnFWM1CCiS+kIqW6eh91+Z4W0Vs0wRwKSlDwj8leD8pm5LHUjpu2TDcTlvdcX1Xt0XXwD9deFz2LKYz1yq2hH3oMoGJ452mmCXBP8TU03U=)
~~~html
<script >
import { ref,reactive } from 'vue'
export default{
setup(){
const proxy = reactive({})
const refProxy = ref(1)
const refProxyObj = ref({name:"1"})
const refProxyMap = ref(new Map([["age","12"]]))
// 不解包
const unProxy = reactive([ref('1')])
// ref 解包
const unpack = ()=>{
proxy.rP = refProxy
proxy.rO = refProxyObj
proxy.rM = refProxyMap
console.log(proxy.rP)
console.log(proxy.rO)
console.log(proxy.rM)
// 修改后ref 值也会跟着变化,因为解包了所以不用.value 获取值
proxy.rP = 2
proxy.rO.name = "2"
proxy.rM.set('age',"18")
}
// ref 不会解包
const pack=()=>{
console.log(unProxy[0].value) // 1 需要自己value 因为元素为ref
}
return{
unpack,
proxy,
refProxy,
refProxyObj,
refProxyMap,
pack
}
}
}
</script>
<template>
<button @click="unpack">
Unpack 解包
</button>
<button @click="pack">
pack 不解包
</button>
<!--触发-->
{{refProxy}} / {{refProxyObj}} /{{refProxyMap}}
</template>
~~~
>[info] ## 解构后会失去响应式
1. `reactive ` 在解构后会失去其响应式的功能 但不绝对
2. 如果是深层,此时获取深层中`key `对应`value `例如下面案例中,获取是`name `对应的`{ z: 'info' }",此时" { z: 'info' }`还是`reactive` 对象因此解构了还是具有响应式的
~~~ html
<template>
<!-- 可以响应监听的 -->
<div>{{ name.z }}</div>
</template>
<script>
import { isReactive, reactive } from 'vue'
export default {
name: 'App',
setup() {
// 此时这样去使用es6结构
let { name } = reactive({ name: { z: 'info' } })
// 在1s后改变值
setTimeout(() => {
name.z = 'w'
console.log(isReactive(name)) // true
}, 1000)
return { name }
},
}
</script>
~~~
>[info] ## 官网
[## reactive()](https://cn.vuejs.org/api/reactivity-core.html#reactive)
[## 声明响应式状态](https://cn.vuejs.org/guide/essentials/reactivity-fundamentals.html#declaring-reactive-state)
- 官网给的工具
- 声明vue2 和 vue3
- 指令速览
- Mustache -- 语法
- v-once -- 只渲染一次
- v-text -- 插入文本
- v-html -- 渲染html
- v-pre -- 显示原始的Mustache标签
- v-cloak -- 遮盖
- v-memo(新)-- 缓存指定值
- v-if/v-show -- 条件渲染
- v-for -- 循环
- v-bind -- 知识
- v-bind -- 修饰符
- v-on -- 点击事件
- v-model -- 双向绑定
- 其他基础知识速览
- 快速使用
- 常识知识点
- key -- 作用 (后续要更新)
- computed -- 计算属性
- watch -- 侦听
- 防抖和节流
- vue3 -- 生命周期
- vue-cli 和 vite 项目搭建方法
- vite -- 导入动态图片
- 组件
- 单文件组件 -- SFC
- 组件通信 -- porp
- 组件通信 -- $emit
- 组件通信 -- Provide / Inject
- 组件通信 -- 全局事件总线mitt库
- 插槽 -- slot
- 整体使用案例
- 动态组件 -- is
- keep-alive
- 分包 -- 异步组价
- mixin -- 混入
- v-model-- 组件
- 使用计算属性
- v-model -- 自定义修饰符
- Suspense -- 实验属性
- Teleport -- 指定挂载
- 组件实例 -- $ 属性
- Option API VS Composition API
- Setup -- 组合API 入口
- api -- reactive
- api -- ref
- 使用ref 和 reactive 场景
- api -- toRefs 和 toRef
- api -- readonly
- 判断性 -- API
- 功能性 -- API
- api -- computed
- api -- $ref 使用
- api -- 生命周期
- Provide 和 Inject
- watch
- watchEffect
- watch vs. watchEffect
- 简单使用composition Api
- 响应性语法糖
- css -- 功能
- 修改css -- :deep() 和 var
- Vue3.2 -- 语法
- ts -- vscode 配置
- attrs/emit/props/expose/slots -- 使用
- props -- defineProps
- props -- defineProps Ts
- emit -- defineEmits
- emit -- defineEmits Ts
- $ref -- defineExpose
- slots/attrs -- useSlots() 和 useAttrs()
- 自定义指令
- Vue -- 插件
- Vue2.x 和 Vue3.x 不同点
- $children -- 移除
- v-for 和 ref
- attribute 强制行为
- 按键修饰符
- v-if 和 v-for 优先级
- 组件使用 v-model -- 非兼容
- 组件
- h -- 函数
- jsx -- 编写
- Vue -- Router
- 了解路由和vue搭配
- vueRouter -- 简单实现
- 安装即使用
- 路由懒加载
- router-view
- router-link
- 路由匹配规则
- 404 页面配置
- 路由嵌套
- 路由组件传参
- 路由重定向和别名
- 路由跳转方法
- 命名路由
- 命名视图
- Composition API
- 路由守卫
- 路由元信息
- 路由其他方法 -- 添加/删除/获取
- 服务器配置映射
- 其他
- Vuex -- 状态管理
- Option Api -- VUEX
- composition API -- VUEX
- module -- VUEX
- 刷新后vuex 数据同步
- 小技巧
- Pinia -- 状态管理
- 开始使用
- pinia -- state
- pinia -- getter
- pinia -- action
- pinia -- 插件 ??
- Vue 源码解读
- 开发感悟
- 练手项目