2020-06-03 (Vue-class-Component 源碼分析)

概述

  相對(duì)于typeScript的class寫(xiě)法,是對(duì)當(dāng)前類的封裝(個(gè)人理解)

首先看2.0 js的寫(xiě)法

 const App=Vue.extent({
     data(){return{'hello':'hello'}},
     computed: {
     world() {
        return this.hello + 'world';
       },
   },

   mounted(){
    this.sayHello();
   }
 methods:{
        sayHello() {
                    console.log(this.hello);
                },
             
        }
  })

3.0 ts寫(xiě)法

    import Component from 'vue-class-component';
    
    @Component({
        name: 'App'
    })
    class App extends Vue {
        hello = 'world';
    
        get world() {
            return this.hello + 'world';
        }
    
        mounted() {
            this.sayHello();
        }
    
        sayHello() {
            console.log(this.hello);
        }
    }

疑問(wèn)點(diǎn)

 1:@Component()  是?(@Component就是一個(gè)修飾器, 用來(lái)修改類的行為,更多關(guān)于裝飾器信息請(qǐng)參閱阮老師Es6) 

 2:hello = 'world';  這是啥寫(xiě)法
 js語(yǔ)法中使用的是constructor去給屬性賦值,在chrome上像vue-class-component中定義class是會(huì)報(bào)錯(cuò)的,但vue-class-component中卻又這么做了.
 然后我們看看class通過(guò)webpack + babel-loader解析后會(huì)變成什么樣子 

 轉(zhuǎn)換前
    class App{
    hello='world';
    sathello(){
     console.log(this.hello);
   } 
  }
 // 轉(zhuǎn)換后  掛在原型上
    function App () {
        this.hello = 'world'
    }
    
    App.prototype.sayHello = function () {
        console.log(this.hello);
    }


 3:App類沒(méi)有constructor 構(gòu)造函數(shù)
 4:導(dǎo)出的類沒(méi)有new 直接使用?

重點(diǎn)分析(index.js所做的東西)

// Component實(shí)際上是既作為工廠函數(shù),又作為裝飾器函數(shù)
function Component (options: ComponentOptions<Vue> | VueClass<Vue>): any {
  if (typeof options === 'function') {
    // 區(qū)別一下。這里的命名雖然是工廠,其實(shí)它才是真正封裝裝飾器邏輯的函數(shù)
    return componentFactory(options)
  }
  return function (Component: VueClass<Vue>) {
    return componentFactory(Component, options)
  }
}

componentFactory此方法的封裝所要做的東西

import Vue, { ComponentOptions } from 'vue'
import { copyReflectionMetadata, reflectionIsSupported } from './reflect'
import { VueClass, DecoratedClass } from './declarations'
import { collectDataFromConstructor } from './data'
import { hasProto, isPrimitive, warn } from './util'

export const $internalHooks = [
  'data',
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeDestroy',
  'destroyed',
  'beforeUpdate',
  'updated',
  'activated',
  'deactivated',
  'render',
  'errorCaptured', // 2.5
  'serverPrefetch' // 2.6
]

