[Vue2.0 ソースコード学習] ライフサイクル - 初期化フェーズ (initState)

1 はじめに

この記事では、ライフサイクルの初期化フェーズで呼び出される 5 番目の初期化関数を紹介しますinitState関数名からするとインスタンスの状態を初期化する関数のようですが、インスタンスの状態とは何でしょうか?前回の記事で少し触れましたが、日常の開発では、Vueなどのオプションがコンポーネントに記述されますが、これらのオプションをインスタンスのステータス オプションと呼びます。言い換えれば、この関数はこれらの状態を初期化するために使用されるため、次に、関数がこれらの状態オプションをどのように初期化するかを分析します。propsdatamethodscomputedwatchinitState

2. initState関数の解析

initStateまず、関数を分析しましょう。関数の定義はsrc/core/instance/state.js、次のようにソース コードにあります。

export function initState (vm: Component) {
    
    
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    
    
    initData(vm)
  } else {
    
    
    observe(vm._data = {
    
    }, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    
    
    initWatch(vm, opts.watch)
  }
}

ご覧のとおり、この関数のコードはそれほど多くなく、ロジックは非常に明確です。

_watchersまず、現在のインスタンス内のすべてのインスタンスを保存するために、新しい属性がインスタンスに追加されますwatcher。登録されたインスタンスであってもvm.$watchオプションを使用して登録されたwatcherインスタンスであっても、この属性に保存されます。watchwatcher

ここでもう少し詳しく説明します。変更検出の章では、Vue属性インターセプトを使用してデータ変更の検出が実装されていることを紹介しました。ただし、属性インターセプトはVueすべてのデータの変更を検出するために使用されるわけではありません。これは、データが多ければ多いほど、これが原因です。つまり、より多くの依存関係がデータにバインドされることになり、依存関係の追跡に大きなメモリ オーバーヘッドが発生するためVue 2.0、このバージョンからはVueすべてのデータが検出されなくなりますが、検出の粒度が変更されます。コンポーネント レベルと各コンポーネントが検出されるため、このコンポーネントで使用されるすべての状態の依存関係を保存するために、新しい属性が各コンポーネントに追加されますvm._watchers。状態の 1 つが変更されると、コンポーネントに通知され、内部で仮想DOMデータ比較が使用してメモリのオーバーヘッドを削減し、パフォーマンスを向上させます。

ソース コードに戻り続けます。次のステップでは、インスタンス内にどのオプションがあるかを判断し、次のように、対応するオプション初期化サブ関数を呼び出して初期化します。

if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
    
    
    initData(vm)
} else {
    
    
    observe(vm._data = {
    
    }, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
    
    
    initWatch(vm, opts.watch)
}

まず、インスタンスにオプションがあるかどうかを確認しprops、オプションがある場合は、propsオプション初期化関数を呼び出してオプションinitPropsを初期化します。props

次に、インスタンスにオプションがあるかどうかを確認しmethods、ある場合は、methodsオプション初期化関数を呼び出してオプションinitMethodsを初期化します。methods

data次に、インスタンスにオプションがあるかどうかを確認し、オプションがある場合は、dataオプション初期化関数を呼び出してオプションをinitData初期化し、そうでない場合は空のオブジェクトとして扱い、応答性の高いオブジェクトに変換します。datadata

computed次に、インスタンスにオプションがあるかどうかを確認し、ある場合は、computedオプション初期化関数を呼び出してオプションinitComputedを初期化します。computed

最後に、インスタンスにオプションがあるかどうかを確認しwatch、オプションがある場合は、watchオプション初期化関数を呼び出してオプションinitWatchを初期化します。watch

つまり、1 つの文は次のとおりです。オプションがある場合は、対応するオプション初期化サブ関数を呼び出してオプションを初期化します。

上記はinitState関数のロジックのすべてですが、実際、関数内で 5 つのオプションが初期化されると、それらの順序は無計画ではなく意図的に配置されていることがわかります。開発中に inと観察とin をdata使用できることに気づいた場合は、これができる理由は、初期化中に、最初に初期化、次に初期化、そして最後に初期化という順序に従っているためですpropswatchdatapropspropsdatawatch

次に、これら 5 つのステータス オプションに対応する 5 つの初期化サブ関数を 1 つずつ分析し、内部でどのように初期化されるかを確認します。

3. 小道具を初期化する

propsオプションは通常、現在のコンポーネントの親コンポーネントによって渡されます。親コンポーネントが子コンポーネントを呼び出すと、通常、props次のように、属性値がラベル属性として子コンポーネントのラベルに追加されます。

<Child prop1="xxx" prop2="yyy"></Child>

前回の記事で初期化イベントinitEvents関数を紹介したとき、テンプレートがコンパイルされるとき、コンポーネント タグが解析されるときに、すべてのタグ属性が解析され、サブコンポーネントが次の場合にサブコンポーネントに渡されると述べました。もちろん、ここにはpropsデータが含まれます。

子コンポーネント内ではprops親コンポーネントからのデータをオプションで受け取りますが、受け取る際は次のように記述できます。

// 写法一
props: ['name']

// 写法二
props: {
    
    
    name: String, // [String, Number]
}

// 写法三
props: {
    
    
    name:{
    
    
		type: String
    }
}

Vueユーザーに提供されるpropsオプションは非常に自由な記述であることがわかりますVue。規約によれば、記述方法はたくさんありますが、最終処理では 1 つの記述方法のみが処理されます。このとき、必ず正規化することを考えることになります。処理前のデータを処理すると、すべての書き込みメソッドが処理され、すべて 1 つの書き込みメソッドに変換されます。正規化されたイベントと同様に、属性を結合するときにpropsデータも正規化されます。

3.1 正規化されたデータ

propsデータ正規化関数の定義は、次のようにソース コードにありますsrc/core/util/options.js

function normalizeProps (options, vm) {
    
    
  const props = options.props
  if (!props) return
  const res = {
    
    }
  let i, val, name
  if (Array.isArray(props)) {
    
    
    i = props.length
    while (i--) {
    
    
      val = props[i]
      if (typeof val === 'string') {
    
    
        name = camelize(val)
        res[name] = {
    
     type: null }
      } else if (process.env.NODE_ENV !== 'production') {
    
    
        warn('props must be strings when using array syntax.')
      }
    }
  } else if (isPlainObject(props)) {
    
    
    for (const key in props) {
    
    
      val = props[key]
      name = camelize(key)
      res[name] = isPlainObject(val)
        ? val
        : {
    
     type: val }
    }
  } else if (process.env.NODE_ENV !== 'production') {
    
    
    warn(
      `Invalid value for option "props": expected an Array or an Object, ` +
      `but got ${
      
      toRawType(props)}.`,
      vm
    )
  }
  options.props = res
}

上記コードではまずインスタンス内のオプションを取得しますがprops、存在しない場合は直接返します。

const props = options.props
if (!props) return

res存在する場合は、最終結果を格納する空のオブジェクトを定義します。次に、propsオプションが配列であるかどうかを確認し (書き込み方法 1)、配列内の各要素を調べます。要素が文字列の場合は、まず要素をキャメル ケース名に変換し、次に要素を , Stored in; if としてkey使用ます{type: null}要素が文字列ではない場合、例外がスローされます。次のように:valueres

if (Array.isArray(props)) {
    
    
    i = props.length
    while (i--) {
    
    
        val = props[i]
        if (typeof val === 'string') {
    
    
            name = camelize(val)
            res[name] = {
    
     type: null }
        } else if (process.env.NODE_ENV !== 'production') {
    
    
            warn('props must be strings when using array syntax.')
        }
    }
}

オプションが配列でない場合はprops、引き続きオブジェクトかどうかを判断します。オブジェクトの場合は、オブジェクト内のキー値の各ペアを走査します。キー値の各ペアを取得した後、最初にキー名を次のように変換します。キャメル ケースの名前付け。次に、値がオブジェクトであるかどうかを判断します。値がオブジェクトの場合 (書き方 3)、キーと値のペアは に格納されます。値がオブジェクトでない場合 (書き方 2) res、キー名はとして保存されkeyます次のように:{type: null}valueres

if (isPlainObject(props)) {
    
    
    for (const key in props) {
    
    
        val = props[key]
        name = camelize(key)
        res[name] = isPlainObject(val)
            ? val
        : {
    
     type: val }
    }
}

オプションが配列でもオブジェクトでもない場合props、非運用環境では例外がスローされ、オプションはres正規化された結果としてインスタンスに再割り当てされますprops次のように:

if (process.env.NODE_ENV !== 'production') {
    
    
    warn(
        `Invalid value for option "props": expected an Array or an Object, ` +
        `but got ${
      
      toRawType(props)}.`,
        vm
    )
}
options.props = res

以上がpropsデータの正規化ですが、3つの書き込み方法のどれを使っても最終的には以下のような書き込み方法に変換されることが分かります。

props: {
    
    
    name:{
    
    
        type: xxx
    }
}

3.2 initProps 関数の分析

オプションを標準化した後props、実際にpropsオプションを初期化できます。initProps関数の定義はsrc/core/instance/state.js、次のようにソース コードにあります。

function initProps (vm: Component, propsOptions: Object) {
    
    
  const propsData = vm.$options.propsData || {
    
    }
  const props = vm._props = {
    
    }
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted
  if (!isRoot) {
    
    
    toggleObserving(false)
  }
  for (const key in propsOptions) {
    
    
    keys.push(key)
    const value = validateProp(key, propsOptions, propsData, vm)
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
    
    
      const hyphenatedKey = hyphenate(key)
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
    
    
        warn(
          `"${
      
      hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      defineReactive(props, key, value, () => {
    
    
        if (vm.$parent && !isUpdatingChildComponent) {
    
    
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwritten whenever the parent component re-renders. ` +
            `Instead, use a data or computed property based on the prop's ` +
            `value. Prop being mutated: "${
      
      key}"`,
            vm
          )
        }
      })
    } else {
    
    
      defineReactive(props, key, value)
    }
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    if (!(key in vm)) {
    
    
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}

