欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue3 setup的使用和原理實例詳解

 更新時間:2023年06月14日 09:57:02   作者:KinHKin(五年前端)  
這篇文章主要介紹了vue3的setup的使用和原理,結合實例形式詳細分析了vue3 setup的基本功能、原理與使用方法,需要的朋友可以參考下

1.前言

為什么要使用setup?

寫一個大型組件時,邏輯關注點的列表很長,不利于維護和閱讀;所以需要把一個邏輯關注點的代碼收集在一起會更好,由此誕生組合式API,即vue中用到的setup。

就像我們要做鹵雞蛋,原來材料有四堆香料:八角、桂皮、香葉、茴香(data、method、computed、watch);一樣一樣找太麻煩,就打包做成香料袋,一次放一個袋子(setup)就行

最近在做vue3相關的項目,用到了組合式api,對于vue3的語法的改進也是大為贊賞,用起來十分方便。對于已經熟悉vue2寫法的同學也說,上手還是需要一定的學習成本,有可能目前停留在會寫會用的階段,但是setup帶來哪些改變,以及ref,reactive這兩api內部實現(xiàn)原理到底是什么,下面先來總結:

setup帶來的改變:

1.解決了vue2的data和methods方法相距太遠,無法組件之間復用

2.提供了script標簽引入共同業(yè)務邏輯的代碼塊,順序執(zhí)行

3.script變成setup函數(shù),默認暴露給模版

4.組件直接掛載,無需注冊

5.自定義的指令也可以在模版中自動獲得

6.this不再是這個活躍實例的引用

7.帶來的大量全新api,比如defineProps,defineEmits,withDefault,toRef,toRefs

ref帶來的改變:

Vue 提供了一個 ref() 方法來允許我們創(chuàng)建可以使用任何值類型的響應式數(shù)據(jù)

Ref作TS的類型標注

reactive帶來的改變:

可以使用 reactive() 函數(shù)創(chuàng)建一個響應式對象或數(shù)組

reactive可以隱式地從它的參數(shù)中推導類型

使用interface進行類型標注

需要了解vue2和vue3區(qū)別的可以查看我的這篇文章:

深入淺出分析vue2和vue3的區(qū)別

2.setup

在 setup() 函數(shù)中手動暴露大量的狀態(tài)和方法非常繁瑣。幸運的是,我們可以通過使用構建工具來簡化該操作。當使用單文件組件(SFC)時,我們可以使用 <script setup> 來大幅度地簡化代碼。

<script setup> 中的頂層的導入和變量聲明可在同一組件的模板中直接使用。你可以理解為模板中的表達式和 <script setup> 中的代碼處在同一個作用域中。

里面的代碼會被編譯成組件 setup() 函數(shù)的內容。這意味著與普通的 <script> 只在組件被首次引入的時候執(zhí)行一次不同,<script setup>中的代碼會在每次組件實例被創(chuàng)建的時候執(zhí)行。

官方解答: 

<script setup> 是在單文件組件 (SFC) 中使用組合式 API 的編譯時語法糖。當同時使用 SFC 與組合式 API 時該語法是默認推薦。相比于普通的 <script> 語法,它具有更多優(yōu)勢:

  • 更少的樣板內容,更簡潔的代碼。
  • 能夠使用純 TypeScript 聲明 props 和自定義事件。
  • 更好的運行時性能 (其模板會被編譯成同一作用域內的渲染函數(shù),避免了渲染上下文代理對象)。
  • 更好的 IDE 類型推導性能 (減少了語言服務器從代碼中抽取類型的工作)。

setup執(zhí)行是在創(chuàng)建實例之前就是beforeCreate執(zhí)行,所以setup函數(shù)中的this還不是組件的實例,而是undefined,setup是同步的。

setup?: (this: void, props: Readonly<LooseRequired<Props & UnionToIntersection<ExtractOptionProp<Mixin>> & UnionToIntersection<ExtractOptionProp<Extends>>>>, ctx: SetupContext<E>) => Promise<RawBindings> | RawBindings | RenderFunction | void;)

 在上面的代碼中我們了解到了第一個參數(shù)props,還有第二個參數(shù)context。

props是接受父組件傳遞過來的所有的屬性和方法;context是一個對象,這個對象不是響應式的,可以進行解構賦值。存在屬性為attrs:instance.slots,slots: instance.slots,emit: instance.emit。

