日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Vue.js設計與實現(xiàn)之組件的實現(xiàn)原理

1.寫在前面

2.props與組件的被動更新

props

在虛擬DOM中,組件的props和普通html標簽上的屬性差別并不大。

創(chuàng)新互聯(lián)建站專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務,包含不限于網(wǎng)站設計制作、做網(wǎng)站、鄂倫春網(wǎng)絡推廣、小程序開發(fā)、鄂倫春網(wǎng)絡營銷、鄂倫春企業(yè)策劃、鄂倫春品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運營等,從售前售中售后,我們都將竭誠為您服務,您的肯定,是我們最大的嘉獎;創(chuàng)新互聯(lián)建站為所有大學生創(chuàng)業(yè)者提供鄂倫春建站搭建服務,24小時服務熱線:18982081108,官方網(wǎng)址:www.cdcxhl.com

對應的虛擬DOM是:

const vnode = {
type: MyComponent,
props: {
name:"pingping",
age:18
}
}

對于組件而言:

const MyComponent = {
name:"MyComponent",
props:{
name:String,
age: Number
},
render(){
return {
type:"div",
children:`my name is ${this.name}, my age is: ${this.age}`
}
}
}

對于組件而言,需要關(guān)心的props內(nèi)容有兩部分:

  • 為組件傳遞數(shù)據(jù)的props,即vnode.props對象
  • 組件內(nèi)部選項自定義的props,即MyComponent.props

組件在渲染時解析props數(shù)據(jù)需要結(jié)合這兩個選項,最終解析出組件在渲染時需要使用到的props和attrs。

function mountComponent(vnode, container, anchor){
const componentOptions = vnode.type;
// 從組件選項中獲取到的props對象即propsOption
const { render, data,props: propsOption } = componentOptions;

// 在數(shù)據(jù)初始化前
beforeCreate && beforeCreate();
// 將原始數(shù)據(jù)對象data封裝成響應式數(shù)據(jù)
const state = reactive(data());

// 調(diào)用resolveProps 函數(shù)解析最終的props和attrs數(shù)據(jù)
const [props, attrs] = resolveProps(propsOptions, vnode.props);

// 組件實例
const instance = {
// 組件狀態(tài)數(shù)據(jù)
state,
// 組件掛載狀態(tài)
isMounted: false,
// 組件渲染內(nèi)容
subTree: null,
props: shallowReactive(props);
}

// 將組件實例設置在vnode上,方便后續(xù)更新
vnode.component = instance;
//... 代碼省略
}

再看看將props解析成最終的props和attrs的resolveProps函數(shù):

在上面代碼中,沒有定義在組件的props選項中的props數(shù)據(jù)將會被存儲在attrs對象中,實際上還需要對其進行默認值處理。

function resolveProps(options, propsData){
const props = {};
const attrs = {};
//遍歷為組件傳遞的props數(shù)據(jù)
for(const key in propsData){
// 鑒別是否為組件約定的props
if(key in options){
props[key] = propsData[key];
}else{
attrs[key] = propsData[key];
}
}
return [props, attrs]
}

組件的被動更新

其實,子組件的props數(shù)據(jù)本質(zhì)上就是來自于父組件傳遞的,在props發(fā)生變化時,會觸發(fā)父組件的重新渲染。

假定父組件初次要渲染的虛擬DOM:

const vnode = {
type: MyComponent,
props:{
name:"pingping",
age:18
}
}

在name或age的數(shù)據(jù)發(fā)生變化時,父組件的渲染函數(shù)會重新執(zhí)行,從而產(chǎn)生新的虛擬DOM:

const vnode = {
type: MyComponent,
props:{
name:"onechuan",
age:18
}
}

由于父組件要渲染的虛擬DOM內(nèi)容發(fā)生變化,此時就需要進行自更新,在更新時會使用patchComponent函數(shù)進行子組件的更新。

