文章目录
三个创建响应式数据的函数reactive、toRefs、ref
-
reactive:把对象转换成一个响应式对象,也就是一个Proxy对象。
-
toRefs:它可以把一个代理对象中的所有属性也都转换成响应式的对象,
toRefs()
在处理这个对象的属性的时候,类似于ref
。 -
ref: 把基本类型的数据转换成响应式对象。
先从一个问题看起, 响应式对象解构过后就不再是响应式的对象了,因为我们在创建响应式对象的时候使用reactive
函数将数据封装成了Proxy
对象
例如,以下的代码是不可以正常工作的:
【案例代码在线演示地址】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document 01 for Vue 3.0 Composition API</title>
</head>
<body>
<div id="app">
x: {{ x }} <br />
y: {{ y }}
</div>
<script type="module">
// 当前重构抽离的函数可以放到一个模块中,将来在任何组件中都可以使用,你可以尝试使用相同的方式实现其他的逻辑
function useMousePosition() {
const position = reactive({
x: 0,
y: 0,
});
const update = (e) => {
position.x = e.pageX;
position.y = e.pageY;
};
onMounted(() => {
window.addEventListener('mousemove', update);
});
onUnmounted(() => {
window.removeEventListener('mousemove', update);
});
return position;
}
import {
createApp,
reactive,
onMounted,
onUnmounted,
} from './node_modules/vue/dist/vue.esm-browser.js';
const app = createApp({
setup() {
// 第一个参数, props 接收外部传入的参数,是一个响应式的对象,它不能被解构。
// 第二个参数, context ,是一个对象,具有三个成员 attrs、emit、slots
// 需要返回一个对象,setup中返回的对象可以使用在模版,methods,computed,以及生命周期的钩子函数中
// const position = useMousePosition();
const { x, y } = useMousePosition();
return {
x,
y,
};
},
});
console.log(app);
app.mount('#app');
</script>
</body>
</html>
因为useMousePosition的返回值position
原本是个响应式对象Proxy对象,将来访问其x
和y
的时候会调用代理对象中的getter
拦截收集依赖,当x
和y
变化之后,会调用代理对象中的setter
进行拦截触发更新。当我们把代理对象解构的时候,const {x, y} = useMousePosition()
, 就相当于定义了 x 和 y 两个变量来接收 position.x 和 position.y ,而基本类型的赋值就相当于把值在内存中复制了一份,所以这里解构得到的就是两个基本类型的变量,跟Proxy代理对象无关,当重新给 x 和 y 赋值的时候,也不会弟调用代理对象的 setter,无法触发更新的操作。所以**是不能够对当前的响应式对象进行解构
**。
babel对解构代码降级之后其实就是定义了 x 和 y 两个变量,用来接收position.x
和position.y
。如图示:
如果我们真要想像这样这么做的话,还是有办法的。
toRefs()
这是就需要用到一个新的API,叫 toRefs()
,下面我们来演示一下如何使用toRefs()
:
【在线演示代码】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document 01 for Vue 3.0 Composition API</title>
</head>
<body>
<div id="app">
x: {{ x }} <br />
y: {{ y }}
</div>
<script type="module">
import {
createApp,
reactive,
onMounted,
onUnmounted,
toRefs,
} from './node_modules/vue/dist/vue.esm-browser.js';
// 当前重构抽离的函数可以放到一个模块中,将来在任何组件中都可以使用,你可以尝试使用相同的方式实现其他的逻辑
function useMousePosition() {
const position = reactive({
x: 0,
y: 0,
});
const update = (e) => {
position.x = e.pageX;
position.y = e.pageY;
};
onMounted(() => {
window.addEventListener('mousemove', update);
});
onUnmounted(() => {
window.removeEventListener('mousemove', update);
});
return toRefs(position);
}
const app = createApp({
setup() {
// 第一个参数, props 接收外部传入的参数,是一个响应式的对象,它不能被解构。
// 第二个参数, context ,是一个对象,具有三个成员 attrs、emit、slots
// 需要返回一个对象,setup中返回的对象可以使用在模版,methods,computed,以及生命周期的钩子函数中
// const position = useMousePosition();
const { x, y } = useMousePosition(); // 这里结构出来的x和y都是响应式对象,具有一个value,在模版使用中可以省略x.value;但是在代码中使用的时候value是不可省略的。
return {
x,
y,
};
},
});
console.log(app);
app.mount('#app');
</script>
</body>
</html>
我们只需要在useMousePosition()
返回之前使用toRefs()
函数将响应式的Proxy对象转换成引用即可。
toRefs()
函数的作用就是把响应式对象中的所有属性也转换成响应式的。
其原理是:
toRefs(proxyObj)
要求我们传入的参数必须是一个Proxy对象,如果不是的话,它会抱警告提示需要传递的是代理Proxy对象。- 接下来它内部会创建一个新的对象,然后遍历传入的这个代理Proxy对象的所有属性,把所有属性的值都转换成代理对象。
- 注意:
toRefs()
里面是把传入的proxyObj代理对象的所有属性的值都转换成响应式的对象,然后再挂载到新创建的对象上,最后把这个新创建的的对象返回。它内部会为代理对象的每一个属性创建一个具有value
属性的对象,该对象是响应式的。value
属性具有getter
和setter
。这一点和下面要讲的**ref()
函数类似**。getter
里面返回代理对象中对应属性的值;setter
中给代理对象的属性赋值。所以我们返回的每一个属性都是响应式的。
所以我们可以解构使用了toRefs()
转换过后的响应式对象,结构的每一个属性也都是响应式的。
ref()
ref()
的作用是把普通数据转换成响应式数据,也就是把基本类型的数据包装成具有value值的响应式对象。- 和
reactive()
不同的是,reactive()
是把一个对象转换成响应式数据。
ref()
的使用案例演示:【在线案例地址】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document 01 for Vue 3.0 Composition API</title>
</head>
<body>
<div id="app">
<button @click="increase">count值➕1</button>
<span>{{count}}</span>
</div>
<script type="module">
import {
createApp,
ref,
} from './node_modules/vue/dist/vue.esm-browser.js';
function useCount() {
const count = ref(0);
return {
count,
increase: () => {
count.value++;
},
};
}
const app = createApp({
setup() {
// 第一个参数, props 接收外部传入的参数,是一个响应式的对象,它不能被解构。
// 第二个参数, context ,是一个对象,具有三个成员 attrs、emit、slots
// 需要返回一个对象,setup中返回的对象可以使用在模版,methods,computed,以及生命周期的钩子函数中
return { ...useCount() };
},
});
app.mount('#app');
</script>
</body>
</html>
案例分析:我们知道基本数据类型存储的是值,所以它不可能是响应式数据,我们知道响应式数据要通过getter
收集依赖,通过setter
触发更新。那么:
ref()
传入的参数如果是个对象的话,它内部会调用reactive
返回一个响应式对象。ref()
传入的参数如果是基本类型的值,例如我们的案例中传入的0
,那它内部会创建一个具有value
属性的对象,该对象的value
属性具有getter
和setter
,getter
里面收集依赖;setter
中触发更新。