setup(props, { attrs, slots, emit, expose }) {
? ?...
?}
?或
?setup(props, content) {
? ?const { attrs, slots, emit, expose } = content
?}

這里要注意一下,attrs 和 slots 是有狀態(tài)的對象,它們總是會隨組件本身的更新而更新。這意味著你應該避免對它們進行解構,并始終以 attrs.x 或 slots.x 的方式引用 property。請注意,與 props 不同,attrs 和 slots 的 property 是非響應式的。如果你打算根據(jù) attrs 或 slots 的更改應用副作用,那么應該在 onBeforeUpdate 生命周期鉤子中執(zhí)行此操作。

3.源碼分析

在vue的3.2.3x版本中,處理setup函數(shù)源碼文件位于:node_moudles/@vue/runtime-core/dist/runtime-core.cjs.js文件中。

setupStatefulComponent

下面開始解析一下setupStatefulComponent的執(zhí)行過程:

function setupStatefulComponent(instance, isSSR) {
    var _a;
    const Component = instance.type;
    {
        if (Component.name) {
            validateComponentName(Component.name, instance.appContext.config);
        }
        if (Component.components) {
            const names = Object.keys(Component.components);
            for (let i = 0; i < names.length; i++) {
                validateComponentName(names[i], instance.appContext.config);
            }
        }
        if (Component.directives) {
            const names = Object.keys(Component.directives);
            for (let i = 0; i < names.length; i++) {
                validateDirectiveName(names[i]);
            }
        }
        if (Component.compilerOptions && isRuntimeOnly()) {
            warn(`"compilerOptions" is only supported when using a build of Vue that ` +
                `includes the runtime compiler. Since you are using a runtime-only ` +
                `build, the options should be passed via your build tool config instead.`);
        }
    }
    // 0. create render proxy property access cache
    instance.accessCache = Object.create(null);
    // 1. create public instance / render proxy
    // also mark it raw so it's never observed
    instance.proxy = reactivity.markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers));
    {
        exposePropsOnRenderContext(instance);
    }
    // 2. call setup()
    const { setup } = Component;
    if (setup) {
        const setupContext = (instance.setupContext =
            setup.length > 1 ? createSetupContext(instance) : null);
        setCurrentInstance(instance);
        reactivity.pauseTracking();
        const setupResult = callWithErrorHandling(setup, instance, 0 /* ErrorCodes.SETUP_FUNCTION */, [reactivity.shallowReadonly(instance.props) , setupContext]);
        reactivity.resetTracking();
        unsetCurrentInstance();
        if (shared.isPromise(setupResult)) {
            setupResult.then(unsetCurrentInstance, unsetCurrentInstance);
            if (isSSR) {
                // return the promise so server-renderer can wait on it
                return setupResult
                    .then((resolvedResult) => {
                    handleSetupResult(instance, resolvedResult, isSSR);
                })
                    .catch(e => {
                    handleError(e, instance, 0 /* ErrorCodes.SETUP_FUNCTION */);
                });
            }
            else {
                // async setup returned Promise.
                // bail here and wait for re-entry.
                instance.asyncDep = setupResult;
                if (!instance.suspense) {
                    const name = (_a = Component.name) !== null && _a !== void 0 ? _a : 'Anonymous';
                    warn(`Component <${name}>: setup function returned a promise, but no ` +
                        `<Suspense> boundary was found in the parent component tree. ` +
                        `A component with async setup() must be nested in a <Suspense> ` +
                        `in order to be rendered.`);
                }
            }
        }
        else {
            handleSetupResult(instance, setupResult, isSSR);
        }
    }
    else {
        finishComponentSetup(instance, isSSR);
    }
}

函數(shù)接受兩個參數(shù),一個是組建實例,另一個是是否ssr渲染,接下來是驗證過程,這里的文件是開發(fā)環(huán)境文件, DEV 環(huán)境,則會開始檢測組件中的各種選項的命名,比如 name、components、directives 等,如果檢測有問題,就會在開發(fā)環(huán)境報出警告。

檢測完成之后,進行初始化,生成一個accessCached的屬性對象,該屬性用以緩存渲染器代理屬性,以減少讀取次數(shù)。然后在初始化一個代理的屬性,instance.proxy = reactivity.markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers));這個代理屬性代理了組件的上下文,并且將它設置為觀察原始值,這樣這個代理對象將不會被追蹤。

