Vue——vue3中的ref和reactive数据理解以及父子组件之间props传递的数据

ref()函数

这是一个用来接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value

作用:创建一个响应式变量,使得某个变量在发生改变时可以同步发生在页面上。

模板语句中使用这个变量时可以直接使用变量名来调用,在setup内部调用时则需要在变量明后面加上一个.value获取它的值,原因是因为使用ref()返回的是一个RefImpl对象类型。

RefImpl

RefImplRefImpl是用于实现ref的内部类。它用于包装单个基本数据类型值,并提供.value属性来访问和更新值。当你使用ref函数创建一个响应式引用时,实际上是在内部创建了一个RefImpl实例。

当在控制台上输出一个ref对象时,输出的就是refImpl对象。

RefImpl用于包装单个值,也可以用来包装一个对象,包装一个对象时,这个ref.value得到的会是一个响应式proxy代理对象。

reactive()函数

作用:创建一个响应式的对象,不可重新赋值,使用该函数创建的对象也是可以像ref()的一样实现响应式的变化,但是不管是模板语句里面还是setup内部都可以像普通的对象一样调用,但是使用reactive返回的同样是一个Proxy对象类型的数据。如下所示

ObjectRefImpl

ObjectRefImplObjectRefImpl是用于实现reactive的内部类。它用于包装一个对象,并为该对象的每个属性创建响应式代理 。当你使用reactive函数创建一个响应式对象时,实际上是在内部创建了一个ObjectRefImpl实例。

为什么说reactive创建的对象不可重新赋值

reactive 创建的对象可以重新赋值,但不能被重新赋值为一个完全不同的对象。这是因为 reactive 创建的对象是响应式的,它会追踪其属性的变化,并在属性发生变化时触发视图更新。如果你重新赋值整个对象,那么 Vue 将无法继续追踪原始对象的变化,因为它不再引用相同的对象。

下面是一个示例,说明 reactive 创建的对象可以重新赋值:

javascript 复制代码
import { reactive } from 'vue';

const state = reactive({
  name: 'John',
  age: 30,
});

console.log(state.name); // 输出 'John'

// 可以重新赋值属性值
state.name = 'Alice';

console.log(state.name); // 输出 'Alice'

在上述示例中,我们可以看到我们成功地重新赋值了 state 对象的 name 属性,这是因为我们仅仅修改了属性值,而不是整个对象。

然而,如果尝试将 state 对象重新赋值为一个全新的对象,例如:

javascript 复制代码
state = reactive({ name: 'Bob', age: 25 }); // 这是不允许的

这将导致一个错误,因为这样做相当于放弃了对原始 state 对象的引用,Vue 将无法继续追踪原始对象的属性变化。如果需要更改整个对象,你应该使用 refcomputed 来处理,而不是 reactive

Proxy对象

Proxy 对象是 JavaScript 中的一个内置对象,它允许你创建一个代理(proxy),用于控制对另一个对象的访问和操作。这个代理对象可以拦截和自定义目标对象的各种操作,例如属性的读取、写入、删除等,从而提供了更高级的元编程能力和数据保护机制。

Proxy 对象的基本语法如下:

javascript 复制代码
const proxy = new Proxy(target, handler);
  • `target`:被代理的目标对象,即你想要拦截操作的对象。

  • `handler`:一个包含各种拦截操作的处理器对象,它定义了代理对象的行为。例如,你可以使用 Proxy 来创建一个简单的日志记录器,以监视目标对象的属性访问和修改:

javascript 复制代码
const target = {
  name: 'John',
  age: 30,
};

const handler = {
  get(target, key) {
    console.log(`Getting ${key}`);
    return target[key];
  },
  set(target, key, value) {
    console.log(`Setting ${key} to ${value}`);
    target[key] = value;
  },
};

const proxy = new Proxy(target, handler);

