使用ref()的一个实例
<template>
<div>{{ count }}</div>
<div><button @click="add">点击加1</button></div>
</template>
<script>
import { ref } from 'vue'
export default {
setup(){
const count = ref(0)
function add(){
count.value ++
}
return {
count,
add
}
}
}
</script>
ref()源码实现
function ref(value) {
return createRef(value);
}
function createRef(rawValue, shallow = false) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
// val=0,如果val是对象(包含数组/Map/Set/WeakMap/WeakSet),则调用reactive()变成响应式;如果不是对象,则直接返回val
// reactive就是将我们的数据转成响应式数据
const convert = (val) => isObject(val) ? reactive(val) : val;
class RefImpl {
constructor(_rawValue, _shallow = false) {
this._rawValue = _rawValue;
this._shallow = _shallow;
this.__v_isRef = true;
this._value = _shallow ? _rawValue : convert(_rawValue);
}
// 在类的内部使用get和set关键字,对属性value设置存储函数和取值函数,拦截该属性的取存行为。
get value() {
track(toRaw(this), "get" /* GET */, 'value');
return this._value;
}
set value(newVal) {
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal;
this._value = this._shallow ? newVal : convert(newVal);
trigger(toRaw(this), "set" /* SET */, 'value', newVal);
}
}
}
# ref()返回的最终结果
ref()深入理解
这里最难理解的就在于这个ref函数。 我们看到,这里也定义了get/set,却没有任何Proxy相关的操作。 在之前的信息中我们知道reactive能构建出响应式数据,但要求传参必须是对象。 ref的入参是对象时,同样也需要reactive做转化。 那ref这个函数的目的到底是什么呢?为什么需要它? 对于基本数据类型、函数传递或者对象结构时,会丢失原始数据的引用, 换言之,我们没法让基本数据类型,或者结构的变量(如果他的值也是基本数据类型的话),成为响应式的数据。 但是有时候,我们确实需要将一个数字、一个字符串置为响应式, 或者就是想利用解构的写法。 那怎么办呢? 只能通过创建一个对象,也就是源码中的Ref数据,然后将原始数据保存在Ref的属性value中国 再将它的引用返回给使用者。 既然是我们自己创造出来的对象,也就没必要使用Proxy再做代理了,直接劫持这个value的get/set即可,这就是ref函数与Ref类型的由来。
ES6类Class的get、set使用
class Person {
//构造函数
constructor(value){
this.name = "ysg";
this._age = value;
}
get value(){
return this._age
}
set value(value){
this._age = value
}
}
let p = new Person(18)
console.log(p.value)
console.log(p._age)
p.value = 31
console.log(p.value)
console.log(p._age)
get触发track函数进行依赖收集
- track 跟踪
// target:触发track的对象
// type: 表示触发track的类型(主要有get、has、iterate三种类型)
// key: 表示触发当前track方法对应得object属性的key
function track(target, type, key) {
if (!shouldTrack || activeEffect === undefined) {
return;
}
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
if ((process.env.NODE_ENV !== 'production') && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
});
}
}
}
set触发trigger方法执行我们收集到的依赖
function trigger(target, type, key, newValue, oldValue, oldTarget) {
const depsMap = targetMap.get(target);
if (!depsMap) {
// never been tracked
return;
}
const effects = new Set();
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect);
}
});
}
};
if (type === "clear" /* CLEAR */) {
// collection being cleared
// trigger all effects for target
depsMap.forEach(add);
}
else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= newValue) {
add(dep);
}
});
}
else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
add(depsMap.get(key));
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case "add" /* ADD */:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY));
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'));
}
break;
case "delete" /* DELETE */:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY));
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
break;
case "set" /* SET */:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY));
}
break;
}
}
const run = (effect) => {
if ((process.env.NODE_ENV !== 'production') && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
});
}
if (effect.options.scheduler) {
effect.options.scheduler(effect);
}
else {
effect();
}
};
effects.forEach(run);
}
当前template模板中有使用到count,则会给count添加render effect; 则当count.value修改,会触发页面更新。