接下來便是setup的核心邏輯了,如果組件上有setup 函數(shù),繼續(xù)執(zhí)行,如果不存在跳到尾部,執(zhí)行finishComponentSetup(instance, isSSR),完成組件的初始化,否則就會進入 if (setup) 之后的分支條件中。是否執(zhí)行setup生成上下文取決于setup.length > 1 ?createSetupContext(instance) : null。

來看一下setup執(zhí)行上下文究竟有哪些東西:

function createSetupContext(instance) {
    const expose = exposed => {
        if (instance.exposed) {
            warn(`expose() should be called only once per setup().`);
        }
        instance.exposed = exposed || {};
    };
    let attrs;
    {
        // We use getters in dev in case libs like test-utils overwrite instance
        // properties (overwrites should not be done in prod)
        return Object.freeze({
            get attrs() {
                return attrs || (attrs = createAttrsProxy(instance));
            },
            get slots() {
                return reactivity.shallowReadonly(instance.slots);
            },
            get emit() {
                return (event, ...args) => instance.emit(event, ...args);
            },
            expose
        });
    }
}

 expose解析:

可以在 setup() 中使用該 API 來清除地控制哪些內容會明確地公開暴露給組件使用者。

當你在封裝組件時,如果嫌 ref 中暴露的內容過多,不妨用 expose 來約束一下輸出。

import { ref } from 'vue'
export default {
  setup(_, { expose }) {
    const count = ref(0)
    function increment() {
      count.value++
    }
    // 僅僅暴露 increment 給父組件
    expose({
      increment
    })
    return { increment, count }
  }
}

例如當你像上方代碼一樣使用 expose 時,父組件獲取的 ref 對象里只會有 increment 屬性,而 count 屬性將不會暴露出去。

執(zhí)行setup函數(shù) 

在處理完 createSetupContext 的上下文后,組件會停止依賴收集,并且開始執(zhí)行 setup 函數(shù)。

const setupResult = callWithErrorHandling(setup, instance, 0 /* ErrorCodes.SETUP_FUNCTION */, [reactivity.shallowReadonly(instance.props) , setupContext]); 

Vue 會通過 callWithErrorHandling 調用 setup 函數(shù),組件實例instance傳入,這里我們可以看最后一行,是作為 args 參數(shù)傳入的,與上文描述一樣,props 會始終傳入,若是 setup.length <= 1 , setupContext 則為 null。

調用玩setup之后,會重置收集的狀態(tài),reactivity.resetTracking(),接下來是判斷setupResult的類型。

     if (shared.isPromise(setupResult)) {
            setupResult.then(unsetCurrentInstance, unsetCurrentInstance);
            if (isSSR) {
                // return the promise so server-renderer can wait on it
                return setupResult
                    .then((resolvedResult) => {
                    handleSetupResult(instance, resolvedResult, isSSR);
                })
                    .catch(e => {
                    handleError(e, instance, 0 /* ErrorCodes.SETUP_FUNCTION */);
                });
            }
            else {
                // async setup returned Promise.
                // bail here and wait for re-entry.
                instance.asyncDep = setupResult;
                if (!instance.suspense) {
                    const name = (_a = Component.name) !== null && _a !== void 0 ? _a : 'Anonymous';
                    warn(`Component <${name}>: setup function returned a promise, but no ` +
                        `<Suspense> boundary was found in the parent component tree. ` +
                        `A component with async setup() must be nested in a <Suspense> ` +
                        `in order to be rendered.`);
                }
            }
        }

如果 setup 函數(shù)的返回值是 promise 類型,并且是服務端渲染的,則會等待繼續(xù)執(zhí)行。否則就會報錯,說當前版本的 Vue 并不支持 setup 返回 promise 對象。

如果不是 promise 類型返回值,則會通過 handleSetupResult 函數(shù)來處理返回結果。

else {
            handleSetupResult(instance, setupResult, isSSR);
        }