console.log(proxy.name); // 会触发代理的get拦截,打印 "Getting name",然后返回 "John"
proxy.age = 31; // 会触发代理的set拦截,打印 "Setting age to 31"

Proxy 的强大之处在于你可以定义自定义行为来拦截目标对象上的各种操作。这使得它在元编程、数据验证、数据保护和许多其他场景中非常有用。

需要注意的是,Proxy 是 ECMAScript 6 的一部分,因此在一些老版本的浏览器中可能不受支持。但现代浏览器和 Node.js 环境中通常都支持 Proxy。

toRefs方法

toRefs是Vue 3中一个有用的辅助函数,用于将响应式对象的属性转换为普通的响应式引用(ref)。这对于在模板中访问响应式对象的属性以及将它们传递给子组件时非常有用。

下面是toRefs的使用示例:

假设你有一个响应式对象(reactive):

javascript 复制代码
import { reactive, toRefs } from 'vue';

const state = reactive({
  name: 'John',
  age: 30,
});

使用toRefs将这个响应式对象的属性转换为响应式引用:

java 复制代码
const stateRefs = toRefs(state);

现在,stateRefs是一个包含了nameage属性的对象,但这些属性已经被转换为响应式引用。你可以像访问普通的引用一样访问它们的值:

javascript 复制代码
console.log(stateRefs.name.value); // 'John'
console.log(stateRefs.age.value); // 30

在模板中使用时,你可以直接使用stateRefs.namestateRefs.age,Vue会自动解开引用并进行响应式追踪。

使用toRefs的主要优点是,在将响应式对象的属性传递给子组件时,子组件可以直接使用属性的引用,而不需要手动解包。这可以避免一些潜在的问题,例如丢失响应性。

ref和reactive得到的不同响应式对象

ref

  • ref 用于创建响应式引用,它返回一个包装对象,这个包装对象具有一个名为 .value 的属性,该属性用于读取和写入值。
  • 内部实现上,ref 创建了一个名为 RefImpl 的对象,它是 Vue 3 内部的私有类,用于包装值并添加响应性。
  • 你可以通过 ref 创建基本数据类型的响应式数据,例如数字、字符串等。
javascript 复制代码
import { ref } from 'vue';

const count = ref(0); // 返回一个 RefImpl 对象
console.log(count.value); // 0
count.value = 1; // 更新值

reactive

  • reactive 用于创建一个响应式代理对象,它会追踪对象内部属性的变化。
  • 内部实现上,reactive 使用了 JavaScript 的 Proxy 对象,将目标对象包装在代理之下,从而实现响应性。
  • 你可以通过 reactive 创建包含多个属性的响应式对象,通常用于复杂的数据结构。
javascript 复制代码
import { reactive } from 'vue';

const state = reactive({
  name: 'John',
  age: 30,
}); // 返回一个 Proxy 对象
console.log(state.name); // John
state.age = 31; // 视图会自动更新

reactive对象使用toRefs时发生了什么?

使用 toRefs 将一个 reactive 对象的属性转换为响应式引用(ref)时,会将原始对象的每个属性都包装成独立的 ref,从而使这些属性可以在模板中正常工作并保持响应性。但是这些属性会变成**ObjectRefImpl类型。**

以下是使用 toRefs 的示例:

javascript 复制代码
import { reactive, toRefs } from 'vue';

const state = reactive({
  name: 'John',
  age: 30,
});

const stateRefs = toRefs(state);

console.log(stateRefs.name.value); // 输出 'John'
console.log(stateRefs.age.value); // 输出 30

在上面的示例中,toRefs 函数将 state 对象的每个属性都包装为 ref,并将这些 ref 存储在 stateRefs 对象中。现在,你可以像访问普通的 ref 一样访问这些属性,而不需要 .value

toRefs 的主要作用是确保 reactive 对象的属性在模板中可以正确追踪和更新,因为模板编译器可以正确处理 ref,而 ref 具有 .value 属性,使得属性的访问和修改都能够正常工作。这对于将属性传递给子组件或在模板中使用响应式数据非常有用,因为它确保了属性的正确响应性行为。