ご覧のとおり、この関数は、現在のVueインスタンスと現在のインスタンスの正規化されたオプションという2 つのパラメータを受け取りますprops

まず、関数内で次の 4 つの変数が定義されます。

const propsData = vm.$options.propsData || {
    
    }
const props = vm._props = {
    
    }
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
  • propspropsData:親コンポーネントによって渡される実際のデータ。
  • props:変数に設定されたすべてのプロパティが保存されるvm._propsポインターpropsvm._props
  • vm.$options._propKeysキー:キャッシュ オブジェクトprops内で指されるポインタ。key将来更新するときは、配列をprops走査するだけですべてのキーを取得できますvm.$options._propKeyspropskey
  • isRoot: 現在のコンポーネントがルート コンポーネントであるかどうか。

次に、現在のコンポーネントがルート コンポーネントであるかどうかを確認します。そうでない場合は、データをレスポンシブなコンポーネントpropsに変換するかどうかを制御するために使用される配列をレスポンシブなコンポーネントに変換する必要はありません。toggleObserving(false)次のように:

if (!isRoot) {
    
    
    toggleObserving(false)
}

次に、propsオプションを調べてキー値の各ペアを取得し、最初にキー名を に追加しkeys、次にvalidateProp関数 (この関数は以下で紹介します) を呼び出して、親コンポーネントによって渡されたデータ型が一致するかどうかを確認しprops、受信したデータを取得します。 value . 次に、次のように関数を使用しvalueてキーと値を(つまり)に追加します。defineReactivepropsvm._props

for (const key in propsOptions) {
    
    
    keys.push(key)
    const value = validateProp(key, propsOptions, propsData, vm)
    if (process.env.NODE_ENV !== 'production') {
    
    
      const hyphenatedKey = hyphenate(key)
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
    
    
        warn(
          `"${
      
      hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      defineReactive(props, key, value, () => {
    
    
        if (vm.$parent && !isUpdatingChildComponent) {
    
    
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwritten whenever the parent component re-renders. ` +
            `Instead, use a data or computed property based on the prop's ` +
            `value. Prop being mutated: "${
      
      key}"`,
            vm
          )
        }
      })
    } else {
    
    
      defineReactive(props, key, value)
    }
  }

追加後、key現在のインスタンスに存在するかどうかを判断しvm、存在しない場合はproxy関数を呼び出して属性とみなしたvmコードを設定し、データにアクセスする際に実際にアクセスしているのは です次のように:keyvm[key]vm._props[key]

if (!(key in vm)) {
    
    
    proxy(vm, `_props`, key)
}

以上がinitProps関数のロジックですが、次に親コンポーネントで渡されたデータ型が一致するかどうかを確認し、渡された値を取得するvalidateProp関数の使い方を見てみましょう。props

3.3 validateProp 関数の分析

validateProp関数の定義はsrc/core/util/props.js、次のようにソース コードにあります。

