最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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的語法的改進也是大為贊賞,用起來十分方便。對于已經(jīng)熟悉vue2寫法的同學也說,上手還是需要一定的學習成本,有可能目前停留在會寫會用的階段,但是setup帶來哪些改變,以及ref,reactive這兩api內(nèi)部實現(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ù)的內(nèi)容。這意味著與普通的 <script> 只在組件被首次引入的時候執(zhí)行一次不同,<script setup>中的代碼會在每次組件實例被創(chuàng)建的時候執(zhí)行。

官方解答: 

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

  • 更少的樣板內(nèi)容,更簡潔的代碼。
  • 能夠使用純 TypeScript 聲明 props 和自定義事件。
  • 更好的運行時性能 (其模板會被編譯成同一作用域內(nèi)的渲染函數(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 來清除地控制哪些內(nèi)容會明確地公開暴露給組件使用者。

當你在封裝組件時,如果嫌 ref 中暴露的內(nèi)容過多,不妨用 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 調(diào)用 setup 函數(shù),組件實例instance傳入,這里我們可以看最后一行,是作為 args 參數(shù)傳入的,與上文描述一樣,props 會始終傳入,若是 setup.length <= 1 , setupContext 則為 null。

調(diào)用玩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ù),并且又是服務端的行內(nèi)模式渲染函數(shù),則將該結果作為 ssrRender 屬性;而在非服務端渲染的情況下,會直接當做 render 函數(shù)來處理。

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

最終還是會跟其他沒有 setup 函數(shù)的組件一樣,調(diào)用 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ù)可能已經(jīng)存在,通過 setup 返回了結果。例如我們在上一節(jié)講的 setup 的返回值為函數(shù)的情況。

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

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

這個在這種規(guī)范行為的指導下,首先判斷了服務端渲染的情況,接著判斷沒有 instance.render 存在的情況,當進行這種判斷時已經(jīng)說明組件并沒有從 setup 中獲得渲染函數(shù),在進行第二種行為的嘗試。從組件中獲取模板,設置好編譯選項后調(diào)用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ù)內(nèi)部的邏輯我們了解了一個組件在初始化完畢時,渲染函數(shù)設置的規(guī)則。

相關文章

  • Vue3 的響應式和以前有什么區(qū)別,Proxy 無敵?

    Vue3 的響應式和以前有什么區(qū)別,Proxy 無敵?

    這篇文章主要介紹了Vue3 的響應式和以前有什么區(qū)別,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-05-05
  • vue.js中v-on:textInput無法執(zhí)行事件問題的解決過程

    vue.js中v-on:textInput無法執(zhí)行事件問題的解決過程

    大家都知道vue.js通過v-on完成事件處理與綁定,但最近使用v-on的時候遇到了一個問題,所以下面這篇文章主要給大家介紹了關于vue.js中v-on:textInput無法執(zhí)行事件問題的解決過程,需要的朋友可以參考下。
    2017-07-07
  • Vue仿今日頭條實例詳解

    Vue仿今日頭條實例詳解

    這篇文章主要介紹了Vue仿今日頭條實例詳解,并把相關代碼做了說明,對此有興趣的朋友參考下吧。
    2018-02-02
  • vue中使用pdfjs-dist?+?turnjs實現(xiàn)頁面的翻書瀏覽功能

    vue中使用pdfjs-dist?+?turnjs實現(xiàn)頁面的翻書瀏覽功能

    這篇文章主要介紹了vue中使用pdfjs-dist?+?turnjs實現(xiàn)頁面的翻書瀏覽,通過本文學習我們知道了pdfjs-dist 的工作原理是把獲取到的 pbf 的文件的數(shù)據(jù)流, 利用 canvas轉換成圖片,本文通過實例代碼詳解講解,需要的朋友可以參考下
    2022-10-10
  • vue異步axios獲取的數(shù)據(jù)渲染到頁面的方法

    vue異步axios獲取的數(shù)據(jù)渲染到頁面的方法

    今天小編就為大家分享一篇vue異步axios獲取的數(shù)據(jù)渲染到頁面的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-08-08
  • 談談對Vue?Router的理解

    談談對Vue?Router的理解

    vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,適合用于構建單頁面應用,這篇文章主要介紹了對Vue?Router的理解,需要的朋友可以參考下
    2022-11-11
  • vue.js使用v-pre與v-html輸出HTML操作示例

    vue.js使用v-pre與v-html輸出HTML操作示例

    這篇文章主要介紹了vue.js使用v-pre與v-html輸出HTML操作,結合實例形式分析了vue.js基于v-pre與v-html屬性輸出HTML的具體操作技巧,需要的朋友可以參考下
    2018-07-07
  • vue日期時間工具類詳解

    vue日期時間工具類詳解

    這篇文章主要為大家詳細介紹了vue日期時間工具類,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • Vue全局使用less樣式,組件使用全局樣式文件中定義的變量操作

    Vue全局使用less樣式,組件使用全局樣式文件中定義的變量操作

    這篇文章主要介紹了Vue全局使用less樣式,組件使用全局樣式文件中定義的變量操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-10-10
  • Vue.js項目模板搭建圖文教程

    Vue.js項目模板搭建圖文教程

    下面小編就為大家?guī)硪黄猇ue.js項目模板搭建圖文教程。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09

最新評論

合江县| 扬州市| 尉犁县| 江津市| 富宁县| 鹤岗市| 郧西县| 百色市| 张家港市| 武隆县| 临沂市| 尤溪县| 贡山| 灯塔市| 奈曼旗| 怀化市| 邵阳县| 宁化县| 重庆市| 井陉县| 铁岭县| 耒阳市| 南阳市| 太和县| 沁阳市| 龙州县| 江华| 浠水县| 奇台县| 鸡西市| 武清区| 紫云| 永平县| 龙里县| 河南省| 丁青县| 荥阳市| 若羌县| 天等县| 南皮县| 张家界市|