需要注意的是,toRefs 创建的 ref 仍然是响应式的,但是它们只包装了原始属性的值,而不是整个对象。这意味着你只能访问和修改属性的值,而不能修改整个对象或添加新的属性。如果需要修改整个对象,你应该使用 reactive 创建一个新的代理对象。

为什么reactive不能用.value访问

reactive 创建的对象不能直接使用 .value 来访问属性的值,因为 reactive 返回的对象是一个代理对象(Proxy),不同于使用 ref 创建的包装对象。

使用 reactive 创建的对象是一个代理,它会在访问和修改属性时自动进行响应式追踪,而不需要额外的 .value 属性。因此,你可以像访问普通 JavaScript 对象一样访问 reactive 对象的属性,而不需要额外的 .value。这正是 Vue 3 中的响应式系统的设计理念之一,使代码更加简洁和自然。

例子

Vue------vue3+element plus实现多选表格使用ajax发送id数组_北岭山脚鼠鼠的博客-CSDN博客

继上次之后再次实现了一个弹出式的编辑框,然后要将某一行tableItem数据传送给子组件进行展示.

然后这里就要用到props。

效果如下

控制台输出了一些数据

这是父组件里面的输出。

除了传进来的表格的行数据以外,又准备了一个reactive包装的对象rea和一个ref包装的对象a和一个ref类型的tableitem接收表格的行数据。一共三个。

javascript 复制代码
const tableItem = ref()

let rea = reactive({ name: "yhy", age: 23 })

const a = ref({ name: 'John', age: 30 });

  var onEdit = (data) => {
            tableItem.value = data //发送当前行数据给组件
            console.log(data)   //输出了一个Proxy数据

            console.log("tableItem部分---------------------Ref------------------------------------------------------------------------")
            console.log(tableItem) //输出了一个RefImpl数据
            console.log(tableItem.value)
            console.log(tableItem.value.shopname) //
            console.log("rea部分------------------Reactive---------------------------------------------------------------------------")
            console.log(rea)
            console.log(rea.name)
            console.log(toRefs(rea))
            console.log(toRefs(rea).name)
            console.log("a部分---------------------Ref------------------------------------------------------------------------")
            console.log(a);
            console.log(a.value);
            console.log(a.value.name);


            //console.log(tableItem.value.get(name))  //会提示不是一个对象
            //console.log(tableItem.get(name))      //会提示console.log(tableItem.value.get(name))
            showDialog.value = true         //显示表单
            dialogFormVisible.value = true  //显示弹窗
        }

输出如下

103行是得到的表格数据,可以看见是Proxy类型的对象,说明和reactive创建的一样的,都是响应式的对象。

tableItem

106得到的是RefImpl数据,这个正是ref返回得到

107得到的是将表格数据(proxy或者说reactive)赋给ref.value之后得到的,虽说ref一般都是用来封装数值,但是像这样封装对象也是可以的。

108就是ref.value.shopname得到的,因为ref.value是reactive类型的对象所以可以直接用.属性名的方式得到。

rea

110和111一个是reactive的直接输出,一个是其属性的输出。

112是使用了toRefs将一个响应式对象(proxy)变成了普通对象,将其属性变成了ObjectRefImpl类似ref那样。

113是输出了这个转换后的普通对象的属性,可以看见是ObjectRefImpl类型,这时可以使用value访问它的值。

a部分

和tableItem差不多,都是封装了一个对象进了value,但是这里那个对象使用了Proxy代理对象。

子组件里面的输出