export function validateProp (key,propOptions,propsData,vm) {
    
    
  const prop = propOptions[key]
  const absent = !hasOwn(propsData, key)
  let value = propsData[key]
  // boolean casting
  const booleanIndex = getTypeIndex(Boolean, prop.type)
  if (booleanIndex > -1) {
    
    
    if (absent && !hasOwn(prop, 'default')) {
    
    
      value = false
    } else if (value === '' || value === hyphenate(key)) {
    
    
      // only cast empty string / same name to boolean if
      // boolean has higher priority
      const stringIndex = getTypeIndex(String, prop.type)
      if (stringIndex < 0 || booleanIndex < stringIndex) {
    
    
        value = true
      }
    }
  }
  // check default value
  if (value === undefined) {
    
    
    value = getPropDefaultValue(vm, prop, key)
    // since the default value is a fresh copy,
    // make sure to observe it.
    const prevShouldObserve = shouldObserve
    toggleObserving(true)
    observe(value)
    toggleObserving(prevShouldObserve)
  }
  if (process.env.NODE_ENV !== 'production') {
    
    
    assertProp(prop, key, value, vm, absent)
  }
  return value
}

ご覧のとおり、この関数は次の 4 つのパラメーターを受け取ります。

  • key:propOptions走査中に取得された各属性名。
  • propOptions: 現在のインスタンスの正規化されたオプションprops
  • propspropsData:親コンポーネントによって渡される実際のデータ。
  • vm: 現在のインスタンス。

まず、関数内で次の 3 つの変数が定義されます。

const prop = propOptions[key]
const absent = !hasOwn(propsData, key)
let value = propsData[key]
  • prop:現在keyにあるpropOptions対応する値。
  • missing: に現在存在するkeyかどうかpropsData、つまり、親コンポーネントがこのプロパティに渡したかどうか。
  • value: の現在対応する値、つまり、この属性の親コンポーネントによって渡される実際の値keypropsData

次に、属性がブール型 (Boolean) であるpropかどうかを判断します。この関数は、属性に特定の型が存在するかどうかを判断するために使用されます。存在する場合は、属性内の型のインデックスを返します (属性は配列である可能性があるため) ). 存在しない場合は、-1 を返します。typegetTypeIndexproptypetypetype

