<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          源碼視角,Vue3為什么推薦使用ref而不是reactive

          共 13253字,需瀏覽 27分鐘

           ·

          2024-08-02 09:15

          點擊上方 前端Q,關(guān)注公眾號

          回復加群,加入前端Q技術(shù)交流群

          refreactive 是 Vue3 中實現(xiàn)響應(yīng)式數(shù)據(jù)的核心 API。ref 用于包裝基本數(shù)據(jù)類型,而 reactive 用于處理對象和數(shù)組。盡管 reactive 似乎更適合處理對象,但 Vue3 官方文檔更推薦使用 ref

          我的想法,ref就是比reactive好用,官方也是這么說的,不服來踩!下面我們從源碼的角度詳細討論這兩個 API,以及 Vue3 為什么推薦使用ref而不是reactive

          ref 的內(nèi)部工作原理

          ref 是一個函數(shù),它接受一個內(nèi)部值并返回一個響應(yīng)式且可變的引用對象。這個引用對象有一個 .value 屬性,該屬性指向內(nèi)部值。

          // 深響應(yīng)式
          export function ref(value?: unknown{
            return createRef(value, false)
          }

          // 淺響應(yīng)式
          export function shallowRef(value?: unknown{
            return createRef(value, true)
          }

          function createRef(rawValue: unknown, shallow: boolean{
            // 如果傳入的值已經(jīng)是一個 ref,則直接返回它
            if (isRef(rawValue)) {
              return rawValue
            }
            // 否則,創(chuàng)建一個新的 RefImpl 實例
            return new RefImpl(rawValue, shallow)
          }

          class RefImpl<T{
            // 存儲響應(yīng)式的值。我們追蹤和更新的就是_value。(這個是重點)
            private _value: T
            // 用于存儲原始值,即未經(jīng)任何響應(yīng)式處理的值。(用于對比的,這塊的內(nèi)容可以不看)
            private _rawValue: T 

            // 用于依賴跟蹤的 Dep 類實例
            public dep?: Dep = undefined
            // 一個標記,表示這是一個 ref 實例
            public readonly __v_isRef = true

            constructor(
              value: T,
              public readonly __v_isShallow: boolean,
            ) {
              // 如果是淺響應(yīng)式,直接使用原始值,否則轉(zhuǎn)換為非響應(yīng)式原始值
              this._rawValue = __v_isShallow ? value : toRaw(value)
              // 如果是淺響應(yīng)式,直接使用原始值,否則轉(zhuǎn)換為響應(yīng)式值
              this._value = __v_isShallow ? value : toReactive(value)
              
              // toRaw 用于將響應(yīng)式引用轉(zhuǎn)換回原始值
              // toReactive 函數(shù)用于將傳入的值轉(zhuǎn)換為響應(yīng)式對象。對于基本數(shù)據(jù)類型,toReactive 直接返回原始值。
              // 對于對象和數(shù)組,toReactive 內(nèi)部會調(diào)用 reactive 來創(chuàng)建一個響應(yīng)式代理。
              // 因此,對于 ref 來說,基本數(shù)據(jù)類型的值會被 RefImpl 直接包裝,而對象和數(shù)組
              // 會被 reactive 轉(zhuǎn)換為響應(yīng)式代理,最后也會被 RefImpl 包裝。
              // 這樣,無論是哪種類型的數(shù)據(jù),ref 都可以提供響應(yīng)式的 value 屬性,
              // 使得數(shù)據(jù)變化可以被 Vue 正確追蹤和更新。
              // export const toReactive = (value) => isObject(value) ? reactive(value) : value
            }

            get value() {
              // 追蹤依賴,這樣當 ref 的值發(fā)生變化時,依賴這個 ref 的組件或副作用函數(shù)可以重新運行。
              trackRefValue(this)
              // 返回存儲的響應(yīng)式值
              return this._value
            }

            set value(newVal) {
              // 判斷是否應(yīng)該使用新值的直接形式(淺響應(yīng)式或只讀)
              const useDirectValue =
                this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
              // 如果需要,將新值轉(zhuǎn)換為非響應(yīng)式原始值
              newVal = useDirectValue ? newVal : toRaw(newVal)
              // 如果新值與舊值不同,更新 _rawValue 和 _value
              if (hasChanged(newVal, this._rawValue)) {
                this._rawValue = newVal
                this._value = useDirectValue ? newVal : toReactive(newVal)
                // 觸發(fā)依賴更新
                triggerRefValue(this, DirtyLevels.Dirty, newVal)
              }
            }
          }

          在上述代碼中,ref 函數(shù)通過 new RefImpl(value) 創(chuàng)建了一個新的 RefImpl 實例。這個實例包含 getter 和 setter,分別用于追蹤依賴和觸發(fā)更新。使用 ref 可以聲明任何數(shù)據(jù)類型的響應(yīng)式狀態(tài),包括對象和數(shù)組。

          import { ref } from 'vue' 

          let state = ref({ count0 })
          state.value.count++

          注意,ref核心是返回「響應(yīng)式且可變的引用對象」,而reactive核心是返回的是「響應(yīng)式代理」,這是兩者本質(zhì)上的核心區(qū)別,也就導致了ref優(yōu)于reactive,我們接著看下reactive源碼實現(xiàn)。

          reactive 的內(nèi)部工作原理

          reactive 是一個函數(shù),它接受一個對象并返回該對象的響應(yīng)式代理,也就是 Proxy

          function reactive(target{
            if (target && target.__v_isReactive) {
              return target
            }

            return createReactiveObject(
              target,
              false,
              mutableHandlers,
              mutableCollectionHandlers,
              reactiveMap
            )
          }

          function createReactiveObject(
            target,
            isReadonly,
            baseHandlers,
            collectionHandlers,
            proxyMap
          {
            if (!isObject(target)) {
              return target
            }
            
            const existingProxy = proxyMap.get(target)
            if (existingProxy) {
              return existingProxy
            }
            
            const proxy = new Proxy(target, baseHandlers)
            proxyMap.set(target, proxy)
            return proxy
          }

          reactive的源碼相對就簡單多了,reactive 通過 new Proxy(target, baseHandlers) 創(chuàng)建了一個代理。這個代理會攔截對目標對象的操作,從而實現(xiàn)響應(yīng)式。

          import { reactive } from 'vue' 

          let state = reactive({ count0 })
          state.count++

          到這里我們可以看出 refreactive 在聲明數(shù)據(jù)的響應(yīng)式狀態(tài)上,底層原理是不一樣的。ref 采用 RefImpl對象實例,reactive采用Proxy代理對象。

          ref 更深入的理解

          當你使用 new RefImpl(value) 創(chuàng)建一個 RefImpl 實例時,這個實例大致上會包含以下幾部分:

          1. 「內(nèi)部值」:實例存儲了傳遞給構(gòu)造函數(shù)的初始值。
          2. 「依賴收集」:實例需要跟蹤所有依賴于它的效果(effect),例如計算屬性或者副作用函數(shù)。這通常通過一個依賴列表或者集合來實現(xiàn)。
          3. 「觸發(fā)更新」:當實例的值發(fā)生變化時,它需要通知所有依賴于它的效果,以便它們可以重新計算或執(zhí)行。

          RefImpl 類似于發(fā)布-訂閱模式的設(shè)計,以下是一個簡化的 RefImpl 類的偽代碼實現(xiàn),展示這個實現(xiàn)過程:

          class Dep {
            constructor() {
              this.subscribers = new Set();
            }

            depend() {
              if (activeEffect) {
                this.subscribers.add(activeEffect);
              }
            }

            notify() {
              this.subscribers.forEach(effect => effect());
            }
          }

          let activeEffect = null;

          function watchEffect(effect{
            activeEffect = effect;
            effect();
            activeEffect = null;
          }

          class RefImpl {
            constructor(value) {
              this._value = value;
              this.dep = new Dep();
            }

            get value() {
              // 當獲取值時,進行依賴收集
              this.dep.depend();
              return this._value;
            }

            set value(newValue) {
              if (newValue !== this._value) {
                this._value = newValue;
                // 值改變時,觸發(fā)更新
                this.dep.notify();
              }
            }
          }

          // 使用示例
          let count = new RefImpl(0);

          watchEffect(() => {
            console.log(`The count is: ${count.value}`); // 訂閱變化
          });

          count.value++; // 修改值,觸發(fā)通知,重新執(zhí)行watchEffect中的函數(shù)

          Dep 類負責管理一個依賴列表,并提供依賴收集和通知更新的功能。RefImpl 類包含一個內(nèi)部值 _value 和一個 Dep 實例。當 value 被訪問時,通過 get 方法進行依賴收集;當 value 被賦予新值時,通過 set 方法觸發(fā)更新。

          refreactive 盡管兩者在內(nèi)部實現(xiàn)上有所不同,但它們都能滿足我們對于聲明響應(yīng)式變量的要求,但是 reactive 卻存在一定的局限性。

          reactive 的局限性

          在 Vue3 中,reactive API 通過 Proxy 實現(xiàn)了一種響應(yīng)式數(shù)據(jù)的方法,盡管這種方法在性能上比 Vue2 有所提升,但 Proxy 的局限性也導致了 reactive 的局限性,這些局限性可能會影響開發(fā)者的使用體驗。

          僅對引用數(shù)據(jù)類型有效

          reactive 主要適用于對象,包括數(shù)組和一些集合類型(如 MapSet)。對于基礎(chǔ)數(shù)據(jù)類型(如 stringnumberboolean),reactive 是無效的。這意味著如果你嘗試使用 reactive 來處理這些基礎(chǔ)數(shù)據(jù)類型,將會得到一個非響應(yīng)式的對象。

          import { reactive } from 'vue';
          const state = reactive({ count0 });

          使用不當會失去響應(yīng)

          1. 「直接賦值對象」:如果直接將一個響應(yīng)式對象賦值給另一個變量,將會失去響應(yīng)性。這是因為 reactive 返回的是對象本身,而不僅僅是代理。

            import { reactive } from 'vue';

            let state = reactive({ count0 });
            state = { count1 }; // 失去響應(yīng)性
          2. 「直接替換響應(yīng)式對象」:同樣,直接替換一個響應(yīng)式對象也會導致失去響應(yīng)性。

            import { reactive } from 'vue';

            let state = reactive({ count0 });
            state = reactive({ count1 }); // 失去響應(yīng)性
          3. 「直接解構(gòu)對象」:在解構(gòu)響應(yīng)式對象時,如果直接解構(gòu)對象屬性,將會得到一個非響應(yīng)式的變量。

            const state = reactive({ count0 });

            let { count } = state;
            count++; // count 仍然是 0

            解決這個問題,需要使用 toRefs 函數(shù)來將響應(yīng)式對象轉(zhuǎn)換為 ref 對象。

            import { toRefs } from 'vue';

            const state = reactive({ count0 });
            let { count } = toRefs(state);
            count++; // count 現(xiàn)在是 1
          4. 「將響應(yīng)式對象的屬性賦值給變量」:如果將響應(yīng)式對象的屬性賦值給一個變量,這個變量的值將不會是響應(yīng)式的。

            let state = reactive({ count0 })

            let count = state.count
            count++  // count 仍然是 0
            console.log(state.count)

          使用 reactive 聲明響應(yīng)式變量的確存在一些不便之處,尤其是對于喜歡使用解構(gòu)賦值的開發(fā)者而言。這些局限性可能會導致意外的行為,因此在使用 reactive 時需要格外注意。相比之下,ref API 提供了一種更靈活和統(tǒng)一的方式來處理響應(yīng)式數(shù)據(jù)。

          為什么推薦使用 ref ?

          ref()它為響應(yīng)式編程提供了一種統(tǒng)一的解決方案,適用于所有類型的數(shù)據(jù),包括基本數(shù)據(jù)類型和復雜對象。以下是推薦使用 ref 的幾個關(guān)鍵原因:

          統(tǒng)一性

          ref 的核心優(yōu)勢之一是它的統(tǒng)一性。它提供了一種簡單、一致的方式來處理所有類型的數(shù)據(jù),無論是數(shù)字、字符串、對象還是數(shù)組。這種統(tǒng)一性極大地簡化了開發(fā)者的代碼,減少了在不同數(shù)據(jù)類型之間切換時的復雜性。

          import { ref } from 'vue';

          let num = ref(0);
          let str = ref('Hello');
          let obj = ref({ count0 });

          // 修改基本數(shù)據(jù)類型
          num.value++;
          str.value += ' World';

          // 修改對象
          obj.value.count++;

          深層響應(yīng)性

          ref 支持深層響應(yīng)性,這意味著它可以追蹤和更新嵌套對象和數(shù)組中的變化。這種特性使得 ref 非常適合處理復雜的數(shù)據(jù)結(jié)構(gòu),如對象和數(shù)組。

          import { ref } from 'vue';

          let obj = ref({
            user: {
              name'xiaoming',
              details: {
                age18
              }
            }
          });

          // 修改嵌套對象
          obj.value.user.details.age++;

          當然,為了減少大型不可變數(shù)據(jù)的響應(yīng)式開銷,也可以通過使用shallowRef來放棄深層響應(yīng)性。

          let shallowObj = shallowRef({ 
              details: { age18, }, 
          });

          靈活性

          ref 提供了高度的靈活性,尤其在處理「普通賦值」方面。這種靈活性使得 ref 在開發(fā)中的使用更加方便,特別是在進行復雜的數(shù)據(jù)操作時。

          import { ref } from 'vue';

          let state = ref({
            count0,
            name'Vue'
          });

          // 替換整個對象
          state.value = {
            count10,
            name'Vue 4'
          };
          // 修改對象內(nèi)的屬性
          state.value.count = 20;
          state.value.name = 'Vue 5';
          // 添加新的屬性
          state.value.newProperty = 'New Property';
          // 刪除屬性
          delete state.value.newProperty;
          // 使用解構(gòu)更新屬性(注意要保持響應(yīng)性)
          let { count, name } = state.value;
          state.value = { count: count + 1, name };
          // 復雜操作,例如根據(jù)條件更新屬性
          if (someCondition) {
            state.value = {
              ...state.value,
              name'Updated Name'
            };
          }
          console.log(state.value)

          總結(jié)

          ref 在 Vue3 中提供了一種更統(tǒng)一、靈活的響應(yīng)式解決方案,還能避免了 reactive 的某些局限性。希望這篇文章對你有所幫助,有所借鑒。大家怎么認為呢,評論區(qū)我們一起討論下!

          往期推薦


          幾行代碼,優(yōu)雅的避免接口重復請求!同事都說好!
          某一線前端小組長的 Code Review 分享
          小程序可測性能力建設(shè)與實踐

          最后


          • 歡迎加我微信,拉你進技術(shù)群,長期交流學習...

          • 歡迎關(guān)注「前端Q」,認真學前端,做個專業(yè)的技術(shù)人...

          點個在看支持我吧

          瀏覽 141
          1點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          1點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本成年人视频在线播放 | AAA免费视频在线 | 影音先锋男人在线资源 | 国产精品久久久久久69 | 亚洲色婷婷五月 |