自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Vue3 源碼解析計劃之Setup,組件渲染前的初始化過程是怎樣的?

開發(fā) 前端
本文中主要分析了組件的初始化過程,主要包括創(chuàng)建組件實例和設(shè)置組件實例,通過進一步細(xì)節(jié)的深入,了解渲染上下文的代理過程,了解了Composition API中的setup 啟動函數(shù)執(zhí)行的時機。

[[439970]]

1寫在前面

Vue3允許在編寫組件的時候添加一個setup啟動函數(shù),作為Composition API邏輯組織的入口。那么渲染前的初始化過程是怎樣的呢?

2setup啟動函數(shù)

在setup函數(shù)內(nèi)部,定義了一個響應(yīng)式對象state,通過reactive API創(chuàng)建。state對象有name和age兩個屬性,模板中引用到的變量state和函數(shù)變量add包含在setup函數(shù)的返回對象中。

  1. <template> 
  2.     <div> 
  3.     <h1>我的名字:{{state.name}}</h1> 
  4.     <h1>我的年齡:{{state.age}}</h1> 
  5.     <button>過年了,又長了一歲</button> 
  6.   </div> 
  7. </template> 
  8. <script> 
  9. import {reactive} from "vue"
  10.    
  11. export default define{ 
  12.     setup(){ 
  13.     const state = reactive({ 
  14.         name:"yichuan"
  15.       age:18 
  16.     }); 
  17.     function add(){ 
  18.         state.age++; 
  19.     } 
  20.      
  21.     return
  22.         state, 
  23.       add 
  24.     } 
  25.   } 
  26. </script> 

 

我們在vue2中知道是在props、data、methods、computed等options中定義一些變量,在組件初始化階段,vue2內(nèi)部會處理這些options,即把定義的變量添加到組件實例上,等模板變異成render函數(shù)時,內(nèi)部通過with(this){}的語法去訪問在組件實例中的變量。

3創(chuàng)建和設(shè)置組件實例