ブール型の場合、個別に処理する必要がある 2 つのエッジ ケースがあります。

  1. absentの場合trueつまり、親コンポーネントがこの属性を渡さずprop、属性にデフォルト値がない場合はfalse、次のように属性値を に設定します。

    if (absent && !hasOwn(prop, 'default')) {
          
          
        value = false
    }
    
  2. 親コンポーネントがこのpropプロパティを渡す場合は、次の点を満たす必要があります。

    • 属性値が空の文字列であるか、属性値が属性名と等しい場合。
    • propタイプが属性typeに存在しませんString
    • 属性に型があるprop場合属性の型のインデックスは型のインデックスより小さくなければなりません。つまり、型の優先順位が高くなります。typeStringBooleantypeStringBoolean

    次に、次のように属性値を に設定しますtrue

    if (value === '' || value === hyphenate(key)) {
          
          
        const stringIndex = getTypeIndex(String, prop.type)
        if (stringIndex < 0 || booleanIndex < stringIndex) {
          
          
            value = true
        }
    }
    

    また、属性値と属性名が等しいと判断した場合は、まず属性名をキャメルケースから-連結文字列に変換し、以下の記述方法ではサブコンポーネントpropを としますtrue

    <Child name></Child>
    <Child name="name"></Child>
    <Child userName="user-name"></Child>
    

    ブール型ではなく、それ以外の型の場合は親コンポーネントに属性が渡されているかどうかだけを判断すればよく、渡されていない場合は属性値がこの時に関数を呼び出します(この関数は後述しますundefinedgetPropDefaultValue。次のように、属性のデフォルト値を取得し、それを応答性の高い値に変換します。

    if (value === undefined) {
          
          
        value = getPropDefaultValue(vm, prop, key)
        // since the default value is a fresh copy,
        // make sure to observe it.
        const prevShouldObserve = shouldObserve
        toggleObserving(true)
        observe(value)
        toggleObserving(prevShouldObserve)
    }
    

    親コンポーネントがこの属性を渡し、対応する実際の値を持っている場合、非運用環境では、assertProp属性値が必要な型と一致するかどうかを確認する関数が呼び出されます (この関数については以下で紹介します)。次のように:

    if (process.env.NODE_ENV !== 'production' ) {
          
          
        assertProp(prop, key, value, vm, absent)
    }
    

    最後に、親コンポーネントによって渡されたプロパティの実際の値が返されます。

3.4 getPropDefaultValue 関数の分析

getPropDefaultValue関数の定義はsrc/core/util/props.js、次のようにソース コードにあります。

function getPropDefaultValue (vm, prop, key){
    
    
  // no default, return undefined
  if (!hasOwn(prop, 'default')) {
    
    
    return undefined
  }
  const def = prop.default
  // warn against non-factory defaults for Object & Array
  if (process.env.NODE_ENV !== 'production' && isObject(def)) {
    
    
    warn(
      'Invalid default value for prop "' + key + '": ' +
      'Props with type Object/Array must use a factory function ' +
      'to return the default value.',
      vm
    )
  }
  // the raw prop value was also undefined from previous render,
  // return previous default value to avoid unnecessary watcher trigger
  if (vm && vm.$options.propsData &&
    vm.$options.propsData[key] === undefined &&
    vm._props[key] !== undefined
  ) {
    
    
    return vm._props[key]
  }
  // call factory function for non-Function types
  // a value is Function if its prototype is function even across different execution context
  return typeof def === 'function' && getType(prop.type) !== 'Function'
    ? def.call(vm)
    : def
}

この関数は、次の 3 つのパラメータを受け取ります。

  • vm: 現在のインスタンス。
  • prop:サブコンポーネントオプションprops内のkey対応する各値。
  • propsキー:各サブコンポーネントのオプションkey

propsその機能は、サブコンポーネントのオプションに基づいて、key対応するデフォルト値を取得することです。

最初に属性があるpropかどうかを確認し、属性がない場合はデフォルト値がないことを意味し、それが直接返されます。default次のように:

if (!hasOwn(prop, 'default')) {
    
    
    return undefined
}

存在する場合は、属性を取り出してdefault変数に割り当てますdef次に、それが非本番環境のdefオブジェクトであるかどうかが判断され、そうであれば、オブジェクトまたは配列のデフォルト値はファクトリ関数から取得する必要があるという警告がスローされます。次のように:

const def = prop.default
// warn against non-factory defaults for Object & Array
if (process.env.NODE_ENV !== 'production' && isObject(def)) {
    
    
    warn(
        'Invalid default value for prop "' + key + '": ' +
        'Props with type Object/Array must use a factory function ' +
        'to return the default value.',
        vm
    )
}

次に、親コンポーネントが属性を渡さないがpropsvm._propsの属性値を持つ場合、vm._props次のように、 の属性値がデフォルト値であると判断されます。

if (vm && vm.$options.propsData &&
    vm.$options.propsData[key] === undefined &&
    vm._props[key] !== undefined
   ) {
    
    
    return vm._props[key]
}

最後に、defそれが関数であるかどうかをprop.type判断しますFunction。関数である場合は、defオブジェクトまたは配列を返すファクトリ関数であることを示し、関数の戻り値はデフォルト値として返されます。関数でない場合は、関数の戻り値がデフォルト値として返されますdef。関数を使用すると、それがdefデフォルト値として返されます。次のように:

return typeof def === 'function' && getType(prop.type) !== 'Function'
    ? def.call(vm)
	: def

3.5assertProp関数の解析

assertProp関数の定義はsrc/core/util/props.js、次のようにソース コードにあります。

function assertProp (prop,name,value,vm,absent) {
    
    
  if (prop.required && absent) {
    
    
    warn(
      'Missing required prop: "' + name + '"',
      vm
    )
    return
  }
  if (value == null && !prop.required) {
    
    
    return
  }
  let type = prop.type
  let valid = !type || type === true
  const expectedTypes = []
  if (type) {
    
    
    if (!Array.isArray(type)) {
    
    
      type = [type]
    }
    for (let i = 0; i < type.length && !valid; i++) {
    
    
      const assertedType = assertType(value, type[i])
      expectedTypes.push(assertedType.expectedType || '')
      valid = assertedType.valid
    }
  }
  if (!valid) {
    
    
    warn(
      `Invalid prop: type check failed for prop "${
      
      name}".` +
      ` Expected ${
      
      expectedTypes.map(capitalize).join(', ')}` +
      `, got ${
      
      toRawType(value)}.`,
      vm
    )
    return
  }
  const validator = prop.validator
  if (validator) {
    
    
    if (!validator(value)) {
    
    
      warn(
        'Invalid prop: custom validator check failed for prop "' + name + '".',
        vm
      )
    }
  }
}

この関数は、次の 5 つのパラメータを受け取ります。

  • 小道具:propオプション;
  • 名前:props中央のpropオプションkey;
  • value:親コンポーネントによってpropsData渡されるkey、対応する実際のデータ。
  • vm: 現在のインスタンス。
  • missing: に現在存在するkeyかどうかpropsData、つまり、親コンポーネントがこのプロパティに渡したかどうか。

その機能は、親コンポーネントによって渡された実際の値が型と一致するかどうかを検証することでありproptype一致しない場合、非実稼働環境では警告がスローされます。

関数内でprop、必須の項目が設定されており (prop.requiredつまりtrue)、親コンポーネントが属性を渡さない場合、その項目が必須であることを示す警告がスローされます。次のように:

if (prop.required && absent) {
    
    
    warn(
        'Missing required prop: "' + name + '"',
        vm
    )
    return
}

そして、その品物が不要であり、品物の価値がvalue存在しない場合には、現時点では合法であると判断され、直接返品されます。次のように:

if (value == null && !prop.required) {
    
    
    return
}

次に、次の 3 つの変数が定義されます。

let type = prop.type
let valid = !type || type === true
const expectedTypes = []
  • タイプ:propを入力します。type
  • valid: 検証が成功したかどうか。
  • ExpectedTypes: 期待されるタイプの配列を保存します。検証が失敗して警告がスローされると、ユーザーは属性がどのようなタイプであると予想されるかを尋ねられます。

通常、typeネイティブ コンストラクター、または複数の型を含む配列にすることができます。それ以外の場合、このプロパティは設定できません。ユーザーがネイティブ コンストラクターまたは配列を設定する場合、vaildデフォルトはfalse( !type) です。ユーザーがこの属性を設定しない場合、つまり検証が必要ない場合、vaildデフォルトは ( ) 、trueつまり検証は成功します。

また、typeに等しい場合はtrue、 と書かれており、検証が成功したことをprops:{name:true}意味します。propしたがって、この構文が表示されるとき、現時点ではtype === truevaildデフォルトは ですtrue

次に、型を確認します。ユーザーがtype属性を設定した場合は、その属性が配列であるかどうかが判断され、そうでない場合は、次のように後続の処理を容易にするために配列に変換されます。

if (type) {
    
    
    if (!Array.isArray(type)) {
    
    
        type = [type]
    }
}

次に、type配列を反復処理し、assertType関数 verifyを呼び出しますvalueassertType関数の検証後、次のようにオブジェクトが返されます。

{
    
    
    vaild:true,       // 表示是否校验成功
    expectedType:'Boolean'   // 表示被校验的类型
}

次に、次のように、検証されたタイプを に追加しexpectedTypesvaild変数を に設定します。assertedType.valid

for (let i = 0; i < type.length && !valid; i++) {
    
    
    const assertedType = assertType(value, type[i])
    expectedTypes.push(assertedType.expectedType || '')
    valid = assertedType.valid
}

ここで注意してください: 上記のループの条件ステートメントには次のような条件があります:!vaildつまり、type配列内に成功する検証がもう 1 つあり、ループはただちに終了し、検証が合格したことを示します。

次に、ループが完了した後にvaild検証falseが失敗したことを意味し、警告がスローされます。次のように:

if (!valid) {
    
    
    warn(
        `Invalid prop: type check failed for prop "${
      
      name}".` +
        ` Expected ${
      
      expectedTypes.map(capitalize).join(', ')}` +
        `, got ${
      
      toRawType(value)}.`,
        vm
    )
    return
}

さらに、propこのオプションは次のようなカスタム検証関数もサポートしています。

props:{
    
    
   // 自定义验证函数
    propF: {
    
    
      validator: function (value) {
    
    
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
}

したがって、ユーザーが渡したカスタム検証関数を使用してデータを検証することも必要です。まず、ユーザーによって渡された検証関数が取得され、その関数が呼び出され、検証対象のデータが渡されます。検証が失敗した場合は、警告がスローされます。次のように:

const validator = prop.validator
if (validator) {
    
    
    if (!validator(value)) {
    
    
        warn(
            'Invalid prop: custom validator check failed for prop "' + name + '".',
            vm
        )
    }
}

4. メソッドの初期化

初期化は比較的単純で、その初期化関数の定義は次のようにmethodsソース コードにあります。src/core/instance/state.js

function initMethods (vm, methods) {
    
    
  const props = vm.$options.props
  for (const key in methods) {
    
    
    if (process.env.NODE_ENV !== 'production') {
    
    
      if (methods[key] == null) {
    
    
        warn(
          `Method "${
      
      key}" has an undefined value in the component definition. ` +
          `Did you reference the function correctly?`,
          vm
        )
      }
      if (props && hasOwn(props, key)) {
    
    
        warn(
          `Method "${
      
      key}" has already been defined as a prop.`,
          vm
        )
      }
      if ((key in vm) && isReserved(key)) {
    
    
        warn(
          `Method "${
      
      key}" conflicts with an existing Vue instance method. ` +
          `Avoid defining component methods that start with _ or $.`
        )
      }
    }
    vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
  }
}

コードからわかるように、初期化では次のmethods3 つのことだけが行われます。名前の文字が命名規則に従っていませんか? 存在し、仕様を満たしている場合は、インスタンスにマウントします。次に、ソース コードを 1 行ずつ分析し、これら 3 つのことを確認していきます。methodmethodmethodvm

まず、methodsオプション内の各オブジェクトを走査し、非運用環境でmethodsメソッドが 1 つだけkey存在valueするかどうかを判断します。つまり、メソッド名のみが存在し、メソッド本体が存在しない場合は、例外がスローされます。メソッドが未定義であることをユーザーに伝えます。次のように:

if (methods[key] == null) {
    
    
    warn(
        `Method "${
      
      key}" has an undefined value in the component definition. ` +
        `Did you reference the function correctly?`,
        vm
    )
}

methodsそして、あるメソッド名があるprops属性名と同じであると判断し、メソッド名が重複していることをユーザに通知する例外をスローします。次のように:

if (props && hasOwn(props, key)) {
    
    
    warn(
        `Method "${
      
      key}" has already been defined as a prop.`,
        vm
    )
}

methodsそして、あるメソッド名がvmインスタンス内にすでに存在し、そのメソッド名が_またはで始まる場合には$例外がスローされ、メソッド名が標準化されていないことをユーザーに通知します次のように:

if ((key in vm) && isReserved(key)) {
    
    
    warn(
        `Method "${
      
      key}" conflicts with an existing Vue instance method. ` +
        `Avoid defining component methods that start with _ or $.`
    )
}

このうち、文字列がまたはisReservedで始まるかどうかを判定するために使用される関数です_$

最後に、上記の判断がすべて正しければ、次のように、methodそれを インスタンス にバインドして、 options のメソッドにアクセスできるvmようにします。this.xxxmethodsxxx

vm[key] = methods[key] == null ? noop : bind(methods[key], vm)

5. データの初期化

初期化も比較的簡単で、その初期化関数の定義は次のようにdataソース コードにあります。src/core/instance/state.js

function initData (vm) {
    
    
    let data = vm.$options.data
    data = vm._data = typeof data === 'function'
        ? getData(data, vm)
    : data || {
    
    }
    if (!isPlainObject(data)) {
    
    
        data = {
    
    }
        process.env.NODE_ENV !== 'production' && warn(
            'data functions should return an object:\n' +
            'https://vuejs.org/v2/guide/components.html##data-Must-Be-a-Function',
            vm
        )
    }
    // proxy data on instance
    const keys = Object.keys(data)
    const props = vm.$options.props
    const methods = vm.$options.methods
    let i = keys.length
    while (i--) {
    
    
        const key = keys[i]
        if (process.env.NODE_ENV !== 'production') {
    
    
            if (methods && hasOwn(methods, key)) {
    
    
                warn(
                    `Method "${
      
      key}" has already been defined as a data property.`,
                    vm
                )
            }
        }
        if (props && hasOwn(props, key)) {
    
    
            process.env.NODE_ENV !== 'production' && warn(
                `The data property "${
      
      key}" is already declared as a prop. ` +
                `Use prop default value instead.`,
                vm
            )
        } else if (!isReserved(key)) {
    
    
            proxy(vm, `_data`, key)
        }
    }
    // observe data
    observe(data, true /* asRootData */)
}

ご覧のとおり、initData関数のロジックは複雑ではなく、initMethods関数のロジックにある程度似ています。dataユーザーから渡されたオプションが正当であるかどうかを一連の条件によって判断し、最終的dataに応答に変換してインスタンスにバインドしますvmコードのロジックを詳しく見てみましょう。

まず、ユーザーから渡されたオプションを取得しdata、変数に代入しdata、その変数をdataポインタとして指しvm._data、関数かどうかを判定しdata、関数であればgetDataその関数を呼び出して戻り値を取得し、に保存しますvm._dataそうでない場合は、それ自体を に保存しますvm._data次のように:

let data = vm.$options.data
data = vm._data = typeof data === 'function'
    ? getData(data, vm)
	: data || {
    
    }

渡されたオプションが関数であるかどうかに関係なくdata、その最終値はオブジェクトである必要があることはわかっていますが、オブジェクトでない場合は、dataオブジェクトである必要があることをユーザーに求める警告がスローされます。次のように:

if (!isPlainObject(data)) {
    
    
    data = {
    
    }
    process.env.NODE_ENV !== 'production' && warn(
        'data functions should return an object:\n' +
        'https://vuejs.org/v2/guide/components.html##data-Must-Be-a-Function',
        vm
    )
}

data次に、オブジェクト内の各項目が調べられ、非運用環境では、dataオブジェクト内の項目の属性名が重複しているかどうかが判断されますkey。重複がある場合は、警告がスローされ、ユーザーに次のことを促します。属性名が重複しています。methods次のように:

if (process.env.NODE_ENV !== 'production') {
    
    
    if (methods && hasOwn(methods, key)) {
    
    
        warn(
            `Method "${
      
      key}" has already been defined as a data property.`,
            vm
        )
    }
}

key次に、特定の項目propに属性名が重複しているかどうかを判断し、重複がある場合は、属性名が重複していることをユーザーに通知する警告をスローします。次のように:

if (props && hasOwn(props, key)) {
    
    
    process.env.NODE_ENV !== 'production' && warn(
        `The data property "${
      
      key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
    )
}

重複がない場合は、関数が呼び出されて、 またはインスタンスで始まらないオブジェクト内のプロパティをproxyプロキシするため、options 内のデータにアクセスできるようになります。次のように:datakey_$vmthis.xxxdataxxx

if (!isReserved(key)) {
    
    
    proxy(vm, `_data`, key)
}

最後に、次のようにobserve関数を呼び出してdataデータを応答に変換します。

observe(data, true /* asRootData */)

6. 計算結果の初期化

計算プロパティcomputedには誰もが精通していると思いますし、日々の開発で頻繁に使用されることは間違いありません。また、計算プロパティには優れた機能があることもわかっています。計算プロパティの結果はキャッシュされ、依存関係がない限り更新されません。応答プロパティの変更、計算。次に、計算プロパティがこれらの関数をどのように実装するかを見てみましょう。

6.1 使用方法を確認する

まず、公式ドキュメントの使用例に従って、次のように計算プロパティの使用法を確認しましょう。

var vm = new Vue({
    
    
  data: {
    
     a: 1 },
  computed: {
    
    
    // 仅读取
    aDouble: function () {
    
    
      return this.a * 2
    },
    // 读取和设置
    aPlus: {
    
    
      get: function () {
    
    
        return this.a + 1
      },
      set: function (v) {
    
    
        this.a = v - 1
      }
    }
  }
})
vm.aPlus   // => 2
vm.aPlus = 3
vm.a       // => 2
vm.aDouble // => 4

ご覧のとおり、computedオプションの属性値は関数にすることができ、getterその関数のデフォルトはデータの読み取りのみに使用される値ゲッターになります。また、値ゲッターgetterと値ストアを持つオブジェクトにすることもできますsetter。データの取得と設定の読み取りに使用されます。

6.2 initComputed関数の解析

計算プロパティの使用法を理解した後、initComputed計算プロパティの初期化関数の内部原理を分析しましょう。initComputed関数の定義はsrc/core/instance/state.js、次のようにソース コードにあります。

function initComputed (vm: Component, computed: Object) {
    
    
    const watchers = vm._computedWatchers = Object.create(null)
    const isSSR = isServerRendering()

    for (const key in computed) {
    
    
        const userDef = computed[key]
        const getter = typeof userDef === 'function' ? userDef : userDef.get
        if (process.env.NODE_ENV !== 'production' && getter == null) {
    
    
            warn(
                `Getter is missing for computed property "${
      
      key}".`,
                vm
            )
        }

        if (!isSSR) {
    
    
            // create internal watcher for the computed property.
            watchers[key] = new Watcher(
                vm,
                getter || noop,
                noop,
                computedWatcherOptions
            )
        }

        if (!(key in vm)) {
    
    
            defineComputed(vm, key, userDef)
        } else if (process.env.NODE_ENV !== 'production') {
    
    
            if (key in vm.$data) {
    
    
                warn(`The computed property "${
      
      key}" is already defined in data.`, vm)
            } else if (vm.$options.props && key in vm.$options.props) {
    
    
                warn(`The computed property "${
      
      key}" is already defined as a prop.`, vm)
            }
        }
    }
}