function handleSetupResult(instance, setupResult, isSSR) {
    if (shared.isFunction(setupResult)) {
        // setup returned an inline render function
        if (instance.type.__ssrInlineRender) {
            // when the function's name is `ssrRender` (compiled by SFC inline mode),
            // set it as ssrRender instead.
            instance.ssrRender = setupResult;
        }
        else {
            instance.render = setupResult;
        }
    }
    else if (shared.isObject(setupResult)) {
        if (isVNode(setupResult)) {
            warn(`setup() should not return VNodes directly - ` +
                `return a render function instead.`);
        }
        // setup returned bindings.
        // assuming a render function compiled from template is present.
        {
            instance.devtoolsRawSetupState = setupResult;
        }
        instance.setupState = reactivity.proxyRefs(setupResult);
        {
            exposeSetupStateOnRenderContext(instance);
        }
    }
    else if (setupResult !== undefined) {
        warn(`setup() should return an object. Received: ${setupResult === null ? 'null' : typeof setupResult}`);
    }
    finishComponentSetup(instance, isSSR);
}

 在 handleSetupResult 這個結果捕獲函數(shù)中,首先判斷 setup 返回結果的類型,如果是一個函數(shù),并且又是服務端的行內模式渲染函數(shù),則將該結果作為 ssrRender 屬性;而在非服務端渲染的情況下,會直接當做 render 函數(shù)來處理。

接著會判斷 setup 返回結果如果是對象,就會將這個對象轉換成一個代理對象,并設置為組件實例的 setupState 屬性。

最終還是會跟其他沒有 setup 函數(shù)的組件一樣,調用 finishComponentSetup 完成組件的創(chuàng)建。

finishComponentSetup

function finishComponentSetup(instance, isSSR, skipOptions) {
    const Component = instance.type;
    // template / render function normalization
    // could be already set when returned from setup()
    if (!instance.render) {
        // only do on-the-fly compile if not in SSR - SSR on-the-fly compilation
        // is done by server-renderer
        if (!isSSR && compile && !Component.render) {
            const template = Component.template;
            if (template) {
                {
                    startMeasure(instance, `compile`);
                }
                const { isCustomElement, compilerOptions } = instance.appContext.config;
                const { delimiters, compilerOptions: componentCompilerOptions } = Component;
                const finalCompilerOptions = shared.extend(shared.extend({
                    isCustomElement,
                    delimiters
                }, compilerOptions), componentCompilerOptions);
                Component.render = compile(template, finalCompilerOptions);
                {
                    endMeasure(instance, `compile`);
                }
            }
        }
        instance.render = (Component.render || shared.NOOP);
        // for runtime-compiled render functions using `with` blocks, the render
        // proxy used needs a different `has` handler which is more performant and
        // also only allows a whitelist of globals to fallthrough.
        if (installWithProxy) {
            installWithProxy(instance);
        }
    }
    // support for 2.x options
    {
        setCurrentInstance(instance);
        reactivity.pauseTracking();
        applyOptions(instance);
        reactivity.resetTracking();
        unsetCurrentInstance();
    }
    // warn missing template/render
    // the runtime compilation of template in SSR is done by server-render
    if (!Component.render && instance.render === shared.NOOP && !isSSR) {
        /* istanbul ignore if */
        if (!compile && Component.template) {
            warn(`Component provided template option but ` +
                `runtime compilation is not supported in this build of Vue.` +
                (``) /* should not happen */);
        }
        else {
            warn(`Component is missing template or render function.`);
        }
    }
}

這個函數(shù)的主要作用是獲取并為組件設置渲染函數(shù),對于模板(template)以及渲染函數(shù)的獲取方式有以下三種規(guī)范行為:

1、渲染函數(shù)可能已經存在,通過 setup 返回了結果。例如我們在上一節(jié)講的 setup 的返回值為函數(shù)的情況。

2、如果 setup 沒有返回,則嘗試獲取組件模板并編譯,從 Component.render 中獲取渲染函數(shù),

3、如果這個函數(shù)還是沒有渲染函數(shù),則將 instance.render 設置為空,以便它能從 mixins/extend 等方式中獲取渲染函數(shù)。

這個在這種規(guī)范行為的指導下,首先判斷了服務端渲染的情況,接著判斷沒有 instance.render 存在的情況,當進行這種判斷時已經說明組件并沒有從 setup 中獲得渲染函數(shù),在進行第二種行為的嘗試。從組件中獲取模板,設置好編譯選項后調用Component.render = compile(template, finalCompilerOptions);進行編譯,編譯過程不再贅述。

