经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » Vue.js » 查看文章
【源码系列#05】Vue3响应式原理(Ref)
来源:cnblogs  作者:柏成  时间:2024/1/5 9:10:32  对本文有异议

Ref & ShallowRef

ref:接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value

可以将 ref 看成 reactive 的一个变形版本,这是由于 reactive 内部采用 Proxy 来实现,而 Proxy 只接受对象作为入参,这才有了 ref 来解决值类型的数据响应,如果传入 ref 的是一个对象,内部也会调用 reactive 方法进行深层响应转换

  1. const count = ref(0)
  2. console.log(count.value) // 0
  3. count.value++
  4. console.log(count.value) // 1

shallowRef:ref() 的浅层作用形式。和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。

  1. const state = shallowRef({ count: 1 })
  2. // 不会触发更改
  3. state.value.count = 2
  4. // 会触发更改
  5. state.value = { count: 2 }

源码实现

  • @issue1 如果是对象和数组,则调用 reactive方法 转化为响应式对象(ref会转换,shallowRef不会转换)
  • @issue2 getter 取值的时候收集依赖
  • @issue3 setter 设置值的时候触发依赖
  1. /**
  2. * @desc 如果是对象和数组,则转化为响应式对象
  3. */
  4. function toReactive(value) {
  5. return isObject(value) ? reactive(value) : value
  6. }
  7. /**
  8. * @desc RefImpl
  9. * @issue1 如果是对象和数组,则转化为响应式对象
  10. */
  11. class RefImpl {
  12. // ref标识
  13. public __v_isRef = true
  14. // 存储effect
  15. public dep = new Set()
  16. public _value
  17. constructor(public rawValue, public _shallow) {
  18. // @issue1
  19. this._value = _shallow ? rawValue : toReactive(rawValue)
  20. }
  21. get value() {
  22. // 取值的时候收集依赖
  23. trackEffects(this.dep)
  24. return this._value
  25. }
  26. set value(newValue) {
  27. // 新旧值不相等
  28. if (newValue !== this.rawValue) {
  29. // @issue1
  30. this._value = this._shallow ? newValue : toReactive(newValue)
  31. this.rawValue = newValue
  32. // 设置值的时候触发依赖
  33. triggerEffects(this.dep)
  34. }
  35. }
  36. }
  37. // 接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。
  38. export function ref(value) {
  39. return new RefImpl(value)
  40. }

测试代码

  1. /**
  2. * 1. Ref
  3. **/
  4. const person = ref({
  5. name: '柏成',
  6. age: 25,
  7. })
  8. effect(() => {
  9. app.innerHTML = person.value.name
  10. })
  11. setTimeout(() => {
  12. person.value.name = '柏成2号' // 会触发更改
  13. }, 1000)
  14. /**
  15. * 2. shallowRef
  16. */
  17. const person = shallowRef({
  18. name: '柏成',
  19. age: 25,
  20. })
  21. effect(() => {
  22. app.innerHTML = person.value.name
  23. })
  24. setTimeout(() => {
  25. person.value.name = '柏成2号' // 不会触发更改
  26. }, 1000)
  27. setTimeout(() => {
  28. person.value = {
  29. name: '柏成9号' // 会触发更改
  30. }
  31. }, 2000)

toRef & toRefs

toRef:基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。

  1. const state = reactive({
  2. foo: 1,
  3. bar: 2
  4. })
  5. const fooRef = toRef(state, 'foo')
  6. // 更改该 ref 会更新源属性
  7. fooRef.value++
  8. console.log(state.foo) // 2
  9. // 更改源属性也会更新该 ref
  10. state.foo++
  11. console.log(fooRef.value) // 3

toRefs:将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

  1. const state = reactive({
  2. foo: 1,
  3. bar: 2
  4. })
  5. const stateAsRefs = toRefs(state)
  6. // 这个 ref 和源属性已经 “链接上了”
  7. state.foo++
  8. console.log(stateAsRefs.foo.value) // 2
  9. stateAsRefs.foo.value++
  10. console.log(state.foo) // 3

源码实现

  1. class ObjectRefImpl {
  2. // 只是将.value属性代理到原始类型上
  3. constructor(public object, public key) {}
  4. get value() {
  5. return this.object[this.key]
  6. }
  7. set value(newValue) {
  8. this.object[this.key] = newValue
  9. }
  10. }
  11. // 基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。
  12. export function toRef(object, key) {
  13. return new ObjectRefImpl(object, key)
  14. }
  15. // 将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。
  16. export function toRefs(object) {
  17. const result = isArray(object) ? new Array(object.length) : {}
  18. for (let key in object) {
  19. result[key] = toRef(object, key)
  20. }
  21. return result
  22. }

测试代码

  1. // 对象
  2. const person = reactive({
  3. name: '柏成',
  4. age: 18
  5. })
  6. // 数组
  7. const numbers = reactive([1, 2, 3, 4, 5])
  8. // 注意!直接解构后会丢失响应性的特点!!!
  9. // let { name, age } = person
  10. const {
  11. name,
  12. age
  13. } = toRefs(person)
  14. const [first] = toRefs(numbers)
  15. effect(() => {
  16. app.innerHTML = `${name.value},${age.value}岁。第一个数字为${first.value}。`
  17. })
  18. setTimeout(() => {
  19. name.value = '柏成9号'
  20. first.value = 999
  21. }, 1000)

自动脱ref

在js中访问ref时需要.value获取,但是在模版中却可以直接取值,不需要加.value!这里就用到了 proxyRefs 自动脱ref方法

源码实现

  1. export function proxyRefs(object) {
  2. return new Proxy(object, {
  3. // 代理的思想,如果是ref 则取ref.value
  4. get(target, key, recevier) {
  5. let r = Reflect.get(target, key, recevier)
  6. return r.__v_isRef ? r.value : r
  7. },
  8. // 设置的时候如果是ref,则给ref.value赋值
  9. set(target, key, value, recevier) {
  10. let oldValue = target[key]
  11. if (oldValue.__v_isRef) {
  12. oldValue.value = value
  13. return true
  14. } else {
  15. return Reflect.set(target, key, value, recevier)
  16. }
  17. },
  18. })
  19. }

测试代码

  1. const name = ref('柏成')
  2. const age = ref('24')
  3. const person = proxyRefs({
  4. name,
  5. age,
  6. sex: '男'
  7. })
  8. effect(() => {
  9. app.innerHTML = `${person.name},${person.age}岁。性别${person.sex}。`
  10. })
  11. setTimeout(() => {
  12. name.value = '柏成9号'
  13. }, 1000)

原文链接:https://www.cnblogs.com/burc/p/17926729.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号