ご覧のとおり、関数内では、次のように変数が最初に定義されてwatchers空のオブジェクトが割り当てられ、ポインターとしてポイントされますvm._computedWatchers

const watchers = vm._computedWatchers = Object.create(null)

次に、computedオプション内の各属性を走査し、最初に各項目の属性値を取得し、それを として記録してから、それが関数であるかどうかをuserDef判断します。関数である場合、関数はデフォルトで値取得者になり、それを変数に割り当てます。; それが関数ではない場合は、それがオブジェクトであることを意味し、オブジェクト内の属性が値受け取り子として使用され、変数に割り当てられます次のように:userDefgettergettergetgetter

const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get

次に、非運用環境では、上記の 2 つの状況で取得された値ゲッターが存在しないと判断され、計算された属性には値ゲッターが必要であることをユーザーに求める警告がスローされます。次のように:

if (process.env.NODE_ENV !== 'production' && getter == null) {
    
    
    warn(
        `Getter is missing for computed property "${
      
      key}".`,
        vm
    )
}

次に、サーバー側のレンダリング環境にないかどうかを確認し、watcherインスタンスを作成し、現在ループされているプロパティ名をキーとして使用し、作成したインスタンスをオブジェクトのwatcher値として保存します。watchers次のように:

if (!isSSR) {
    
    
    // create internal watcher for the computed property.
    watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
    )
}