javascript 复制代码
    props: {
        tableItem: {
        },
    },
    setup(props) {

        const tableItem2 = ref();
        const temp = ref();
        tableItem2.value = toRefs(props).tableItem.value

        console.log("1--------")
        console.log(props);       //props是一个proxy代理对象
        console.log("2--------")
        console.log(props.tableItem)  //里面包着的tableItem也是一个代理对象
        console.log("3--------")
        //console.log(props.tableItem.value)   
        console.log(props.tableItem.shopname)  //Proxy代理对象不需要用value,可以直接访问
        console.log("4--------")
        temp.value = toRefs(props.tableItem)  //使用一个ref接受tableItem这个reactive创建的proxy对象然后将里面的属性全部变成了拥有ObjectRefImpl类型
        console.log(temp)
        console.log(temp.value.shopname)        //为什么这里ObjectRefImpl不需要用value
        console.log(temp.value.shopname.value)  //shopname是ObjectRefImpl类型,但是.value输出undefined
        console.log("5--------")
        console.log(toRefs(props).tableItem)   //tableItem变成了ObjectRefImpl类型,但是value还是proxy类型
        console.log("6--------")
        console.log(toRefs(props).tableItem.value)  //这里tableItem是ObjectRefImpl类型,用.value输出了proxy类型
        console.log(toRefs(props).tableItem.value.shopname) //
}

1下可以看见传进来的props也是一个proxy代理对象,要用到的数据在里面也是一个对象的形式

2里输出了传来的tableItem(reactive) 3里直接输出了里面的属性

4里先是用toRefs将reactive对象变成普通对象(属性变成了ObjectRefImpl)封装进一个ref对象temp.value里面,然后又成了proxy代理对象,但里面的6个属性还是ObjectRefImpl类型的

然后之前说过这里ObjectRefImpl和ref一样需要用value访问数据,这里却不用,并且用了也访问不到.........

5里面使用toRefs将props变成了普通对象,并将其下的tableItem变成了ObjectRefImpl类型,但是tableItem.value是proxy代理对象,其下的6个属性类型没变。

所以6里面可以用.value输出一个proxy类型,用.value.shopname输出一个沙县小吃。

特例

那么toRefs 修改后的数据的属性变成了ObjectRefImpl类型了,但是如果属性是一个对象时,并且这个对象里面里面还包含了多个属性时要怎么访问这个对象里面的多个属性?

当使用 toRefs 修改后的数据的属性是一个对象,并且这个对象里面包含了多个属性时,你可以直接使用 .value 访问该属性,然后再使用点符号或中括号符号来访问该对象内部的多个属性。

下面是一个示例,说明如何访问 toRefs 修改后的数据对象内部的多个属性

javascript 复制代码
import { reactive, toRefs } from 'vue';

const state = reactive({
  person: {
    name: 'John',
    age: 30,
  },
  city: 'New York',
});

const stateRefs = toRefs(state);

// 访问对象属性
console.log(stateRefs.person.value.name); // 输出 'John'
console.log(stateRefs.person.value.age); // 输出 30

console.log(stateRefs.city.value); // 输出 'New York'

在上述示例中,我们首先使用 .value 访问 personcity 这两个属性,然后再使用点符号或中括号符号访问这些属性内部的属性。这样可以访问 person 对象内的 nameage 属性以及 city 属性。

总之,使用 toRefs 修改后的数据对象的属性仍然需要使用 .value 来访问,然后再使用标准的属性访问语法来访问对象内部的多个属性。这样可以访问和操作对象内部的数据。

父组件代码

html 复制代码
<template >
    <div id="shoplist">
        <el-table ref="multipleTableRef" :data="data.arr" style="width: 100%" height="90%" stripe
            @selection-change="handleSelectionChange">
            <template #default>
                <el-table-column type="selection" width="40" />
                <el-table-column property="shopname" label="店名" width="120" show-overflow-tooltip />
                <el-table-column property="score" label="评分" sortable width="80" />
                <el-table-column property="sales" label="销量" sortable width="80" />
                <el-table-column property="type" label="类型" width="70" />
                <el-table-column property="operations" label="操作" width="70">
                    <template #default="scope"> <el-button link type="primary" @click="onEdit(scope.row)"
                            size="small">Edit</el-button>
                    </template>
                </el-table-column>
            </template>
        </el-table>

        <div style="margin-top: 20px; margin-left:20px">
            <el-button @click="toggleSelection(data.arr)">全选</el-button>
            <el-button @click="toggleSelection()">清除</el-button>
            <el-button @click="delete_post">批量删除</el-button>
        </div>
    </div>
    <el-dialog v-model="dialogFormVisible" title="Shipping address">
        <dialog-component v-if="showDialog" :showDialog="showDialog" :tableItem="tableItem"></dialog-component>
    </el-dialog>
