ref说明
接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。
ref 对象是可更改的,也就是说你可以为 .value 赋予新的值。它也是响应式的,即所有对 .value 的操作都将被追踪,并且写操作会触发与之相关的副作用。
如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。
若要避免这种深层次的转换,请使用 shallowRef() 来替代。
与reactive的区别
- reactive可以实现对象的响应式代理
- ref可以把基本数据类型也变成响应式数据
ref的使用示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="./reactivity.global.js"></script>
<!-- <script src="../../../node_modules/vue/dist/vue.global.js"></script> -->
<script>
const app = document.getElementById('app');
const { ref, effect } = VueReactivity;
const status = ref(false);
effect(() => {
app.innerHTML = status.value ? '1' : '0';
});
setTimeout(() => {
status.value = !status.value;
}, 1000);
</script>
</body>
</html>
实现ref
packages/reactivity/src/ref.ts
import { isObject } from '@vue/shared';
import { trackEffects, triggerEffects } from './effect';
import { reactive } from './reactive';
function toReactive(value) {
return isObject(value) ? reactive(value) : value;
}
class RefImpl {
public _value;
public dep = new Set();
public readonly __v_isRef = true;
constructor(public rawValue) {
this._value = toReactive(rawValue);
}
get value() {
// 依赖收集
trackEffects(this.dep);
return this._value;
}
set value(newValue) {
if (newValue !== this.rawValue) {
this._value = toReactive(newValue);
this.rawValue = newValue;
triggerEffects(this.dep);
}
}
}
export function ref(value) {
return new RefImpl(value);
}
简单的ref就实现了
toRefs说明
toRefs官方文档
将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。
当从组合式函数中返回响应式对象时,toRefs 相当有用。使用它,消费者组件可以解构/展开返回的对象而不会失去响应性。
toRefs 在调用时只会为源对象上可以枚举的属性创建 ref。如果要为可能还不存在的属性创建 ref,请改用 toRef。
toRef、toRefs的使用示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="./reactivity.global.js"></script>
<!-- <script src="../../../node_modules/vue/dist/vue.global.js"></script> -->
<script>
const app = document.getElementById('app');
const { effect, reactive, toRefs, toRef } = VueReactivity;
const state = reactive({
foo: 1,
bar: 'bar',
des: 'des text',
});
const stateAsRefs = toRefs(state);
const stateAsRef = toRef(state, 'des');
const { foo, bar } = stateAsRefs;
const des = stateAsRef;
effect(() => {
app.innerHTML = foo.value + '__' + bar.value + '__' + des.value;
});
// 这个 ref 和源属性已经“链接上了”
setTimeout(() => {
state.foo++;
}, 1000);
setTimeout(() => {
bar.value = 'bowen';
des.value = 'none';
}, 2000);
</script>
</body>
</html>
实现toRef、toRefs
packages/reactivity/src/ref.ts 新增
// 将访问或者设置.value的操作代理到源对象
class ObjectRefIMpl {
constructor(public object, public key) {}
get value() {
return this.object[this.key];
}
set value(newValue) {
this.object[this.key] = newValue;
}
}
export function toRef(object, key) {
return new ObjectRefIMpl(object, key);
}
export function toRefs(object) {
const result = isArray(object) ? new Array(object.length) : {};
for (const key in object) {
result[key] = toRef(object, key);
}
return result;
}
使用toRefs处理完数据之后,我们取值都需要加上.value,书写也是不方便的,所以有proxyRefs这样一个方法可以去除.value直接取值和赋值
proxyRefs的使用示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="./reactivity.global.js"></script>
<!-- <script src="../../../node_modules/vue/dist/vue.global.js"></script> -->
<script>
const app = document.getElementById('app');
const { effect, ref, proxyRefs } = Vue;
const name = ref('bowen');
const age = ref(18);
const person = proxyRefs({ name, age })
effect(() => {
app.innerHTML = person.name + '__' + person.age;
});
</script>
</body>
</html>
实现proxyRefs
packages/reactivity/src/ref.ts 新增
export function proxyRefs(object) {
return new Proxy(object, {
get(target, key, receiver) {
let res = Reflect.get(target, key, receiver);
return res.__v_isRef ? res.value : res;
},
set(target, key, value, receiver) {
let oldValue = target[key];
if (oldValue.__v_isRef) {
oldValue.value = value;
return true;
}
return Reflect.set(target, key, value, receiver);
},
});
}