function patch(n1, n2, container, anchor){
if(n1 && n1.type !== n2.type){
unmount(n1);
n1 = null;
}
const {type} = n2;

if(typeof type === "string"){
//...普通元素
}else if(typeof type === Text){
//...文本節(jié)點
}else if(typeof type === Fragement){
//...片段
}else if(typeof type === "object"){
// vnode.type的值是選項對象,作為組件處理
if(!n1){
//掛載組件
mountComponent(n2, container, anchor);
}else{
//更新組件
patchComponent(n1, n2, anchor);
}
}
}

由父組件更新引起的子組件更新叫做子組件的被動更新,在子組件更新時需要檢測子組件是否真的需要更新,如果需要更新則更新子組件的props和slots等內(nèi)容。具體的patchComponent代碼如下所示:

function patchComponent(n1, n2, anchor){
//獲取組件實例,新舊組件實例是一樣的
const instance = (n2.component = n1.component);
const {props} = instance;

if(hasPropsChanged(n1.props, n2.props)){
const [nextProps] = resovleProps(n1.props, n2.props);
// 更新props
for(const k in nextProps){
props[k] = nextProps[k]
}
// 刪除不存在的props
for(const k in props){
if(!(k in nextProps)) delete props[k];
}
}
}

hasPropsChanged函數(shù)用于判斷新舊props內(nèi)容是否有改動,有改動則進行組件的更新。

function hasPropsChanged(prevProps, nextProps){
const nextKeys = Object.keys(nextProps);
cosnt prevKeys = Object.keys(prevProps);
// 新舊數(shù)量是否改變
if(nextKeys.length !== prevKeys.length){
return true
}
// 是否有不相等的props
for(let i = 0; i < nextKeys.length; i++){
const key = nextKeys[i];
if(nextProps[key] !== prevProps[key]) return true
}
return false
}

props和attrs本質(zhì)上都是根據(jù)組件的props選項定義和給組件傳遞的props數(shù)據(jù)進行處理的。但是由于props數(shù)據(jù)與組件本身的狀態(tài)數(shù)據(jù)都需要暴露到渲染函數(shù)中,渲染函數(shù)中可以通過this進行訪問,對此需要封裝一個渲染上下文對象。

function mountComponent(vnode, container, anchor){
// 省略代碼...

// 組件實例
const instance = {
state,
isMounted: false,
subTree: null,
props: shallowReactive(props);
}

// 創(chuàng)建渲染上下文對象,本質(zhì)上是組件實例的代理
const renderContext = new Proxy(instance, {
get(t, k, r){
// 獲取組件自身狀態(tài)和props數(shù)據(jù)
const {state, props} = t;
// 先嘗試讀取自身數(shù)據(jù)
if(state && k in state){
return state[k]
}else if(k in props){
return props[k]
}else{
console.log("不存在");
}
},
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.log("不存在");
}
}
})

created && created.call(renderCOntext
//代碼省略...
}

在上面代碼中,通過為組件實例創(chuàng)建一個代理對象,即渲染上下文對象,對數(shù)據(jù)狀態(tài)攔截實現(xiàn)讀取和設置操作。在渲染函數(shù)或生命周期鉤子中可以通過this讀取數(shù)據(jù)時,會優(yōu)先從組件自身狀態(tài)中獲取,倘若組件自身沒有對應數(shù)據(jù),則從props數(shù)據(jù)中進行讀取。渲染上下文對象其實就是作為渲染函數(shù)和生命周期鉤子的this值。

當然,渲染上下文對象處理的不僅僅是組件自身的數(shù)據(jù)和props數(shù)據(jù),還包括:methods、computed等選項的數(shù)據(jù)和方法。

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

組件的setup函數(shù)是Vue.js3新增的組件選項,主要用于配合組合式api進行建立組合邏輯、創(chuàng)建響應式數(shù)據(jù)、創(chuàng)建通用函數(shù)、注冊生命周期鉤子等能力。在組件的整個生命周期中,setup函數(shù)只會在被掛載時執(zhí)行一次,返回值可以是組件的渲染函數(shù)也可以是暴露出的響應式數(shù)據(jù)到渲染函數(shù)中。

const Comp = {
//setup函數(shù)可以返回一個函數(shù)作為組件的渲染函數(shù)
setup(){
return ()=>{
return {
type:"div",
children:"pingping"
}
}
}
}