最後に、現在ループされている属性名が現在のインスタンスに存在するかどうかが判断されますvm。存在する場合は、非運用環境で警告がスローされます。存在しない場合は、計算された属性を設定する関数が呼び出されます。defineComputedインスタンス。vm

上記はinitComputed関数の内部ロジックですが、次に、defineComputed関数がvmインスタンスに対して計算されたプロパティをどのように設定するかを見てみましょう。

6.3 計算関数解析の定義

defineComputed関数の定義はsrc/core/instance/state.js、次のようにソース コードにあります。

const sharedPropertyDefinition = {
    
    
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

export function defineComputed (target,key,userDef) {
    
    
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    
    
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : userDef
    sharedPropertyDefinition.set = noop
  } else {
    
    
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop
    sharedPropertyDefinition.set = userDef.set
      ? userDef.set
      : noop
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    
    
    sharedPropertyDefinition.set = function () {
    
    
      warn(
        `Computed property "${
      
      key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

この関数は 3 つのパラメータ、つまりtargetkeyを受け入れますuserDeftargetその機能は、上記の属性を定義することでありkey、属性keygetter合計は値setterに従ってuserDef設定されます。この関数の具体的なロジックを見てみましょう。

変数は最初に定義されますsharedPropertyDefinition。これはデフォルトのプロパティ記述子です。

shouldCache次に、計算されたプロパティをキャッシュするかどうかを識別する変数が関数内で定義されます。この変数の値は、現在の環境が非サーバー レンダリング環境であるかどうかを示します。非サーバー レンダリング環境の場合、この変数は ですtrueつまり、非サーバー レンダリング環境では、計算されたプロパティのみをキャッシュする必要があります。次のように:

const shouldCache = !isServerRendering()

userDef次に、関数であればデフォルトの valuer であると判断しますがgetter、ここでは非サーバーレンダリング環境では直接使用せず、userDef関数getterを呼び出してcreateComputedGetter(この関数は後述します)作成します。これgetteruserDef単なる通常のものでありgetter、キャッシュ機能を持っていないため、計算されたプロパティが実行されるため、getterサーバー側のレンダリング環境で直接userDef使用できるキャッシュ機能を備えた追加のものを作成する必要があります。getterサーバー側のレンダリング環境にキャッシュする必要はありません。ユーザーがsetter機能を設定していないため、sharedPropertyDefinition.setに設定されますnoop次のように:

if (typeof userDef === 'function') {
    
    
    sharedPropertyDefinition.get = shouldCache
        ? createComputedGetter(key)
    : userDef
    sharedPropertyDefinition.set = noop
}

関数ではない場合はuserDefオブジェクトとして扱います。を設定する場合はsharedPropertyDefinition.get、まずuserDef.get存在するかどうかを確認します。存在しない場合は に設定しますnoop。存在する場合は上記と同じです。非サーバー レンダリング環境で、ユーザーが明示的に に設定userDef.cacheしない場合は、関数を呼び出しますをクリックして割り当てを作成します次に、それを関数として設定します。次のように:falsecreateComputedGettergettersharedPropertyDefinition.getsharedPropertyDefinition.setuserDef.set

sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop
sharedPropertyDefinition.set = userDef.set
    ? userDef.set
	: noop

次に、ユーザーが非運用環境で設定していない場合はsettersetterデフォルトの関数が提供されます。これは、ユーザーがsetter計算された属性を設定せずに変更することを防ぐためであり、次のように警告がスローされます。

if (process.env.NODE_ENV !== 'production' &&
    sharedPropertyDefinition.set === noop) {
    
    
    sharedPropertyDefinition.set = function () {
    
    
        warn(
            `Computed property "${
      
      key}" was assigned to but it has no setter.`,
            this
        )
    }
}

