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

Vue組件實現(xiàn)原理詳細分析

 更新時間:2023年01月18日 09:09:32   作者:volit_  
這篇文章主要介紹了Vue組件基礎(chǔ)操作,組件是vue.js最強大的功能之一,而組件實例的作用域是相互獨立的,這就意味著不同組件之間的數(shù)據(jù)無法相互進行直接的引用

1.渲染組件

從用戶的角度來看,一個有狀態(tài)的組件實際上就是一個選項對象。

const Componetn = {
    name: "Button",
    data() {
        return {
            val: 1
        }
    }
}

而對于渲染器來說,一個有狀態(tài)的組件實際上就是一個特殊的vnode。

const vnode = {
    type: Component,
    props: {
        val: 1
    },
}

通常來說,組件渲染函數(shù)的返回值必須是其組件本身的虛擬DOM。

const Component = {
    name: "Button",
    render() {
        return {
            type: 'button',
            children: '按鈕'
        }
    }
}

這樣在渲染器中,就可以調(diào)用組件的render方法來渲染組件了。

function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render } = componentOptions;
    const subTree = render();
    patch(null, subTree, container, anchor);
}

2.組件的狀態(tài)與自更新

在組件中,我們約定組件使用data函數(shù)來定義組件自身的狀態(tài),同時可以在渲染函數(shù)中,調(diào)用this訪問到data中的狀態(tài)。

const Component = {
    name: "Button",
    data() {
        return {
            val: 1
        }
    }
    render() {
        return {
            type: 'button',
            children: `${this.val}`
        }
    }
}
function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render, data } = componentOptions;
    const state = reactive(data); // 將data封裝成響應(yīng)式對象
    effect(() => {
        const subTree = render.call(state,state); // 將data本身指定為render函數(shù)調(diào)用過程中的this
    	patch(null, subTree, container, anchor);
    });
}

但是,響應(yīng)式數(shù)據(jù)修改的同時,相對應(yīng)的組件也會重新渲染,當多次修改組件狀態(tài)時,組件將會連續(xù)渲染多次,這樣的性能開銷明顯是很大的。因此,我們需要實現(xiàn)一個任務(wù)緩沖隊列,來讓組件渲染只會運行在最后一次修改操作之后。

const queue = new Set();
let isFlushing = false;
const p = Promise.resolve();
function queueJob(job) {
    queue.add(job);
    if(!isFlushing) {
        isFlushing = true;
        p.then(() => {
            try {
                queue.forEach(job=>job());
            } finally {
                isFlushing = false;
                queue.length = 0;
            }
        })
    }
}
function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render, data } = componentOptions;
    const state = reactive(data); // 將data封裝成響應(yīng)式對象
    effect(() => {
        const subTree = render.call(state,state); // 將data本身指定為render函數(shù)調(diào)用過程中的this
    	patch(null, subTree, container, anchor);
    }, {
        scheduler: queueJob
    });
}

3.組件實例和生命周期

組件實例實際上就是一個狀態(tài)合集,它維護著組件運行過程中的所有狀態(tài)信息。

function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render, data } = componentOptions;
    const state = reactive(data); // 將data封裝成響應(yīng)式對象
    const instance = {
        state,
        isMounted: false, // 組件是否掛載
        subTree: null // 組件實例
    }
    vnode.component = instance;
    effect(() => {
        const subTree = render.call(state,state); // 將data本身指定為render函數(shù)調(diào)用過程中的this
    	if(!instance.isMounted) {
            patch(null, subTree, container, anchor);
            instance.isMounted = true;
		} else{
            ptach(instance.subTree, subTree, container, anchor);
        }
        instance.subTree = subTree; // 更新組件實例
    }, {
        scheduler: queueJob
    });
}

因為isMounted這個狀態(tài)可以區(qū)分組件的掛載和更新,因此我們可以在這個過程中,很方便的插入生命周期鉤子。