</template>
  
<script>
import { onMounted, ref } from 'vue';
import { getCurrentInstance } from 'vue'
import { reactive, toRefs } from '@vue/reactivity';
import $ from 'jquery'
import DialogComponent from '../components/DialogComponent.vue';
export default {
    name: 'ElementView',
    components: {
        DialogComponent
    },
    setup() {
        const instance = getCurrentInstance(); //这个玩意不能用在生产环境好像
        const multipleTableRef = ref(null);
        const multipleSelection = ref([])
        const data2 = ref([])
        const list = reactive([])
        const tableItem = ref({ name: 'yhy' })
        const dialogFormVisible = ref(false)
        const showDialog = ref()
        let rea = reactive({ name: "yhy", age: 23 })
        const a = ref({ name: 'John', age: 30 });
        var toggleSelection = (rows) => {
            console.log(instance) //输出了这个vue组件的实例对象
            console.log(instance.refs.multipleTableRef)  //输出了一个代理对象
            var ultipleTabInstance = toRefs(instance.refs.multipleTableRef)//将代理对象转换为了普通对象,不转会报错
            console.log(ultipleTabInstance);  //输出了一个普通对象
            if (rows) {
                rows.forEach(row => {
                    ultipleTabInstance.toggleRowSelection.value(row, undefined)
                    console.log(row)
                });
            } else {
                ultipleTabInstance.clearSelection.value()
            }
        }
        //备用方案
        onMounted(() => {
            // console.log(multipleTableRef);
        })
        //该方法用于将表格数据赋给ref变量
        var handleSelectionChange = (val) => {
            console.log(val)
            multipleSelection.value = val;
        }

        //此处是实现删除逻辑的方法
        var delete_post = () => {
            data2.value = multipleSelection.value
            console.log(data2.value)
            data2.value.forEach(a => {
                console.log(a.id)
                list.unshift(a.id)
            })
            console.log(list)
            //将该id数组传到后端进行批量删除
            $.ajax({
                url: "http://127.0.0.1:4000/posts/add2",
                type: "POST",
                headers: {
                    'Content-Type': 'application/json;charset=utf-8',
                },
                data: JSON.stringify({ "content": list })
                , success(resp) {
                    if (resp === "success") {
                        console.log("caa")
                        list.value = null
                    }
                }
            });
        }
        var onEdit = (data) => {
            tableItem.value = data //发送当前行数据给组件
            console.log(data)   //输出了一个Proxy数据

            console.log("tableItem部分---------------------Ref------------------------------------------------------------------------")
            console.log(tableItem) //输出了一个RefImpl数据
            console.log(tableItem.value)
            console.log(tableItem.value.shopname) //
            console.log("rea部分------------------Reactive---------------------------------------------------------------------------")
            console.log(rea)
            console.log(rea.name)
            console.log(toRefs(rea))
            console.log(toRefs(rea).name)
            console.log("a部分---------------------Ref------------------------------------------------------------------------")
            console.log(a);
            console.log(a.value);
            console.log(a.value.name);


            //console.log(tableItem.value.get(name))  //会提示不是一个对象
            //console.log(tableItem.get(name))      //会提示console.log(tableItem.value.get(name))
            showDialog.value = true         //显示表单
            dialogFormVisible.value = true  //显示弹窗
        }

        //到这里为止都是加上的
        var data = reactive({
            arr: [{
                id: 1,
                shopname: "沙县小吃",
                score: 5.5,
                sales: 1200,
                hh: 12,
                type: "快餐"
            },
            {
                id: 2,
                shopname: "法式牛排餐厅",
                score: 7.5,
                sales: 2400,
                hh: 12,
                type: "西餐"
            },
            {
                id: 3,
                shopname: "沙县大吃",
                score: 6.5,
                sales: 200,
                hh: 12,
                type: "快餐"
            },
            {
                id: 4,
                shopname: "南昌瓦罐汤",
                score: 6.9,
                sales: 2400,
                hh: 12,
                type: "快餐"
            },
            ]
        })

        return {
            data,
            multipleTableRef,
            toggleSelection,
            handleSelectionChange,
            delete_post,
            data2,
            onEdit,
            showDialog,
            tableItem,
            dialogFormVisible,
            close,
            rea, a
        }
    }
}
</script>
  