最後に、Object.definePropertyプロパティをkeyバインドするメソッドを呼び出しますtarget。プロパティ記述子は上記で設定したものですsharedPropertyDefinitionこのようにして、計算されたプロパティはインスタンスにバインドされますvm

上記はdefineComputed関数のロジックのすべてです。さらに、計算された属性がキャッシュされるかどうか、およびその応答性は主に、それが関数の戻り結果getterに設定されているかどうかに依存することがわかりました。それでは次に、この関数を詳しく見てcreateComputedGetterみましょう。createComputedGetter

6.4 createComputedGetter関数の解析

createComputedGetter関数の定義はsrc/core/instance/state.js、次のようにソース コードにあります。

function createComputedGetter (key) {
    
    
    return function computedGetter () {
    
    
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {
    
    
            watcher.depend()
            return watcher.evaluate()
        }
    }
}

ご覧のとおり、この関数は内部で関数を返す高階関数であるcomputedGetterため、computedGetter実際には に関数が代入されますsharedPropertyDefinition.get計算されたプロパティの値が取得されると、プロパティの関数が実行されgetter、プロパティの関数が最終的に実行される関数getterになりますsharedPropertyDefinition.getcomputedGetter

関数内では、computedGetterまず現在のインスタンス上の_computedWatchers属性keyに対応するインスタンスを格納しwatcher、存在する場合にはそのインスタンス上のメソッドやメソッドwatcherを呼び出しそのメソッドの戻り値を計算された属性の計算結果として返します。では、インスタンス上のメソッドとメソッドとは何でしょうか?watcherdependevaluateevaluatewatcherdependevaluate

6.5 依存と評価

上記のインスタンスを作成したときのことを振り返ると、次のようになりますwatcher

const computedWatcherOptions = {
    
     computed: true }
watchers[key] = new Watcher(
    vm,
    getter || noop,
    noop,
    computedWatcherOptions
)

渡される 2 番目のパラメーターはgetter関数、4 番目のパラメーターはオブジェクトですcomputedWatcherOptions

次のように、クラスの定義をもう一度確認してみましょうWatcher

export default class Watcher {
    
    
    constructor (vm,expOrFn,cb,options,isRenderWatcher) {
    
    
        if (options) {
    
    
            // ...
            this.computed = !!options.computed
            // ...
        } else {
    
    
            // ...
        }

        this.dirty = this.computed // for computed watchers
        if (typeof expOrFn === 'function') {
    
    
            this.getter = expOrFn
        }

        if (this.computed) {
    
    
            this.value = undefined
            this.dep = new Dep()
        }
    }

    evaluate () {
    
    
        if (this.dirty) {
    
    
            this.value = this.get()
            this.dirty = false
        }
        return this.value
    }

    /**
     * Depend on this watcher. Only for computed property watchers.
     */
    depend () {
    
    
        if (this.dep && Dep.target) {
    
    
            this.dep.depend()
        }
    }

    update () {
    
    
        if (this.computed) {
    
    
            if (this.dep.subs.length === 0) {
    
    
                this.dirty = true
            } else {
    
    
                this.getAndInvoke(() => {
    
    
                    this.dep.notify()
                })
            }
        }
    }

    getAndInvoke (cb: Function) {
    
    
        const value = this.get()
        if (
            value !== this.value ||
            // Deep watchers and watchers on Object/Arrays should fire even
            // when the value is the same, because the value may
            // have mutated.
            isObject(value) ||
            this.deep
        ) {
    
    
            // set new value
            const oldValue = this.value
            this.value = value
            this.dirty = false
            if (this.user) {
    
    
                try {
    
    
                    cb.call(this.vm, value, oldValue)
                } catch (e) {
    
    
                    handleError(e, this.vm, `callback for watcher "${
      
      this.expression}"`)
                }
            } else {
    
    
                cb.call(this.vm, value, oldValue)
            }
        }
    }
}

Watcherクラスをインスタンス化するときに、オブジェクトが 4 番目のパラメーターとして渡されることがわかりますcomputedWatcherOptions = { computed: true }。オブジェクト内の属性は、computedこのwatcherインスタンスwatcherが計算された属性、つまりWatcherクラス内の属性のインスタンスであることを示しますthis.computed。 , このクラスは、this.dirty計算された属性の戻り値が変更されたかどうかをマークするための属性も定義します。計算された属性のキャッシュは、この属性によって判断されます。計算された属性が依存するデータが変更されるたびに、属性は次のように設定されます。そのため、次回計算された属性が読み取られたときに、再計算結果が返されます。それ以外の場合は、前の計算結果が直接返されますthis.dirtytrue

メソッドが呼び出されるとwatcher.depend()、計算された属性を読み取るメソッドが計算された属性のインスタンスの依存関係リストwatcherに追加されますwatcher。計算された属性で使用されるデータが変更されると、watcher計算された属性のインスタンスがwatcher.update()メソッドを実行します。updateメソッドは、現在の属性はwatcher計算された属性かどうかを判断しますwatcher。そうである場合、getAndInvoke計算された属性の戻り値が変更されたかどうかを比較するために呼び出されます。変更された場合は、コールバックが実行され、計算された属性を読み取る人に再設定を通知しますwatcher。 - レンダリング ロジックを実行します。