但是,這種方式通常用于不是以模板來渲染內(nèi)容,如果組件是模板來渲染內(nèi)容,那么setup函數(shù)就不可以返回函數(shù),否則會與模板編譯的渲染函數(shù)沖突。

返回對象的情況,是將對象的數(shù)據(jù)暴露給模板使用,setup函數(shù)暴露的數(shù)據(jù)可以通過this進行訪問。

const Comp = {
props:{
name:String
},
//setup函數(shù)可以返回一個函數(shù)作為組件的渲染函數(shù)
setup(props, setupContext){
console.log(`my name is ${props.name}`);
const age = ref(18);
// setupContex包含與組件接口相關(guān)的重要數(shù)據(jù)
const {slots, emit, attrs} = setupContext;
return {
age
}
},
render(){
return {
type:"div",
children:`my age is ${this.age}`
}
}
}

那么setup函數(shù)是如何設計與實現(xiàn)的呢?

function mountComponent(vnode, container, anchor){ 
const componentOptions = vnode.type;
//從選項組件中取出setup函數(shù)
let {render, data, setup, /*...*/} = componentOptions;

beforeCreate && beforeCreate();

const state = data ? reactive(data()) : null;
const [props, attrs] = resolveProps(propsOption, vnode.props);

const instance = {
state,
props:shallowReactive(props),
isMounted:false,
subTree:null
}

const setupContext = { attrs };
const setupResult = setup(shallowReadonly(instance.props), setupContext);
// 存儲setup返回的數(shù)據(jù)
let setupState = null;
// 判斷setup返回的是函數(shù)還是數(shù)據(jù)對象
if(typeof setupResult === "function"){
// 報告沖突
if(render) console.error("setup函數(shù)返回渲染函數(shù),render選項可以忽略");
render = setupResult;
}else{
setupState = setupContext;
}

vnode.component = instance;

const renderContext = new 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 if(setupState && k in setupState){
return setupState[k]
}else{
console.log("不存在");
}
},
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 if(setupState && k in setupState){
setupState[k] = v;
}else{
console.log("不存在");
}
}
})
//省略部分代碼...
}

4.組件事件與emit的實現(xiàn)

emit是用于父組件傳遞方法到子組件,是一個發(fā)射事件的自定義事件。

上面組件的虛擬DOM:

const CompVNode = {
type:MyComponent,
props:{
onChange:handler
}
}

const MyComponent = {
name:"MyComponent",
setup(props, {emit}){
emit("change", 1, 1)
return ()=>{
return //...
}
}
}

emit發(fā)射事件的本質(zhì)是:通過事件名稱去props對象數(shù)據(jù)中尋找對應的事件處理函數(shù)并執(zhí)行。

function mountComponent(vnode, container, anchor){ 
// 省略部分代碼

const instance = {
state,
props:shallowReactive(props),
isMounted:false,
subTree:null
}

function emit(event, ...payload){
// 如change -> onChange
const eventName = `on${event[0].toUpperCase() + event.slice(1)}`;
// 根據(jù)處理后的事件名稱去props中尋找對應的事件處理函數(shù)
const handler = instance.props[eventName];
if(handler){
handler(...payload);
}else{
console.error("事件不存在")
}
}

const setupContext = { attrs, emit };

//省略部分代碼...
}

在上面代碼中,其實就是在setupContext對象中添加emit方法,在emit函數(shù)被調(diào)用時,根據(jù)約定對事件名稱便于在props數(shù)據(jù)對象中找到對應的事件處理函數(shù)。最終調(diào)用函數(shù)和傳遞參數(shù),在解析props數(shù)據(jù)時需要對事件類型的props進行處理。

function resolveProps(options, propsData){
const props = {};
const attrs = {};
for(const key in propsData){
if(key in options || key.startWith("on")){
props[key] = propsData[key]
}else{
attrs[key] = propsData[key]
}
}
return [props, attrs]
}

5.插槽的工作原理與實現(xiàn)

插槽就是在組件中預留槽位,具體渲染內(nèi)容由用戶插入:

父組件中使用組件,通過插槽傳入自定義內(nèi)容: