「從原始碼中學習」徹底理解Vue選項Props

前端勸退師發表於2019-03-14

最近都在寫Vue相關的文章,感興趣的可以看回我之前寫的。

作者文章總集

正文:吃下這條魚? - props初始化

initProps 是如何執行的:

「從原始碼中學習」徹底理解Vue選項Props

1.normalizeProps: initProps 之前的規範化資料

normalizeProps的程式碼有點長,這裡只列舉經過規範化後的prop型別和結果

1.1 字串

props: ["data"]
// 規範化後
props: {
  data:{
    type: null
  }
}
複製程式碼

1.2 物件

props: {
  data1: {
    type: String,
    default: ''
  }
  data2: Number,
}
// 規範化後
props: {
  data1: {
    type: String,
    default: ''
  },
  data2: {
    type: Number
  },
}
複製程式碼

2.initProps: 處理props

「從原始碼中學習」徹底理解Vue選項Props

原始碼分析如下:

function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  if (!isRoot) {
    toggleObserving(false)
  }
  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 (!isRoot && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop direct....`,
            vm
          )
        }
      })
    } else {
      defineReactive(props, key, value)
    }
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}
複製程式碼

2.1 常量定義

 const propsData = vm.$options.propsData || {}
 const props = vm._props = {}
 const keys = vm.$options._propKeys = []
 const isRoot = !vm.$parent
複製程式碼
  • propsData: 儲存著傳遞進來的 props 的值
  • props: 引用vm._props`,並初始化為{}
  • keys: 在 vm.$options 上新增 _propKeys 屬性
  • isRoot: 判斷是否存在vm.$parent,若無則為根節點

2.2 條件判斷及迴圈

if (!isRoot) {
  toggleObserving(false)
}
for (const key in propsOptions) {
  // 省略...
}
toggleObserving(true)
複製程式碼
  • !isRoot:若當前例項非根節點,關閉toggleObserving
  • toggleObserving: 可以理解為資料觀測的開關
  • for...in : 遍歷propsOptions

2.2.1: 遍歷propsOptions做什麼

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 (!isRoot && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwri  tten 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)
    }
}
複製程式碼

劃重點:

  • propsOptionsopts.props
  • key 就是每個 prop 的名字

此時進入迴圈:

keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
複製程式碼
  • key 新增到 vm.$options._propKeys
  • value: 用validateProp校驗是否為預期的型別值,然後返回相應 prop 值(或default值)

2.2.2: 接著進入 if...else:

這裡註釋一下:

if (process.env.NODE_ENV !== 'production') {
      // 駝峰轉連字元
      const hyphenatedKey = hyphenate(key)
      // 校驗prop是否為內建的屬性
      // 內建屬性:key,ref,slot,slot-scope,is
      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 (!isRoot && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwri  tten 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)
    }
複製程式碼

最後簡化:

if (process.env.NODE_ENV !== 'production') {
      // 駝峰轉連字元
      // 校驗prop是否為內建的屬性
      // 內建屬性:key,ref,slot,slot-scope,is
      // 若是內建,彈出警告
      defineReactive(props, key, value, () => {
      // 子元件直接修改屬性時 彈出警告
    } else {
      defineReactive(props, key, value)
    }
複製程式碼

工具函式: 「從原始碼中學習」Vue原始碼中的JS騷操作

2.2.3: defineReactive: 最終處理

defineReactive(props, key, value)
複製程式碼

defineReactive是老熟人了,但這裡要注意一點: 先前toggleObserving(false),關閉了觀測的開關,所以defineReactive中呼叫 observe, 是一個無效呼叫。

此時到這裡,可以得出一個結論

props 是通過 defineReactive定義的,此時雖然是響應式資料,但沒有進行深度定義。

即,父元件傳給子元件props後,子元件不必再重複觀測props

2.2.4 toggleObserving(true)`: 最後開啟觀測開關

toggleObserving(true)
複製程式碼

重新開啟觀測開關,避免影響後續程式碼執行。

「從原始碼中學習」徹底理解Vue選項Props

感悟:相比分析原始碼,理解後寫成部落格更難。用文字講清楚一件事可比敲程式碼難多了。

「從原始碼中學習」徹底理解Vue選項Props

求一份深圳的內推

目前本人在準備跳槽,希望各位大佬和HR小姐姐可以內推一份靠譜的深圳前端崗位!

  • 微信:huab119
  • 郵箱:454274033@qq.com

相關文章