function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render, data, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated } = componentOptions;
    beforeCreate && beforeCreate(); // 在狀態(tài)創(chuàng)建之前,調(diào)用beforeCreate鉤子
    const state = reactive(data); // 將data封裝成響應(yīng)式對象
    const instance = {
        state,
        isMounted: false, // 組件是否掛載
        subTree: null // 組件實例
    }
    vnode.component = instance;
    created && created.call(state); // 狀態(tài)創(chuàng)建完成后,調(diào)用created鉤子
    effect(() => {
        const subTree = render.call(state,state); // 將data本身指定為render函數(shù)調(diào)用過程中的this
    	if(!instance.isMounted) { 
            beforeMount && beforeMount.call(state); // 掛載到真實DOM前,調(diào)用beforeMount鉤子
            patch(null, subTree, container, anchor);
            instance.isMounted = true;
            mounted && mounted.call(state); // 掛載到真實DOM之后,調(diào)用mounted鉤子
		} else{
            beforeUpdate && beforeUpdate.call(state); // 組件更新狀態(tài)掛載到真實DOM之前,調(diào)用beforeUpdate鉤子
            ptach(instance.subTree, subTree, container, anchor);
        	updated && updated.call(state); // 組件更新狀態(tài)掛載到真實DOM之后,調(diào)用updated鉤子
        }
        instance.subTree = subTree; // 更新組件實例
    }, {
        scheduler: queueJob
    });
}

4.props與組件狀態(tài)的被動更新

通常,我們會指定組件接收到的props。因此,對于一個組件的props將會有兩部分的定義:傳遞給組件的props和組件定義的props。

const Component = {
    name: "Button",
    props: {
        name: String
    }
}
function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render, data, props: propsOptions, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated } = componentOptions;
    beforeCreate && beforeCreate(); // 在狀態(tài)創(chuàng)建之前,調(diào)用beforeCreate鉤子
    const state = reactive(data); // 將data封裝成響應(yīng)式對象
    // 調(diào)用 resolveProps 函數(shù)解析出最終的 props 數(shù)據(jù)與 attrs 數(shù)據(jù)
    const [props, attrs] = resolveProps(propsOptions, vnode.props);
    const instance = {
        state,
        // 將解析出的 props 數(shù)據(jù)包裝為 shallowReactive 并定義到組件實例上
        props: shallowReactive(props),
        isMounted: false, // 組件是否掛載
        subTree: null // 組件實例
    }
    vnode.component = instance;
    // ...
}
function resolveProps(options, propsData) {
    const props = {}; // 存儲定義在組件中的props屬性
    const attrs = {}; // 存儲沒有定義在組件中的props屬性
    for(const key in propsData ) {
    	if(key in options) {
            props[key] = propsData[key];
        } else {
            attrs[key] = propsData[key];
        }
    }
    return [props, attrs];
}

我們把由父組件自更新所引起的子組件更新叫作子組件的被動更新。當子組件發(fā)生被動更新時,我們需要做的是:

  • 檢測子組件是否真的需要更新,因為子組件的 props 可能是不變的;
  • 如果需要更新,則更新子組件的 props、slots 等內(nèi)容。
function patchComponet(n1, n2, container) {
 	const instance = (n2.component = n1.component);
    const { props } = instance;
    if(hasPropsChanged(n1.props, n2.props)) {
        // 檢查是否需要更新props
        const [nextProps] = resolveProps(n2.type.props, n2.props);
        for(const k in nextProps) {
            // 更新props
            props[k] = nextProps[k];
        }
        for(const k in props) {
            // 刪除沒有的props
            if(!(k in nextProps)) delete props[k];
        }
    }
}
function hasPropsChanged( prevProps, nextProps) {
    const nextKeys = Object.keys(nextProps);
    if(nextKeys.length !== Object.keys(preProps).length) {
        // 如果新舊props的數(shù)量不對等,說明新舊props有改變
        return true;
    }
    for(let i = 0; i < nextKeys.length; i++) {
        // 如果新舊props的屬性不對等,說明新舊props有改變
        const key = nextKeys[i];
        if(nextProps[key] !== prevProps[key]) return true;
    }
    return false;
}

由于props數(shù)據(jù)與組件本身的數(shù)據(jù)都需要暴露到渲染函數(shù)中,并使渲染函數(shù)能夠通過this訪問它們,因此我們需要封裝一個渲染上下文對象。

function mountComponent(vnode, container, anchor) {
    // ...
    const instance = {
        state,
        // 將解析出的 props 數(shù)據(jù)包裝為 shallowReactive 并定義到組件實例上
        props: shallowReactive(props),
        isMounted: false, // 組件是否掛載
        subTree: null // 組件實例
    }
    vnode.component = instance;
    const renderContext = next Proxy(instance, {
        get(t, k, r) {
            const {state, props} = t;
            if(state && k in state) {
                return state[k];
            } else if (k in props) [
                return props[k];
            ] else {
                console.error("屬性不存在");
            }
        },
        set(t, k, v, r) {
            const { state, props } = t;
            if(state && k in state) {
                state[k] = v;
            } else if(k in props) {
                props[k] = v;
            } else {
                console.error("屬性不存在");
            }
        }
    });
    // 生命周期函數(shù)調(diào)用時要綁定渲染上下文對象
    created && created.call(renderContext);
    // ...
}