export function componentFactory (
  Component: VueClass<Vue>,
  options: ComponentOptions<Vue> = {}
): VueClass<Vue> {
  // 為component的name賦值
  options.name = options.name || (Component as any)._componentTag || (Component as any).name
  // prototype props.
  // 獲取原型
  const proto = Component.prototype
  // 遍歷原型
  Object.getOwnPropertyNames(proto).forEach(function (key) {
// 如果是constructor, 則不處理
if (key === 'constructor') {
  return
}

// hooks
// 如果原型屬性(方法)名是vue生命周期鉤子名,則直接作為鉤子函數(shù)掛載在options最外層
if ($internalHooks.indexOf(key) > -1) {
  options[key] = proto[key]
  return
}
// getOwnPropertyDescriptor 返回描述對(duì)象
const descriptor = Object.getOwnPropertyDescriptor(proto, key)!
// void 0 === undefined
if (descriptor.value !== void 0) {
  // methods
  // 如果是方法名就掛載到methods上
  if (typeof descriptor.value === 'function') {
    (options.methods || (options.methods = {}))[key] = descriptor.value
  } else {
    // typescript decorated data
    // 把成員變量作為mixin放到options上,一個(gè)變量一個(gè)mixin,而不是直接統(tǒng)計(jì)好放到data或者同一個(gè)mixin中
    // 因?yàn)閐ata我們已經(jīng)作為了保留字段,可以在類中聲明成員方法data()和options中聲明data同樣的方法聲明變量
    (options.mixins || (options.mixins = [])).push({
      data (this: Vue) {
        return { [key]: descriptor.value }
      }
    })
  }
} else if (descriptor.get || descriptor.set) {
  // computed properties
  // 轉(zhuǎn)換成計(jì)算屬性的getter和setter
  (options.computed || (options.computed = {}))[key] = {
    get: descriptor.get,
    set: descriptor.set
  }
}
  })

  // add data hook to collect class properties as Vue instance's data
  // 這里再次添加了一個(gè)mixin,會(huì)把這個(gè)類實(shí)例化,然后把對(duì)象中的值放到mixin中
  // 只有在這里我們聲明的class的constructor被調(diào)用了
  ;(options.mixins || (options.mixins = [])).push({
    data (this: Vue) {
      return collectDataFromConstructor(this, Component)
    }
  })

  // decorate options
  // 如果這個(gè)類還有其他的裝飾器,也逐個(gè)調(diào)用. vue-class-component只提供了類裝飾器
  // props、components、watch等特殊參數(shù)只能寫(xiě)在Component(options)的options參數(shù)里
  // 因此我們使用vue-property-decorator庫(kù)的屬性裝飾器
  // 通過(guò)下面這個(gè)循環(huán)應(yīng)用屬性裝飾器就可以合并options(ps: 不明白可以看看createDecorator這個(gè)函數(shù))
  const decorators = (Component as DecoratedClass).__decorators__
  if (decorators) {
    decorators.forEach(fn => fn(options))
    delete (Component as DecoratedClass).__decorators__
  }

  // find super
  // 找到這個(gè)類的父類,如果父類已經(jīng)是繼承于Vue的,就直接調(diào)用它的extend方法,否則調(diào)用Vue.extend
  const superProto = Object.getPrototypeOf(Component.prototype)
  const Super = superProto instanceof Vue
    ? superProto.constructor as VueClass<Vue>
    : Vue
  // 最后生成我們要的Vue組件
  const Extended = Super.extend(options)

  // 處理靜態(tài)成員
  forwardStaticMembers(Extended, Component, Super)

  // 如果我們支持反射,那么也把對(duì)應(yīng)的反射收集的內(nèi)容綁定到Extended上
  if (reflectionIsSupported) {
    copyReflectionMetadata(Extended, Component)
  }

  return Extended
}

const reservedPropertyNames = [
  // Unique id
  'cid',

  // Super Vue constructor
  'super',

  // Component options that will be used by the component
  'options',
  'superOptions',
  'extendOptions',
  'sealedOptions',

  // Private assets
  'component',
  'directive',
  'filter'
]

const shouldIgnore = {
  prototype: true,
  arguments: true,
  callee: true,
  caller: true
}

function forwardStaticMembers (
  Extended: typeof Vue,
  Original: typeof Vue,
  Super: typeof Vue
): void {
  // We have to use getOwnPropertyNames since Babel registers methods as non-enumerable
  Object.getOwnPropertyNames(Original).forEach(key => {
// Skip the properties that should not be overwritten
if (shouldIgnore[key]) {
  return
}

// Some browsers does not allow reconfigure built-in properties
const extendedDescriptor = Object.getOwnPropertyDescriptor(Extended, key)
if (extendedDescriptor && !extendedDescriptor.configurable) {
  return
}

const descriptor = Object.getOwnPropertyDescriptor(Original, key)!

// If the user agent does not support `__proto__` or its family (IE <= 10),
// the sub class properties may be inherited properties from the super class in TypeScript.
// We need to exclude such properties to prevent to overwrite
// the component options object which stored on the extended constructor (See #192).
// If the value is a referenced value (object or function),
// we can check equality of them and exclude it if they have the same reference.
// If it is a primitive value, it will be forwarded for safety.
if (!hasProto) {
  // Only `cid` is explicitly exluded from property forwarding
  // because we cannot detect whether it is a inherited property or not
  // on the no `__proto__` environment even though the property is reserved.
  if (key === 'cid') {
    return
  }

  const superDescriptor = Object.getOwnPropertyDescriptor(Super, key)

  if (
    !isPrimitive(descriptor.value) &&
    superDescriptor &&
    superDescriptor.value === descriptor.value
  ) {
    return
  }
}

// Warn if the users manually declare reserved properties
if (
  process.env.NODE_ENV !== 'production' &&
  reservedPropertyNames.indexOf(key) >= 0
) {
  warn(
    `Static property name '${key}' declared on class '${Original.name}' ` +
    'conflicts with reserved property name of Vue internal. ' +
    'It may cause unexpected behavior of the component. Consider renaming the property.'
  )
}

Object.defineProperty(Extended, key, descriptor)
 })
 }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容