組件實例的設(shè)置函數(shù)setupComponent流程是:

  • 判斷是否是一個有狀態(tài)組件
  • 初始化props
  • 初始化插槽
  • 設(shè)置有狀態(tài)的組件實例
  • 返回組件實例
  1. function setupComponent(instance,isSSR=false){ 
  2.  const {props,children,shapeFlag}= instance.vnode; 
  3.   //判斷是否是一個有狀態(tài)的組件 
  4.   const isStateful = shapeFlag & 4; 
  5.   //初始化 props 
  6.   initProps(instance,props,isStateful,isSSR); 
  7.   //初始化 插槽 
  8.   initSlots(instance,children); 
  9.   //設(shè)置有狀態(tài)的組件實例 
  10.   const setupResult = isStateful  
  11.      ? setupStatefulComponent(instance,isSSR)  
  12.      : undefined; 
  13.    
  14.   return setupResult; 

在函數(shù)setupStatefulComponent的執(zhí)行過程中,流程如下:

  • 創(chuàng)建渲染代理的屬性訪問緩存
  • 創(chuàng)建渲染上下文的代理
  • 判斷處理setup函數(shù)
    • 如果setup函數(shù)帶有參數(shù),則創(chuàng)建一個setupContext
    • 執(zhí)行setup函數(shù),獲取結(jié)果
    • 處理setup執(zhí)行結(jié)果
  1. function setupStatefulComponent(instance,isSSR){ 
  2.  const Component = instance.type; 
  3.   //創(chuàng)建渲染代理的屬性訪問緩存 
  4.   instance.accessCache = {}; 
  5.   //創(chuàng)建渲染上下文的代理 
  6.   instance.proxy = new Proxy(instance.ctx,PublicInstanceProxyHandlers); 
  7.   //判斷處理setup函數(shù) 
  8.   const {setup} = Component; 
  9.   if(setup){ 
  10.    //如果setup函數(shù)帶有參數(shù),則創(chuàng)建一個setupContext 
  11.    const setupContext = ( 
  12.       instance.setupContext = setup.length > 1  
  13.       ? createSetupContext(instance)  
  14.       : null
  15.      
  16.     //執(zhí)行setup函數(shù),獲取結(jié)果 
  17.     const setupResult = callWithErrorHandling( 
  18.       setup, 
  19.       instance, 
  20.       0,/*SETUP_FUNCTION*/ 
  21.      [instance.props,setupContext] 
  22.     ) 
  23.      
  24.     //處理setup執(zhí)行結(jié)果 
  25.     handleSetupResult(instance,setupResult); 
  26.      
  27.   }else
  28.    //完成組件實例的設(shè)置 
  29.     finishComponentSetup(instance); 
  30.   } 
  31.    

在vue2中也有代理模式:

  • props求值后的數(shù)據(jù)存儲在this._props中
  • data定義的數(shù)據(jù)存儲在this._data中

在vue3中,為了維護方便,把組件中不通用狀態(tài)的數(shù)據(jù)存儲到不同的屬性中,比如:存儲到setupState、ctx、data、props中。在執(zhí)行組件渲染函數(shù)的時候,直接訪問渲染上下文instance.ctx中的屬性,做一層proxy對渲染上下文instance.ctx屬性的訪問和修改,代理到setupState、ctx、data、props中數(shù)據(jù)的訪問和修改。

4創(chuàng)建渲染上下文代理

創(chuàng)建渲染上下文代理,使用了proxy的set、get、has三個屬性。

我們第一次獲取key對應(yīng)的數(shù)據(jù)后,利用accessCache[key]去緩存數(shù)據(jù)。下次再根據(jù)key查找數(shù)據(jù),直接通過accessCache[key]獲取對應(yīng)的值,不需要依次調(diào)用hasOwn去判斷。

  1. get({ _: instance }: ComponentRenderContext, key: string) { 
  2.     const { ctx, setupState, data, props, accessCache, type, appContext } = 
  3.       instance 
  4.  
  5.     // for internal formatters to know that this is a Vue instance 
  6.     if (__DEV__ && key === '__isVue') { 
  7.       return true 
  8.     } 
  9.  
  10.     // prioritize <script setup> bindings during dev. 
  11.     // this allows even properties that start with _ or $ to be used - so that 
  12.     // it aligns with the production behavior where the render fn is inlined and 
  13.     // indeed has access to all declared variables. 
  14.     if ( 
  15.       __DEV__ && 
  16.       setupState !== EMPTY_OBJ && 
  17.       setupState.__isScriptSetup && 
  18.       hasOwn(setupState, key
  19.     ) { 
  20.       return setupState[key
  21.     } 
  22.  
  23.     // data / props / ctx 
  24.     // This getter gets called for every property access on the render context 
  25.     // during render and is a major hotspot. The most expensive part of this 
  26.     // is the multiple hasOwn() calls. It's much faster to do a simple property 
  27.     // access on a plain object, so we use an accessCache object (with null 
  28.     // prototype) to memoize what access type a key corresponds to
  29.     let normalizedProps 
  30.     if (key[0] !== '$') { 
  31.       // data / props / ctx / setupState 
  32.       // 渲染代理的屬性訪問緩存中 
  33.       const n = accessCache![key
  34.       if (n !== undefined) { 
  35.         //從緩存中獲取 
  36.         switch (n) { 
  37.           case AccessTypes.SETUP:    
  38.             return setupState[key
  39.           case AccessTypes.DATA: 
  40.             return data[key
  41.           case AccessTypes.CONTEXT: 
  42.             return ctx[key
  43.           case AccessTypes.PROPS: 
  44.             return props![key
  45.           // default: just fallthrough 
  46.         } 
  47.       } else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) { 
  48.         //從setupState中獲取數(shù)據(jù) 
  49.         accessCache![key] = AccessTypes.SETUP 
  50.         return setupState[key
  51.       } else if (data !== EMPTY_OBJ && hasOwn(data, key)) { 
  52.         //從data中獲取數(shù)據(jù) 
  53.         accessCache![key] = AccessTypes.DATA 
  54.         return data[key
  55.       } else if ( 
  56.         // only cache other properties when instance has declared (thus stable) 
  57.         // props 
  58.         (normalizedProps = instance.propsOptions[0]) && 
  59.         hasOwn(normalizedProps, key
  60.       ) { 
  61.         accessCache![key] = AccessTypes.PROPS 
  62.         return props![key
  63.       } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) { 
  64.         //從ctx中獲取數(shù)據(jù) 
  65.         accessCache![key] = AccessTypes.CONTEXT 
  66.         return ctx[key
  67.       } else if (!__FEATURE_OPTIONS_API__ || shouldCacheAccess) { 
  68.         accessCache![key] = AccessTypes.OTHER 
  69.       } 
  70.     } 
  71.  
  72.     const publicGetter = publicPropertiesMap[key
  73.     let cssModule, globalProperties 
  74.     // public $xxx properties 
  75.     if (publicGetter) { 
  76.       if (key === '$attrs') { 
  77.         track(instance, TrackOpTypes.GET, key
  78.         __DEV__ && markAttrsAccessed() 
  79.       } 
  80.       return publicGetter(instance) 
  81.     } else if ( 
  82.       // css module (injected by vue-loader) 
  83.       (cssModule = type.__cssModules) && 
  84.       (cssModule = cssModule[key]) 
  85.     ) { 
  86.       return cssModule 
  87.     } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) { 
  88.       // user may set custom properties to `this` that start with `$` 
  89.       accessCache![key] = AccessTypes.CONTEXT 
  90.       return ctx[key
  91.     } else if ( 
  92.       // global properties 
  93.       ((globalProperties = appContext.config.globalProperties), 
  94.       hasOwn(globalProperties, key)) 
  95.     ) { 
  96.       if (__COMPAT__) { 
  97.         const desc = Object.getOwnPropertyDescriptor(globalProperties, key)! 
  98.         if (desc.get) { 
  99.           return desc.get.call(instance.proxy) 
  100.         } else { 
  101.           const val = globalProperties[key
  102.           return isFunction(val) ? val.bind(instance.proxy) : val 
  103.         } 
  104.       } else { 
  105.         return globalProperties[key
  106.       } 
  107.     } else if ( 
  108.       __DEV__ && 
  109.       currentRenderingInstance && 
  110.       (!isString(key) || 
  111.         // #1091 avoid internal isRef/isVNode checks on component instance leading 
  112.         // to infinite warning loop 
  113.         key.indexOf('__v') !== 0) 
  114.     ) { 
  115.       if ( 
  116.         data !== EMPTY_OBJ && 
  117.         (key[0] === '$' || key[0] === '_') && 
  118.         hasOwn(data, key
  119.       ) { 
  120.         warn( 
  121.           `Property ${JSON.stringify( 
  122.             key 
  123.           )} must be accessed via $data because it starts with a reserved ` + 
  124.             `character ("$" or "_"and is not proxied on the render context.` 
  125.         ) 
  126.       } else if (instance === currentRenderingInstance) { 
  127.         warn( 
  128.           `Property ${JSON.stringify(key)} was accessed during render ` + 
  129.             `but is not defined on instance.` 
  130.         ) 
  131.       } 
  132.     } 
  133.   } 

注意:如果我們直接給props中的數(shù)據(jù)賦值,在非生產(chǎn)環(huán)境中收到一條警告,因為直接修改props不符合數(shù)據(jù)單向流動的設(shè)計思想。

set函數(shù)的實現(xiàn):

  1. export const PublicInstanceProxyHandlers: ProxyHandler<any> = { 
  2.  set
  3.     { _: instance }: ComponentRenderContext, 
  4.     key: string, 
  5.     value: any 
  6.   ): boolean { 
  7.     const { data, setupState, ctx } = instance 
  8.     if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) { 
  9.       //給setupState賦值 
  10.       setupState[key] = value 
  11.     } else if (data !== EMPTY_OBJ && hasOwn(data, key)) { 
  12.       //給data賦值 
  13.       data[key] = value 
  14.     } else if (hasOwn(instance.props, key)) { 
  15.       //不能直接給props賦值 
  16.       __DEV__ && 
  17.         warn( 
  18.           `Attempting to mutate prop "${key}". Props are readonly.`, 
  19.           instance 
  20.         ) 
  21.       return false 
  22.     } 
  23.     if (key[0] === '$' && key.slice(1) in instance) { 
  24.       //不能給vue內(nèi)部以$開頭的保留屬性賦值 
  25.        
  26.       __DEV__ && 
  27.         warn( 
  28.           `Attempting to mutate public property "${key}". ` + 
  29.             `Properties starting with $ are reserved and readonly.`, 
  30.           instance 
  31.         ) 
  32.       return false 
  33.     } else { 
  34.       if (__DEV__ && key in instance.appContext.config.globalProperties) { 
  35.         Object.defineProperty(ctx, key, { 
  36.           enumerable: true
  37.           configurable: true
  38.           value 
  39.         }) 
  40.       } else { 
  41.         ctx[key] = value 
  42.       } 
  43.     } 
  44.     return true 
  45.   } 

has函數(shù)的實現(xiàn):

  1. has( 
  2.     { 
  3.       _: { data, setupState, accessCache, ctx, appContext, propsOptions } 
  4.     }: ComponentRenderContext, 
  5.     key: string 
  6.   ) { 
  7.     let normalizedProps 
  8.     //依次判斷 
  9.     return ( 
  10.       !!accessCache![key] || 
  11.       (data !== EMPTY_OBJ && hasOwn(data, key)) || 
  12.       (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) || 
  13.       ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) || 
  14.       hasOwn(ctx, key) || 
  15.       hasOwn(publicPropertiesMap, key) || 
  16.       hasOwn(appContext.config.globalProperties, key
  17.     ) 
  18.   } 

5判斷處理setup函數(shù)

  1. //判斷處理setup函數(shù) 
  2. const { setup } = Component 
  3. if (setup) { 
  4.   //如果setup函數(shù)帶參數(shù),則創(chuàng)建了一個setupContext 
  5.   const setupContext = (instance.setupContext = 
  6.                         setup.length > 1 ? createSetupContext(instance) : null
  7.  
  8.   setCurrentInstance(instance) 
  9.   pauseTracking() 
  10.   //執(zhí)行setup函數(shù)獲取結(jié)果 
  11.   const setupResult = callWithErrorHandling( 
  12.     setup, 
  13.     instance, 
  14.     ErrorCodes.SETUP_FUNCTION, 
  15.     [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext] 
  16.   ) 
  17.   resetTracking() 
  18.   unsetCurrentInstance() 
  19.  
  20.   if (isPromise(setupResult)) { 
  21.     setupResult.then(unsetCurrentInstance, unsetCurrentInstance) 
  22.  
  23.     if (isSSR) { 
  24.       // return the promise so server-renderer can wait on it 
  25.       return setupResult 
  26.         .then((resolvedResult: unknown) => { 
  27.         handleSetupResult(instance, resolvedResult, isSSR) 
  28.       }) 
  29.         .catch(e => { 
  30.         handleError(e, instance, ErrorCodes.SETUP_FUNCTION) 
  31.       }) 
  32.     } else if (__FEATURE_SUSPENSE__) { 
  33.       // async setup returned Promise. 
  34.       // bail here and wait for re-entry. 
  35.       instance.asyncDep = setupResult 
  36.     } else if (__DEV__) { 
  37.       warn( 
  38.         `setup() returned a Promise, but the version of Vue you are using ` + 
  39.         `does not support it yet.` 
  40.       ) 
  41.     } 
  42.   } else { 
  43.     //處理setup執(zhí)行結(jié)果 
  44.     handleSetupResult(instance, setupResult, isSSR) 
  45.   } 
  46. else { 
  47.   finishComponentSetup(instance, isSSR) 

6標(biāo)準(zhǔn)化模板或渲染函數(shù)

組件會通過 函數(shù)渲染成DOM,但是我們很少直接改寫render函數(shù)。而是通過這兩種方式:

  • 使用SFC(SIngle File Components)單文件的開發(fā)方式來開發(fā)組件,通過編寫組件的template模板去描述一個組件的DOM結(jié)構(gòu)
  • 還可以不借助webpack編譯,直接引入vue.js,開箱即用,直接在組件對象template屬性中寫組件的模板

Vue.js在web端有runtime-only和runtime-compiled兩個版本,在不是特殊要求的開發(fā)時,推薦使用runtime-only版本,因為它的體積相對更小,而且運行時不用進行編譯,耗時少,性能更優(yōu)秀。對于老舊項目可以使用runtime-compiled,runtime-only和runtime-compiled的區(qū)別在于是否注冊了compile。

compile方法是通過外部注冊的:

  1. let compile; 
  2. function registerRuntimeCompiler(_compile){ 
  3.  compile = _compile; 

compile和組件template屬性存在,render方法不存在的情況,runtime-compiled版本會在Javascript運行時進行模板編譯,生成render函數(shù)。

compile和組件template屬性不存在,組件template屬性存在的情況,由于沒有compile,用的是runtime-only版本,會報警告告訴用戶,想要運行時編譯得使用runtime-compiled版本的vue.js。

在執(zhí)行setup函數(shù)并獲取結(jié)果的時候,使用callWithErrorHandling把setup包裝了一層,有哪些好處呢?

7參考文章

《Vue3核心源碼解析》

《Vue中文社區(qū)》

《Vue3中文文檔》

8寫在最后

 

本文中主要分析了組件的初始化過程,主要包括創(chuàng)建組件實例和設(shè)置組件實例,通過進一步細(xì)節(jié)的深入,了解渲染上下文的代理過程,了解了Composition API中的setup 啟動函數(shù)執(zhí)行的時機。

 

責(zé)任編輯:武曉燕 來源: 前端萬有引力
相關(guān)推薦

2021-12-12 18:31:35

VNode組件Vue3

2022-01-26 11:00:58

源碼層面Vue3

2023-04-27 11:07:24

Setup語法糖Vue3

2021-12-01 08:11:44

Vue3 插件Vue應(yīng)用

2021-12-02 05:50:35

Vue3 插件Vue應(yīng)用

2012-03-13 13:38:42

Java

2023-05-29 09:37:17

Vue3Vite

2025-03-14 10:37:24

SpringSpring IOC容器

2022-02-18 09:39:51

Vue3.0Vue2.0Script Set

2021-07-07 05:00:17

初始化源碼

2023-11-29 08:49:31

Vue.jsData 函數(shù)

2009-06-04 09:26:51

struts 源碼struts 資源文件

2024-08-13 09:26:07

2021-12-14 21:43:13

Vue3函數(shù)computed

2023-08-28 07:25:58

DDE服務(wù)器管理器

2023-10-06 20:57:52

C++聚合成員

2023-11-27 22:55:17

DNS域名解析

2021-05-12 10:25:53

組件驗證漏洞

2016-09-14 22:22:03

Android Vue性能優(yōu)化

2025-02-13 11:11:53

Redis哨兵代碼
點贊
收藏

51CTO技術(shù)棧公眾號