🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] >[success] # ref,reactive 响应式引用的用法和原理 **ref** 与 **reactive** 是 **vue3** 提供给我们 **解决数据响应式问题的方法**,下面我们将通过 **2** 个例子来讲解 **ref** 与 **reactive** 以及 **readonly** 、**toRefs** 具体用法。 1. **ref** :解决 **基本数据类型数据(基本类型:Boolean、String等等)** 的 **数据响应式问题** 2. **reactive** :解决 **非基本数据类型数据(引用类型:Array、Object等等)** 的 **数据响应式问题** 3. **readonly** :数据 **只读** 。 4. **toRefs** : 解决 **解构赋值** 的 **数据响应式问题**。 >[success] ## ref **需求** :我们在 **setup** 中定义了变量 **name** , 并且将 **name** 返回 **return** 出去,在页面的 **template 模板** 中使用 **name**, **2s 之后更改了变量 name** , 我们 **希望 template 模板上的 name 也随之更新** , 代码如下: 1. **非响应式数据案例** **index.html** ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ref 响应式引用的用法和原理</title> <!-- 通过cdn方式引入vue --> <script src="https://unpkg.com/vue@next"></script> </head> <body> <div id="root"></div> </body> <script> const app = Vue.createApp({ template: ` <div>{{name}}</div> `, /** * created 实例被完全初始化之前 * @param {object} props - 当前组件的props * @param {number} contex - 上下文 */ setup(props, contex) { // 声明定义变量name let name = 'dell' // 2s 后更改 name setTimeout(() => { name = 'lee' }, 2000); return { name // data中的变量 } } }) const vm = app.mount('#root') </script> </html> ~~~ 2. **ref 响应式数据案例** 上面的数据,无法更新,这是 **因为 name 只是一个普通的变量并不会响应式的更新** ,如果想 **响应式的更新 name** ,使用 **ref** 可以解决 **基本数据类型数据** 的 **数据响应式问题** 。 **index.html** ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ref 响应式引用的用法和原理</title> <!-- 通过cdn方式引入vue --> <script src="https://unpkg.com/vue@next"></script> </head> <body> <div id="root"></div> </body> <script> // ref 响应式的引用 // 原理, 通过 proxy 对数据进行封装, 当数据变化时, 触发模板等内容的更新 // ref 处理基础类型的数据 const app = Vue.createApp({ template: ` <div>{{name}}</div> `, /** * created 实例被完全初始化之前 * @param {object} props - 当前组件的props * @param {number} contex - 上下文 */ setup(props, contex) { // 引入 composition API 的 ref const { ref } = Vue // 调用 ref 后,proxy, 'dell' 变成 proxy({value: 'dell'}) 这样的一个响应式引用 let name = ref('dell') // 值会存入到 name.value 中 // 2s 后更改 name setTimeout(() => { name.value = 'lee' // 修改 name.value 的值 }, 2000); return { name // data中的变量 } } }) const vm = app.mount('#root') </script> </html> ~~~ **template 模板** 中不需要写 **name.value** ,直接写 **name** 就行,因为 **vue** 在 **做模板处理时,它会做一个转换,它如果知道 name 是通过 ref 返回的一个响应式引用,会自动的在底层帮我们调用 name.value** >[success] ## reactive 下面的代码是 **非基本数据类型数据** 的案例,也是 **无法做到数据的响应式** 。 1. **非响应式数据案例** **index.html** ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>reactive 响应式引用的用法和原理</title> <!-- 通过cdn方式引入vue --> <script src="https://unpkg.com/vue@next"></script> </head> <body> <div id="root"></div> </body> <script> const app = Vue.createApp({ template: ` <div>{{nameObj.name}}</div> `, /** * created 实例被完全初始化之前 * @param {object} props - 当前组件的props * @param {number} contex - 上下文 */ setup(props, contex) { // 定义声明对象 const nameObj = { name: 'dell' } // 2s 后更改 nameObj setTimeout(() => { nameObj.name = 'lee' }, 2000); return { nameObj // data中的变量 } } }) const vm = app.mount('#root') </script> </html> ~~~ 2. **reactive 响应式数据案例** **reactive** 可以解决 **非基本数据类型数据** 的 **数据响应式问题** 。 **index.html** ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>reactive 响应式引用的用法和原理</title> <!-- 通过cdn方式引入vue --> <script src="https://unpkg.com/vue@next"></script> </head> <body> <div id="root"></div> </body> <script> // reactive 响应式的引用 // 原理, 通过 proxy 对数据进行封装, 当数据变化时, 触发模板等内容的更新 // reactive 处理非基础类型的数据 const app = Vue.createApp({ template: ` <div>{{nameObj.name}}</div> `, /** * created 实例被完全初始化之前 * @param {object} props - 当前组件的props * @param {number} contex - 上下文 */ setup(props, contex) { // 引入 composition API 的 reactive const { reactive } = Vue // 调用 reactive 后,proxy, { name: 'dell' } 变成 proxy({ name: 'dell' }) 这样的一个响应式引用 const nameObj = reactive({ name: 'dell' }) // 2s 后更改 nameObj setTimeout(() => { nameObj.name = 'lee' }, 2000); return { nameObj // data中的变量 } } }) const vm = app.mount('#root') </script> </html> ~~~ 上面是数据是 **对象** ,也可以使用 **数组** ,如下: ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>reactive 响应式引用的用法和原理</title> <!-- 通过cdn方式引入vue --> <script src="https://unpkg.com/vue@next"></script> </head> <body> <div id="root"></div> </body> <script> // reactive 响应式的引用 // 原理, 通过 proxy 对数据进行封装, 当数据变化时, 触发模板等内容的更新 // reactive 处理非基础类型的数据 const app = Vue.createApp({ template: ` <div>{{nameObj[0]}}</div> `, /** * created 实例被完全初始化之前 * @param {object} props - 当前组件的props * @param {number} contex - 上下文 */ setup(props, contex) { // 引入 composition API 的 reactive const { reactive } = Vue const nameObj = reactive([123]) // 2s 后更改 nameObj setTimeout(() => { nameObj[0] = 456 }, 2000); return { nameObj // data中的变量 } } }) const vm = app.mount('#root') </script> </html> ~~~ >[success] ## readonly 假如我想让部分数据 **不希望,数据被变更** ,变成 **只读状态** , 我们可以使用 **vue3** 提供的 **readonly** 实现这个功能,代码如下: **index.html** ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>readonly 的用法和原理</title> <!-- 通过cdn方式引入vue --> <script src="https://unpkg.com/vue@next"></script> </head> <body> <div id="root"></div> </body> <script> // ref, reactive 响应式的引用 // 原理, 通过 proxy 对数据进行封装, 当数据变化时, 触发模板等内容的更新 // reactive 处理非基础类型的数据 const app = Vue.createApp({ template: ` <div>{{nameObj[0]}}</div> `, /** * created 实例被完全初始化之前 * @param {object} props - 当前组件的props * @param {number} contex - 上下文 */ setup(props, contex) { // 引入 composition API 的 reactive const { reactive, readonly } = Vue const nameObj = reactive([123]) // 复制一份数据 const copyNameObj = readonly(nameObj) // 2s 后更改 nameObj 与copyNameObj setTimeout(() => { nameObj[0] = 456 copyNameObj[0] = 456 }, 2000); return { nameObj, // data中的变量 copyNameObj } } }) const vm = app.mount('#root') </script> </html> ~~~ 这样 **2s** 过后,浏览器控制台就会 **抛出警告** 。 >[success] ## toRefs 下面代码中在 **setup 函数** 中 **return** 一个对象 **index.html** ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>toRefs 的用法和原理</title> <!-- 通过cdn方式引入vue --> <script src="https://unpkg.com/vue@next"></script> </head> <body> <div id="root"></div> </body> <script> const app = Vue.createApp({ template: ` <div>{{nameObj.name}}</div> `, /** * created 实例被完全初始化之前 * @param {object} props - 当前组件的props * @param {number} contex - 上下文 */ setup(props, contex) { // 引入 composition API 的 reactive const { reactive } = Vue // 调用 reactive 后,proxy, { name: 'dell' } 变成 proxy({ name: 'dell' }) 这样的一个响应式引用 const nameObj = reactive({ name: 'dell' }) // 2s 后更改 nameObj setTimeout(() => { nameObj.name = 'lee' }, 2000); return { nameObj // data中的变量 } } }) const vm = app.mount('#root') </script> </html> ~~~ 有些人就会觉得这么写很麻烦,用 **ES6 解构赋值** 直接 **return** 一个 **name** ,像下面这样写: ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>toRefs 的用法和原理</title> <!-- 通过cdn方式引入vue --> <script src="https://unpkg.com/vue@next"></script> </head> <body> <div id="root"></div> </body> <script> const app = Vue.createApp({ template: ` <div>{{name}}</div> `, /** * created 实例被完全初始化之前 * @param {object} props - 当前组件的props * @param {number} contex - 上下文 */ setup(props, contex) { // 引入 composition API 的 reactive const { reactive } = Vue // 调用 reactive 后,proxy, { name: 'dell' } 变成 proxy({ name: 'dell' }) 这样的一个响应式引用 const nameObj = reactive({ name: 'dell' }) // 2s 后更改 nameObj setTimeout(() => { nameObj.name = 'lee' }, 2000); // 解构赋值 const { name } = nameObj // data中的变量 return { name } } }) const vm = app.mount('#root') </script> </html> ~~~ **2s 后就会发现不生效** , **解构赋值** 并没有生效,因为 **解构只是把这个值导出去,这个值并不是响应式的** ,如果想做**解构赋值** ,解决这个问题,需要使用 **toRefs** 才可以解决这个问题,代码如下: ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>toRefs 的用法和原理</title> <!-- 通过cdn方式引入vue --> <script src="https://unpkg.com/vue@next"></script> </head> <body> <div id="root"></div> </body> <script> const app = Vue.createApp({ template: ` <div>{{name}}</div> `, /** * created 实例被完全初始化之前 * @param {object} props - 当前组件的props * @param {number} contex - 上下文 */ setup(props, contex) { // 引入 composition API 的 reactive const { reactive, toRefs } = Vue // 调用 reactive 后,proxy, { name: 'dell' } 变成 proxy({ name: 'dell' }) 这样的一个响应式引用 const nameObj = reactive({ name: 'dell' }) // 2s 后更改 nameObj setTimeout(() => { nameObj.name = 'lee' }, 2000); // 解构赋值 // 调用 toRefs 后,它会把 proxy({ name: 'dell' }) 转换成 { name: proxy({value:'dell'}) } // 实际上最终返回的是 { name: proxy({value:'dell'}) } const { name } = toRefs(nameObj) // data中的变量 return { name } } }) const vm = app.mount('#root') </script> </html> ~~~ >[success] ## 总结 当我们学了 **ref** 、 **reactive** 这种新的 **composition API** 之后,我们完全 **可以替代老的语法中 data 这种写法**,比如说以前 **定义** 都在 **data 中定义变量** ,如下: ~~~ data() { return { name: 'dell', nameObj: { name: 'dell' } } } ~~~ 用新的 **composition API 语法来定义的话,完全没必要用 data** ,用 **ref** 、 **reactive** 这种形式来写即可。