メソッドを呼び出すと、まずであるかどうかがwatcher.evaluate()判定され、 である場合は、計算された属性が依存するデータが変更されたことを意味し、呼び出しを呼び出して計算結果を再取得して最終的に戻ります。の場合は、前の計算結果が直接返されます。this.dirtytruetruethis.get()false

その内部原理を図に示します。

ここに画像の説明を挿入します

7.時計を初期化する

次は最後の初期化関数、初期化watchオプションです。このオプションは日常の開発でもwatchよく使用され、既存のデータをリッスンし、データが変更されたときに対応するコールバック関数を実行するために使用できます。それでは、いくつかのオプションがどのように初期化されるかを見てみましょうwatch

7.1 使用方法を確認する

watchまず、次のように、公式ドキュメントの使用例に従ってオプションの使用法を確認してみましょう。

var vm = new Vue({
    
    
  data: {
    
    
    a: 1,
    b: 2,
    c: 3,
    d: 4,
    e: {
    
    
      f: {
    
    
        g: 5
      }
    }
  },
  watch: {
    
    
    a: function (val, oldVal) {
    
    
      console.log('new: %s, old: %s', val, oldVal)
    },
    // methods选项中的方法名
    b: 'someMethod',
    // 深度侦听,该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
    c: {
    
    
      handler: function (val, oldVal) {
    
     /* ... */ },
      deep: true
    },
    // 该回调将会在侦听开始之后被立即调用
    d: {
    
    
      handler: 'someMethod',
      immediate: true
    },
    // 调用多个回调
    e: [
      'handle1',
      function handle2 (val, oldVal) {
    
     /* ... */ },
      {
    
    
        handler: function handle3 (val, oldVal) {
    
     /* ... */ },
      }
    ],
    // 侦听表达式
    'e.f': function (val, oldVal) {
    
     /* ... */ }
  }
})
vm.a = 2 // => new: 2, old: 1

ご覧のとおり、watchオプションの使用は非常に柔軟です。まず、watchオプションはオブジェクト、キーは監視される式、値は対応するコールバック関数です。値はメソッド名、またはオプションを含むオブジェクトにすることもできます。ユーザーに提供する用途は柔軟であるため、さまざまな用途に応じてコードを条件に応じて判断し、処理する必要があります。

7.2 initWatch関数の解析

watchオプションの使用法を理解した後、watchオプション初期化関数initWatchの内部原理を分析しましょう。initWatch関数の定義はsrc/core/instance/state.js、次のようにソース コードにあります。

function initWatch (vm, watch) {
    
    
  for (const key in watch) {
    
    
    const handler = watch[key]
    if (Array.isArray(handler)) {
    
    
      for (let i = 0; i < handler.length; i++) {
    
    
        createWatcher(vm, key, handler[i])
      }
    } else {
    
    
      createWatcher(vm, key, handler)
    }
  }
}

ご覧のとおり、オプションは関数内で処理され、各項目の対応する値watchが取得されます配列である場合は、配列をループし、関数を呼び出して配列内の各項目を順番に作成します。配列でない場合は、関数を直接呼び出して作成しますでは、この関数はどのように作成されるのでしょうか?keyhandlerhandlercreateWatcherwatchercreateWatcherwatchercreateWatcherwatcher

7.3 createWatcher関数の解析

createWatcher関数の定義はsrc/core/instance/state.js、次のようにソース コードにあります。

function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
    
    
  if (isPlainObject(handler)) {
    
    
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    
    
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}

ご覧のとおり、この関数は次の 4 つのパラメーターを受け取ります。

  • vm: 現在のインスタンス。
  • expOrFn: リッスンされているプロパティ式
  • ハンドラ:watchオプションの各項目の値
  • options:渡すためのvm.$watchオプション オブジェクト

関数内ではまずhandler渡されたものがオブジェクトかどうかが判定され、オブジェクトであればユーザーが次のような書き方をしたものとみなされます。

watch: {
    
    
    c: {
    
    
        handler: function (val, oldVal) {
    
     /* ... */ },
		deep: true
    }
}

つまり、リッスン オプションを使用すると、次のように、handlerオブジェクト全体が全体として記録されoptionshandlerオブジェクト内のプロパティがhandler実際のコールバック関数として記録されます。handler

if (isPlainObject(handler)) {
    
    
    options = handler
    handler = handler.handler
}

次に、受信したhandler文字列が文字列であるかどうかを判断し、文字列である場合は、ユーザーが次の書き込み方法を使用したと見なされます。

watch: {
    
    
    // methods选项中的方法名
    b: 'someMethod',
}

つまり、コールバック関数はmethodsオプション内のメソッド名です。オプションを初期化するときにmethods、オプション内の各メソッドが現在のインスタンスにバインドされることがわかっているため、現時点では現在のインスタンスからメソッドを取得するだけで済みます。実際のコールバック関数はhandler次のように記録されます。

if (typeof handler === 'string') {
    
    
    handler = vm[handler]
}

オブジェクトでも文字列でもない場合は、関数であるとみなされ、処理は行われません。

さまざまな種類の値を処理した後、expOrFnリッスンする属性式、handler変数はコールバック関数、options変数はリッスン オプションとなり、最後にvm.$watcherメソッドが呼び出されます (このメソッドについては、グローバル メソッドの導入時に詳しく説明します)。インスタンスメソッド) を渡して上記の 3 つのパラメータを入力して初期化を完了しますwatch

8. まとめ

この記事では、ライフサイクルの初期化フェーズで呼び出される 5 番目の初期化関数を紹介しますinitStatepropsこの初期化関数内で、 、methodsdatacomputed合計 5 つのオプションが初期化されますwatch

これら 5 つのオプションの初期化順序は任意ではなく、慎重に配置されます。dataこの順序で初期化することによってのみ、それを開発で使用しpropsおよびwatchで観察することができますdataprops

これら 5 つのオプションのすべてのプロパティは最終的にインスタンスにバインドされるため、これを使用して任意this.xxxのプロパティにアクセスできます。同時に、まさにこのため、これら 5 つのオプションのすべての属性名を繰り返すべきではありません。繰り返すと、属性が互いに上書きされます。

最後に、これら 5 つのオプションを 1 つずつ初期化する方法の内部原理を分析しました。

おすすめ

転載: blog.csdn.net/weixin_46862327/article/details/132798838