<style></style>
  

子组件代码

html 复制代码
<template>
    <el-form label-width="120px">
        <el-form-item label="Activity name">
            <el-input v-model="tableItem2.shopname" :value="tableItem2.shopname" />
        </el-form-item>
        <el-form-item label="Activity name">
            <el-input v-model="tableItem2.score" :value="tableItem2.score" />
        </el-form-item>
        <el-form-item label="Activity name">
            <el-input v-model="tableItem2.sales" :value="tableItem2.sales" />
        </el-form-item>
        <el-form-item label="Activity name">
            <el-input v-model="tableItem2.type" :value="tableItem2.type" />
        </el-form-item>
        <el-form-item>
            <el-button type="primary" @click="onSubmit">Create</el-button>
            <el-button>Cancel</el-button>
        </el-form-item>
    </el-form>
</template>
<script>
import { toRefs } from '@vue/reactivity';
import { ref } from 'vue';

export default ({

    name: 'DialogComponent',
    props: {
        tableItem: {
        },
    },
    setup(props) {

        const tableItem2 = ref();
        const temp = ref();
        tableItem2.value = toRefs(props).tableItem.value

        console.log("1--------")
        console.log(props);       //props是一个proxy代理对象
        console.log("2--------")
        console.log(props.tableItem)  //里面包着的tableItem也是一个代理对象
        console.log("3--------")
        //console.log(props.tableItem.value)   
        console.log(props.tableItem.shopname)  //Proxy代理对象不需要用value,可以直接访问
        console.log("4--------")
        temp.value = toRefs(props.tableItem)  //使用一个ref接受tableItem这个reactive创建的proxy对象然后将里面的属性全部变成了拥有ObjectRefImpl类型
        console.log(temp)
        console.log(temp.value.shopname)        //为什么这里ObjectRefImpl不需要用value
        console.log(temp.value.shopname.value)  //shopname是ObjectRefImpl类型,但是.value输出undefined
        console.log("5--------")
        console.log(toRefs(props).tableItem)   //tableItem变成了ObjectRefImpl类型,但是value还是proxy类型
        console.log("6--------")
        console.log(toRefs(props).tableItem.value)  //这里tableItem是ObjectRefImpl类型,用.value输出了proxy类型
        console.log(toRefs(props).tableItem.value.shopname) //

        const onSubmit = () => {
            console.log(tableItem2.value)
        }
        return {
            onSubmit,
            tableItem2,
        }
    },
})

</script>

<style scoped></style>
相关推荐
Martin -Tang35 分钟前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发36 分钟前
解锁微前端的优秀库
前端
王解1 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
老码沉思录1 小时前
写给初学者的React Native 全栈开发实战班
javascript·react native·react.js
我不当帕鲁谁当帕鲁2 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂2 小时前
工程化实战内功修炼测试题
前端·javascript
放逐者-保持本心,方可放逐3 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
毋若成5 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
红中马喽5 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
Black蜡笔小新6 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html