最后將編譯后的渲染函數(shù)賦值給組件實例的 render 屬性,如果沒有則賦值為 NOOP 空函數(shù)。

接著判斷渲染函數(shù)是否是使用了 with 塊包裹的運行時編譯的渲染函數(shù),如果是這種情況則會將渲染代理設置為一個不同的 has handler 代理陷阱,它的性能更強并且能夠去避免檢測一些全局變量。

至此組件的初始化完畢,渲染函數(shù)也設置結束了。

4.總結

在vue3中,新的setup函數(shù)屬性給我們提供了書寫的便利,其背后的工作量無疑是巨大的,有狀態(tài)的組件的初始化的過程,在 setup 函數(shù)初始化部分我們討論的源碼的執(zhí)行過程,我們不僅學習了 setup 上下文初始化的條件,也明確的知曉了 setup 上下文究竟給我們暴露了哪些屬性,并且從中學到了一個新的 RFC 提案屬性: expose 屬性

我們學習了 setup 函數(shù)執(zhí)行的過程以及 Vue 是如何處理捕獲 setup 的返回結果的。

然后我們講解了組件初始化時,不論是否使用 setup 都會執(zhí)行的 finishComponentSetup 函數(shù),通過這個函數(shù)內部的邏輯我們了解了一個組件在初始化完畢時,渲染函數(shù)設置的規(guī)則。

相關文章

  • vue配置文件自動生成路由和菜單實例代碼

    vue配置文件自動生成路由和菜單實例代碼

    因為不同的用戶有不同的權限,能訪問的頁面是不一樣的,所以我們在寫后臺管理系統(tǒng)時就會遇過這樣的需求:根據(jù)后臺數(shù)據(jù)動態(tài)添加路由和菜單,這篇文章主要給大家介紹了關于vue配置文件自動生成路由和菜單的相關資料,需要的朋友可以參考下
    2021-08-08
  • vue報錯Error:Cannot?find?module?'fs/promises'的解決方式

    vue報錯Error:Cannot?find?module?'fs/promises'的解決方

    最近的項目需要用到vue/cli,但是用cnpm安裝vue/cli的時候報錯了,下面這篇文章主要給大家介紹了關于vue報錯Error:Cannot?find?module?'fs/promises'的解決方式,需要的朋友可以參考下
    2022-11-11
  • 使用vue初用antd 用v-model來雙向綁定Form表單問題

    使用vue初用antd 用v-model來雙向綁定Form表單問題

    這篇文章主要介紹了使用vue初用antd 用v-model來雙向綁定Form表單問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • vue第三方庫中存在擴展運算符報錯問題的解決方案

    vue第三方庫中存在擴展運算符報錯問題的解決方案

    這篇文章主要介紹了vue第三方庫中存在擴展運算符報錯問題,本文給大家分享解決方案,通過結合實例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2022-07-07
  • 使用vue打包進行云服務器上傳的問題

    使用vue打包進行云服務器上傳的問題

    這篇文章主要介紹了使用vue打包進行云服務器上傳,本文給大家介紹的非常詳細,對大家的工作或學習具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-03-03
  • 通過原生vue添加滾動加載更多功能

    通過原生vue添加滾動加載更多功能

    這篇文章主要介紹了通過原生vue添加滾動加載更多功能,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-11-11
  • Vue項目如何實現(xiàn)切換主題色思路

    Vue項目如何實現(xiàn)切換主題色思路

    這篇文章主要介紹了Vue項目如何實現(xiàn)切換主題色思路,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • vue2.0實現(xiàn)移動端的輸入框實時檢索更新列表功能

    vue2.0實現(xiàn)移動端的輸入框實時檢索更新列表功能

    最近小編在做vue2.0的項目,遇到移動端實時檢索搜索更新列表的效果,下面腳本之家小編給大家?guī)砹藇ue2.0 移動端的輸入框實時檢索更新列表功能的實例代碼,感興趣的朋友參考下吧
    2018-05-05
  • 讓Vue響應Map或Set的變化操作

    讓Vue響應Map或Set的變化操作

    這篇文章主要介紹了讓Vue響應Map或Set的變化操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • vue實現(xiàn)百分比占比條效果

    vue實現(xiàn)百分比占比條效果

    這篇文章主要為大家詳細介紹了vue實現(xiàn)百分比占比條效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09

最新評論