- A+
所属分类:Web前端
Ref & ShallowRef
ref:接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value
可以将 ref 看成 reactive 的一个变形版本,这是由于 reactive 内部采用 Proxy 来实现,而 Proxy 只接受对象作为入参,这才有了 ref 来解决值类型的数据响应,如果传入 ref 的是一个对象,内部也会调用 reactive 方法进行深层响应转换
const count = ref(0) console.log(count.value) // 0 count.value++ console.log(count.value) // 1
shallowRef:ref() 的浅层作用形式。和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。
const state = shallowRef({ count: 1 }) // 不会触发更改 state.value.count = 2 // 会触发更改 state.value = { count: 2 }
源码实现
- @issue1 如果是对象和数组,则调用 reactive方法 转化为响应式对象(ref会转换,shallowRef不会转换)
- @issue2 getter 取值的时候收集依赖
- @issue3 setter 设置值的时候触发依赖
/** * @desc 如果是对象和数组,则转化为响应式对象 */ function toReactive(value) { return isObject(value) ? reactive(value) : value } /** * @desc RefImpl * @issue1 如果是对象和数组,则转化为响应式对象 */ class RefImpl { // ref标识 public __v_isRef = true // 存储effect public dep = new Set() public _value constructor(public rawValue, public _shallow) { // @issue1 this._value = _shallow ? rawValue : toReactive(rawValue) } get value() { // 取值的时候收集依赖 trackEffects(this.dep) return this._value } set value(newValue) { // 新旧值不相等 if (newValue !== this.rawValue) { // @issue1 this._value = this._shallow ? newValue : toReactive(newValue) this.rawValue = newValue // 设置值的时候触发依赖 triggerEffects(this.dep) } } } // 接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。 export function ref(value) { return new RefImpl(value) }
测试代码
/** * 1. Ref **/ const person = ref({ name: '柏成', age: 25, }) effect(() => { app.innerHTML = person.value.name }) setTimeout(() => { person.value.name = '柏成2号' // 会触发更改 }, 1000) /** * 2. shallowRef */ const person = shallowRef({ name: '柏成', age: 25, }) effect(() => { app.innerHTML = person.value.name }) setTimeout(() => { person.value.name = '柏成2号' // 不会触发更改 }, 1000) setTimeout(() => { person.value = { name: '柏成9号' // 会触发更改 } }, 2000)
toRef & toRefs
toRef:基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。
const state = reactive({ foo: 1, bar: 2 }) const fooRef = toRef(state, 'foo') // 更改该 ref 会更新源属性 fooRef.value++ console.log(state.foo) // 2 // 更改源属性也会更新该 ref state.foo++ console.log(fooRef.value) // 3
toRefs:将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。
const state = reactive({ foo: 1, bar: 2 }) const stateAsRefs = toRefs(state) // 这个 ref 和源属性已经 “链接上了” state.foo++ console.log(stateAsRefs.foo.value) // 2 stateAsRefs.foo.value++ console.log(state.foo) // 3
源码实现
class ObjectRefImpl { // 只是将.value属性代理到原始类型上 constructor(public object, public key) {} get value() { return this.object[this.key] } set value(newValue) { this.object[this.key] = newValue } } // 基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。 export function toRef(object, key) { return new ObjectRefImpl(object, key) } // 将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。 export function toRefs(object) { const result = isArray(object) ? new Array(object.length) : {} for (let key in object) { result[key] = toRef(object, key) } return result }
测试代码
// 对象 const person = reactive({ name: '柏成', age: 18 }) // 数组 const numbers = reactive([1, 2, 3, 4, 5]) // 注意!直接解构后会丢失响应性的特点!!! // let { name, age } = person const { name, age } = toRefs(person) const [first] = toRefs(numbers) effect(() => { app.innerHTML = `${name.value},${age.value}岁。第一个数字为${first.value}。` }) setTimeout(() => { name.value = '柏成9号' first.value = 999 }, 1000)
自动脱ref
在js中访问ref时需要.value获取,但是在模版中却可以直接取值,不需要加.value!这里就用到了 proxyRefs 自动脱ref方法
源码实现
export function proxyRefs(object) { return new Proxy(object, { // 代理的思想,如果是ref 则取ref.value get(target, key, recevier) { let r = Reflect.get(target, key, recevier) return r.__v_isRef ? r.value : r }, // 设置的时候如果是ref,则给ref.value赋值 set(target, key, value, recevier) { let oldValue = target[key] if (oldValue.__v_isRef) { oldValue.value = value return true } else { return Reflect.set(target, key, value, recevier) } }, }) }
测试代码
const name = ref('柏成') const age = ref('24') const person = proxyRefs({ name, age, sex: '男' }) effect(() => { app.innerHTML = `${person.name},${person.age}岁。性别${person.sex}。` }) setTimeout(() => { name.value = '柏成9号' }, 1000)