>[success] # 响应性API ~~~ 1.目前'setup' 的返回对象参数可以直接渲染到模板上,问题是数据不是响应式,想让数据变成响应式 需要使用'3.x' 提供的响应式API ~~~ >[info] ## Ref ~~~ 1.接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property .value 来理解官方这句话'ref() 返回的是value reference (包装对象)并且这个对象value属性指向包装的值让其具备了响应' 2.注意第一条说的是'value' 属性指向了包装对象并且是value具备了响应 2.1.数据操作的是使用是'xxx.value' 2.2.但在模板中简化了操作流程因此可以直接'xxx'不需要调用'value' 属性 3.一般定义基本类型的响应事数据使用'ref' 常见的基本类型'String','Number','BigInt','Boolean','Symbol','Null' 'Undefined' 4.如果你绑定的是对象非上面提出的基本类型他将会调用'reactive ',此时'.value'返回的是一个'proxy' 对象, 关于这一点稍后会详细解释,现在简单看一下如果"const per = ref({name:'z'})" 包裹的是对象相对的你的修改 你需要'per.value.name = "zz" '依然要通过'value' 并且此时'per.value' 是一个'prxoy'对象 5.注意下面案例中其实我们使用const定义参数也间接想表达'ref'包裹的基础类型已经是一个对象(是一个ref对象) ~~~ >[danger] ##### 案例 ~~~ <template> <div > <button @click="print">触发事件</button> {{str}} <input v-model="str" /> </div> </template> <script> import {ref,reactive} from 'vue' export default { name: 'testlison', setup(prop, ctx) { const str = ref('z') const num = ref(1) const boolean = ref(false) const ull = ref(null) const un = ref(undefined) const print = ()=>{ str.value = "q" num.value = 100 boolean.value = true ull.value = {name:'zz'} un.value = 1000 } return { str,print} }, ~~~ >[danger] ##### 是所有情况下ref 在模板渲染时候都不用.value么 ~~~ 1.答案是否定的,只有当'ref'包裹的对象是直接从setup返回的可以省略'value',但是如果是嵌套的依然 需要你在模板中使用value属性 ~~~ ~~~html <template> <div> <span>{{ count }}</span> <button @click="count ++">Increment count</button> <!-- 因为count ref对象被二次包裹在nested中因省略value 响应改变是不生效 --> <!-- <button @click="nested.count ++">Nested Increment count</button> --> <!-- 正确写法 --> <button @click="nested.count.value ++">Nested Increment count</button> </div> </template> <script> import { ref } from 'vue' export default { setup() { const count = ref(0) return { count, nested: { count } } } } </script> ~~~ >[info] ## reactive ~~~ 1.'reactive' 返回对象的响应式副本,并且他响应转换是'深层'的它影响所有嵌套 property,基于'Proxy'实现, 和 '2.x' 的 Vue.observable()等同 2.解释上面官方的话 2.1.定义对象 时候要使用我并且会返回一个'proxy'对象这个对象具有响应式 2.2.'深层'就是这个对象内部嵌套多深所有嵌套的属性都能影响到举个例子 "const obj =reactive( {name:'ww',per:{age:1100}})" 从obj的name属性到per甚至你的per.age属性都能被我代理了 并且无论你是delete删除还是说新增新的属性都可以被响应到,'reactive'的返回的'proxy'对象可以被响应,如果你操 作原对象不享受这个待遇 2.3.内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的 3.现在可以在进一步理解'ref' 如果传值是对象内部会调用'reactive' 这一点了(注意说的是ref对对象会内部自动调用'reactive') 4.当你从组件中的data()返回一个对象时,它是由reactive()在内部做出反应的 ~~~ >[danger] ##### 案例 ~~~ <template> <div > <button @click="print">触发事件</button> {{proxyObj.count}} {{proxyObj.name}} </div> </template> <script> import {ref,reactive} from 'vue' export default { name: 'testlison', setup(prop, ctx) { // 要注意了你直接操作obj 是没有响应效果 const obj = {count:0} // 你需操作我我是响应的 const proxyObj = reactive(obj) const print = ()=>{ proxyObj.count ++ // 感动了吧以前你还需要$set 现在不用了 proxyObj.name = "ww" } return { proxyObj,print} }, } </script> <style scoped lang="less"></style> ~~~ >[danger] reactive 包裹一个对象包含ref的对象 -- Ref Unwrapping(包装对象的自动展开) ~~~ 1.当一个ref作为一个响应式对象的属性被访问或改变时,它会自动打开内部值,这样它的行为就像一个普通的属性 简单的说'当一个包装对象被作为另一个响应式对象的(属性)引用的时候也会被自动展开' 2.理解官方这句话就是 现在我定义了一个ref对象,'const count =ref(0)' =>我将ref对象做一个对象的属性 'const obj = {count}'=>此时我使用是'obj.count.value' 但是如果我用reactive 包裹着含有ref属性的对象即 'const state = reactive(obj)' 他会自动将这个value给打开使用效果变成'state.count'不用再加value属性了 在进一步抽象理解当还有ref属性的对象被reactive包裹实际上效果是'const state = reactive(count:count.value)' const count = ref(0) const obj = reactive({ count }) console.log(obj.count) // 0 obj.count++ console.log(obj.count) // 1 console.log(count.value) // 1 count.value++ console.log(obj.count) // 2 console.log(count.value) // 2 ~~~ ~~~ <template> <div> <!-- count 和 rcount.count 同时都改变了 --> <span>{{ count }}--{{rcount.count}}</span> <button @click="print">Increment count</button> </div> </template> <script> import { ref ,reactive} from 'vue' export default { setup() { const count = ref(0) // 这里是{count}用一个对象吧包裹了ref // 注意不是直接一个reactive 包裹count 错误写法reactiv(count) const rcount = reactive({count}) const print=() =>{ rcount.count ++ } return { rcount, count, print } } } </script> ~~~ >[danger] ##### 非对象中的属性包含ref对象并且用reactive包裹 ~~~ 1.'当一个包装对象被作为另一个响应式对象的(属性)引用的时候也会被自动展开' 但是当从数组或本地集合类型(如 Map)访问ref时,不会执行展开操作 ~~~ ~~~ const books = reactive([ref('Vue 3 Guide')]) // need .value here 这里依旧需要value console.log(books[0].value) const map = reactive(new Map([['count', ref(0)]])) // need .value here 这里依旧需要value console.log(map.get('count').value) ~~~ >[danger] ##### 上面 reactive 包裹 ref 情况过于复杂我该怎么做 ~~~ 1.以上这些关于包装对象的细节可能会让你觉得有些复杂,但实际使用中你只需要记住一个基本的规则: 只有当你直接以变量的形式引用一个包装对象的时候才会需要用 .value 去取它内部的值 —— 在模版中你甚至 不需要知道它们的存在。 ~~~ >[info] ## 现在对比reactive与ref-细节 ~~~ 1.ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式) 2.如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象 3.ref内部: 通过给value属性添加getter/setter来实现对数据的劫持 4.reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据 5.ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value) ~~~ >[info] ## ref 和 reactive 设计出现 ~~~ 1.我们知道在 JavaScript 中,原始值类型如 string 和 number 是只有值,没有引用的。如果在一个函数中返 回一个字符串变量,接收到这个字符串的代码只会获得一个值,是无法追踪原始变量后续的变化的 ~~~ >[danger] ##### 我应该什么时候去使用这两个Api ~~~ 1.最简单就是基本类型用'ref' ,引用类型用'reactive' ,并且这也是依赖于你的编程习惯,在定义变量的时候 是属于喜欢单独每一项定义还是使用用对象定义 ~~~ * 举个例子单独每一项定义 ~~~ import { ref, onMounted, onUnmounted } from "vue"; export function useMousePosition() { const x = ref(0); const y = ref(0); function update(e) { x.value = e.pageX; y.value = e.pageY; } onMounted(() => { window.addEventListener("mousemove", update); }); onUnmounted(() => { window.removeEventListener("mousemove", update); }); return { x, y }; } ~~~ * 对象定义 ~~~ function useMousePosition() { const pos = reactive({ x: 0, y: 0 }); // ... return toRefs(pos); } ~~~ [参考文章链接](https://www.danvega.dev/blog/2020/02/12/vue3-ref-vs-reactive/)