最近2018中文字幕在日韩欧美国产成人片_国产日韩精品一区二区在线_在线观看成年美女黄网色视频_国产精品一区三区五区_国产精彩刺激乱对白_看黄色黄大色黄片免费_人人超碰自拍cao_国产高清av在线_亚洲精品电影av_日韩美女尤物视频网站

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

新聞中心

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

1.寫在前面

2.props與組件的被動(dòng)更新

props

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

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

對(duì)應(yīng)的虛擬DOM是:

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

對(duì)于組件而言:

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

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

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

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

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

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

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

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

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

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

在上面代碼中,沒有定義在組件的props選項(xiàng)中的props數(shù)據(jù)將會(huì)被存儲(chǔ)在attrs對(duì)象中,實(shí)際上還需要對(duì)其進(jìn)行默認(rèn)值處理。

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]
}

組件的被動(dòng)更新

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

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

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

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

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

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

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é)點(diǎn)
}else if(typeof type === Fragement){
//...片段
}else if(typeof type === "object"){
// vnode.type的值是選項(xiàng)對(duì)象,作為組件處理
if(!n1){
//掛載組件
mountComponent(n2, container, anchor);
}else{
//更新組件
patchComponent(n1, n2, anchor);
}
}
}

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

function patchComponent(n1, n2, anchor){
//獲取組件實(shí)例,新舊組件實(shí)例是一樣的
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)容是否有改動(dòng),有改動(dòng)則進(jìn)行組件的更新。

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選項(xiàng)定義和給組件傳遞的props數(shù)據(jù)進(jìn)行處理的。但是由于props數(shù)據(jù)與組件本身的狀態(tài)數(shù)據(jù)都需要暴露到渲染函數(shù)中,渲染函數(shù)中可以通過this進(jìn)行訪問,對(duì)此需要封裝一個(gè)渲染上下文對(duì)象。

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

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

// 創(chuàng)建渲染上下文對(duì)象,本質(zhì)上是組件實(shí)例的代理
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
//代碼省略...
}

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

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

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

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

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

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

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

const Comp = {
props:{
name:String
},
//setup函數(shù)可以返回一個(gè)函數(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ù)是如何設(shè)計(jì)與實(shí)現(xiàn)的呢?

function mountComponent(vnode, container, anchor){ 
const componentOptions = vnode.type;
//從選項(xiàng)組件中取出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);
// 存儲(chǔ)setup返回的數(shù)據(jù)
let setupState = null;
// 判斷setup返回的是函數(shù)還是數(shù)據(jù)對(duì)象
if(typeof setupResult === "function"){
// 報(bào)告沖突
if(render) console.error("setup函數(shù)返回渲染函數(shù),render選項(xiàng)可以忽略");
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的實(shí)現(xiàn)

emit是用于父組件傳遞方法到子組件,是一個(gè)發(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對(duì)象數(shù)據(jù)中尋找對(duì)應(yīng)的事件處理函數(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中尋找對(duì)應(yīng)的事件處理函數(shù)
const handler = instance.props[eventName];
if(handler){
handler(...payload);
}else{
console.error("事件不存在")
}
}

const setupContext = { attrs, emit };

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

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

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.插槽的工作原理與實(shí)現(xiàn)

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

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