5.setup函數(shù)的作用與實現(xiàn)

setup函數(shù)時Vue3新增的組件選項,有別于Vue2中的其他組件選項,setup函數(shù)主要用于配合組合式API,為用戶提供一個地方,用于創(chuàng)建組合邏輯、創(chuàng)建響應(yīng)式數(shù)據(jù)、創(chuàng)建通用函數(shù)、注冊生命周期鉤子等。在組件的整個生命周期中,setup函數(shù)只會在被掛載的時候執(zhí)行一次,它的返回值可能有兩種情況:

  • 返回一個函數(shù),該函數(shù)作為該組件的render函數(shù)
  • 返回一個對象,該對象中包含的數(shù)據(jù)將暴露給模板

此外,setup函數(shù)接收兩個參數(shù)。第一個參數(shù)是props數(shù)據(jù)對象,另一個是setupContext是和組件接口相關(guān)的一些重要數(shù)據(jù)。

cosnt { slots, emit, attrs, expose } = setupContext;
/**
	slots: 組件接收到的插槽
	emit: 一個函數(shù),用來發(fā)射自定義事件
	attrs:沒有顯示在組件的props中聲明的屬性
	expose:一個函數(shù),用來顯式地對外暴露組件數(shù)據(jù)
*/

下面我們來實現(xiàn)一下setup組件選項。

function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render, data, setup, /* ... */ } = componentOptions;
    beforeCreate && beforeCreate(); // 在狀態(tài)創(chuàng)建之前,調(diào)用beforeCreate鉤子
    const state = reactive(data); // 將data封裝成響應(yīng)式對象
    const [props, attrs] = resolveProps(propsOptions, vnode.props);
    const instance = {
        state,
        props: shallowReactive(props),
        isMounted: false, // 組件是否掛載
        subTree: null // 組件實例
    }
    const setupContext = { attrs };
    const setupResult = setup(shallowReadOnly(instance.props), setupContext);
    let setupState = null;
    if(typeof setResult === 'function') {
        if(render) console.error('setup函數(shù)返回渲染函數(shù),render選項將被忽略');
        render = setupResult;
    } else {
        setupState = setupResult;
    }
    vnode.component = instance;
    const renderContext = next Proxy(instance, {
        get(t, k, r) {
            const {state, props} = t;
            if(state && k in state) {
                return setupState[k]; // 增加對setupState的支持
            } else if (k in props) [
                return props[k];
            ] else {
                console.error("屬性不存在");
            }
        },
        set(t, k, v, r) {
            const { state, props } = t;
            if(state && k in state) {
                setupState[k] = v; // 增加對setupState的支持
            } else if(k in props) {
                props[k] = v;
            } else {
                console.error("屬性不存在");
            }
        }
    });
    // 生命周期函數(shù)調(diào)用時要綁定渲染上下文對象
    created && created.call(renderContext);
}

6.組件事件和emit的實現(xiàn)

在組件中,我們可以使用emit函數(shù)發(fā)射自定義事件。

function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type;
    const { render, data, setup, /* ... */ } = componentOptions;
    beforeCreate && beforeCreate(); // 在狀態(tài)創(chuàng)建之前,調(diào)用beforeCreate鉤子
    const state = reactive(data); // 將data封裝成響應(yīng)式對象
    const [props, attrs] = resolveProps(propsOptions, vnode.props);
    const instance = {
        state,
        props: shallowReactive(props),
        isMounted: false, // 組件是否掛載
        subTree: null // 組件實例
    }
    function emit(event, ...payload) {
        const eventName = `on${event[0].toUpperCase() + event.slice(1)}`;
        const handler = instance.props[eventName];
        if(handler) {
            handler(...payload);
        } else {
            console.error('事件不存在');
        }
    }
    const setupContext = { attrs, emit };
    // ...
}

由于沒有在組件props中聲明的屬性不會被添加到props中,因此所有的事件都將不會被添加到props中。對此,我們需要對resolveProps函數(shù)進行一些特別處理。

function resolveProps(options, propsData) {
    const props = {}; // 存儲定義在組件中的props屬性
    const attrs = {}; // 存儲沒有定義在組件中的props屬性
    for(const key in propsData ) {
    	if(key in options || key.startWidth('on')) {
            props[key] = propsData[key];
        } else {
            attrs[key] = propsData[key];
        }
    }
    return [props, attrs];
}

7.插槽的工作原理及實現(xiàn)

顧名思義,插槽就是指組件會預(yù)留一個槽位,該槽位中的內(nèi)容需要由用戶來進行插入。

<templete>
	<header><slot name="header"></slot></header>
    <div>
        <slot name="body"></slot>
    </div>
    <footer><slot name="footer"></slot></footer>
</templete>

在父組件中使用的時候,可以這樣來使用插槽:

<templete>
	<Component>
    	<templete #header>
            <h1>
                標題
            </h1>
        </templete>
        <templete #body>
        	<section>內(nèi)容</section>
        </templete>
        <tempelte #footer>
            <p>
                腳注
            </p>
        </tempelte>
    </Component>
</templete>

而上述父組件將會被編譯為如下函數(shù):

function render() {
    retuen {
        type: Component,
        children: {
            header() {
                return { type: 'h1', children: '標題' }
            },
            body() {
                return { type: 'section', children: '內(nèi)容' }
            },
            footer() {
                return { type: 'p', children: '腳注' }
            }
        }
    }
}

而Component組件將會被編譯為:

function render() {
    return [
        {
            type: 'header',
            children: [this.$slots.header()]
        },
        {
            type: 'bdoy',
            children: [this.$slots.body()]
        },
        {
            type: 'footer',
            children: [this.$slots.footer()]
        }
    ]
}

在mountComponent函數(shù)中,我們就只需要直接取vnode的children對象就可以了。當然我們同樣需要對slots進行一些特殊處理。

function mountComponent(vnode, container, anchor) {
    // ...
    const slots = vnode.children || {};
    const instance = {
        state,
        props: shallowReactive(props),
        isMounted: false, // 組件是否掛載
        subTree: null, // 組件實例
    	slots
    }
    const setupContext = { attrs, emit, slots };
    const renderContext = next Proxy(instance, {
        get(t, k, r) {
            const {state, props} = t;
            if(k === '$slots') { // 對slots進行一些特殊處理
                return slots;
            }
            // ...
        },
        set(t, k, v, r) {
            // ...
        }
    });
    // ...
}

8.注冊生命周期

在setup中,有一部分組合式API是用來注冊生命周期函數(shù)鉤子的。對于生命周期函數(shù)的獲取,我們可以定義一個currentInstance變量存儲當前正在初始化的實例。

let currentInstance = null;
function setCurrentInstance(instance) {
    currentInstance = instance;
}

然后我們在組件實例中添加mounted數(shù)組,用來存儲當前組件的mounted鉤子函數(shù)。

function mountComponent(vnode, container, anchor) {
    // ...
    const slots = vnode.children || {};
    const instance = {
        state,
        props: shallowReactive(props),
        isMounted: false, // 組件是否掛載
        subTree: null, // 組件實例
    	slots,
        mounteds
    }
    const setupContext = { attrs, emit, slots };
    // 在setup執(zhí)行之前,設(shè)置當前實例
    setCurrentInstance(instance);
    const setupResult = setup(shallowReadonly(instance.props),setupContext);
	//執(zhí)行完后重置
    setCurrentInstance(null);
    // ...
}

然后就是onMounted本身的實現(xiàn)和執(zhí)行時機了。

function onMounted(fn) {
    if(currentInstance) {
        currentInstace.mounteds.push(fn);
    } else {
        console.error("onMounted鉤子只能在setup函數(shù)中執(zhí)行");
    }
}
function mountComponent(vnode, container, anchor) {
    // ...
    effect(() => {
        const subTree = render.call(state,state); // 將data本身指定為render函數(shù)調(diào)用過程中的this
    	if(!instance.isMounted) { 
            beforeMount && beforeMount.call(state); // 掛載到真實DOM前,調(diào)用beforeMount鉤子
            patch(null, subTree, container, anchor);
            instance.isMounted = true;
            instance.mounted && instance.mounted.forEach( hook => {
                hook.call(renderContext);
            }) // 掛載到真實DOM之后,調(diào)用mounted鉤子
		} else{
            // ...
        }
        instance.subTree = subTree; // 更新組件實例
    }, {
        scheduler: queueJob
    });
}

到此這篇關(guān)于Vue組件實現(xiàn)原理詳細分析的文章就介紹到這了,更多相關(guān)Vue組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue3父子組件相互調(diào)用方法詳解

    vue3父子組件相互調(diào)用方法詳解

    在vue3項目開發(fā)中,我們常常會遇到父子組件相互調(diào)用的場景,下面主要以setup語法糖格式詳細聊聊父子組件那些事兒,并通過代碼示例介紹的非常詳細,具有一定的參考價值,需要的朋友可以參考下
    2024-05-05
  • vite構(gòu)建vue3項目的全過程記錄

    vite構(gòu)建vue3項目的全過程記錄

    vite是VUE3創(chuàng)建項目的工具,項目大了之后,性能明顯優(yōu)于webpack,下面這篇文章主要給大家介紹了關(guān)于vite構(gòu)建vue3項目的相關(guān)資料,需要的朋友可以參考下
    2023-01-01
  • vue使用rem實現(xiàn) 移動端屏幕適配

    vue使用rem實現(xiàn) 移動端屏幕適配

    這篇文章主要介紹了vue使用rem實現(xiàn) 移動端屏幕適配的相關(guān)知識,通過實例代碼介紹了vue用rem布局的實現(xiàn)代碼,需要的朋友可以參考下
    2018-09-09
  • 在vue中使用回調(diào)函數(shù),this調(diào)用無效的解決

    在vue中使用回調(diào)函數(shù),this調(diào)用無效的解決

    這篇文章主要介紹了在vue中使用回調(diào)函數(shù),this調(diào)用無效的解決,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • elementUI select組件使用及注意事項詳解

    elementUI select組件使用及注意事項詳解

    這篇文章主要介紹了elementUI select組件使用及注意事項詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-05-05
  • vue數(shù)據(jù)控制視圖源碼解析

    vue數(shù)據(jù)控制視圖源碼解析

    本篇內(nèi)容給大家詳細分析了關(guān)于vue數(shù)據(jù)控制視圖的源碼以及重點做了注釋,有興趣的朋友參考學習下。
    2018-03-03
  • 基于vue-cli 路由 實現(xiàn)類似tab切換效果(vue 2.0)

    基于vue-cli 路由 實現(xiàn)類似tab切換效果(vue 2.0)

    這篇文章主要介紹了基于vue-cli 路由 實現(xiàn)類似tab切換效果(vue 2.0),非常不錯,具有一定的參考借鑒價值 ,需要的朋友可以參考下
    2019-05-05
  • vue中tinymce的使用實例詳解

    vue中tinymce的使用實例詳解

    TinyMCE Vue是TinyMCE官方發(fā)布的Vue組件,可以更輕松地在Vue應(yīng)用程序中使用TinyMCE,這篇文章主要介紹了vue中tinymce的使用,需要的朋友可以參考下
    2022-11-11
  • vue使用npm發(fā)布自己的公網(wǎng)包

    vue使用npm發(fā)布自己的公網(wǎng)包

    本文主要介紹了vue使用npm發(fā)布自己的公網(wǎng)包,通過創(chuàng)建一個簡單的npm包,本文詳細闡述了從創(chuàng)建到發(fā)布的整個過程,具有一定的參考價值,感興趣的可以了解一下
    2023-08-08
  • Vue watch 偵聽對象屬性詳解

    Vue watch 偵聽對象屬性詳解

    Vue的watch偵聽器格式有兩種:方法格式和對象格式的偵聽器,這篇文章主要介紹了Vue watch 偵聽對象屬性相關(guān)知識,需要的朋友可以參考下
    2023-04-04

最新評論

西盟| 大田县| 伊川县| 衢州市| 满洲里市| 彝良县| 贡觉县| 岱山县| 澜沧| 原平市| 东丽区| 林甸县| 桦甸市| 林西县| 卓资县| 剑河县| 巴东县| 任丘市| 宣化县| 株洲市| 海城市| 九寨沟县| 天等县| 通道| 广饶县| 华蓥市| 西乡县| 民丰县| 旌德县| 宁南县| 晋城| 三河市| 宁夏| 太谷县| 文化| 新建县| 偃师市| 芦山县| 江门市| 千阳县| 驻马店市|