Răsfoiți Sursa

prop validation

Evan You 10 ani în urmă
părinte
comite
91455a6618

+ 14 - 11
src/runtime/instance/render.js

@@ -1,7 +1,6 @@
-import { extend, resolveAsset, hasOwn, isArray, isObject } from '../util/index'
+import { extend, resolveAsset, hasOwn, isArray, isObject, getPropValue } from '../util/index'
 import { createElement, patch, updateListeners, flatten } from '../vdom/index'
 import { callHook } from './lifecycle'
-import { getPropValue } from './state'
 
 export const renderState = {
   activeInstance: null,
@@ -60,11 +59,11 @@ export function renderMixin (Vue) {
       // but if no props changed, nothing happens
       updateProps(this, parentData)
       updateEvents(this, parentData, oldParentData)
-    }
-    // diff parent data (attrs on the placeholder) and queue update
-    // if anything changed
-    if (parentDataChanged(parentData, oldParentData)) {
-      this.$forceUpdate()
+      // diff parent data (attrs on the placeholder) and queue update
+      // if anything changed
+      if (parentDataChanged(parentData, oldParentData)) {
+        this.$forceUpdate()
+      }
     }
   }
 
@@ -176,7 +175,7 @@ function mergeParentData (vm, data, parentData) {
   if (parentData.attrs) {
     const attrs = data.attrs || (data.attrs = {})
     for (let key in parentData.attrs) {
-      if (!hasOwn(props, key)) {
+      if (!props[key]) {
         attrs[key] = parentData.attrs[key]
       }
     }
@@ -184,7 +183,7 @@ function mergeParentData (vm, data, parentData) {
   if (parentData.props) {
     const props = data.props || (data.props = {})
     for (let key in parentData.props) {
-      if (!hasOwn(props, key)) {
+      if (!props[key]) {
         props[key] = parentData.props[key]
       }
     }
@@ -215,8 +214,12 @@ function mergeParentData (vm, data, parentData) {
 
 function updateProps (vm, data) {
   if (data.attrs || data.props) {
-    for (let key in vm.$options.props) {
-      vm[key] = getPropValue(data, key)
+    let keys = vm.$options.propKeys
+    if (keys) {
+      for (let i = 0; i < keys.length; i++) {
+        let key = keys[i]
+        vm[key] = getPropValue(data, key, vm)
+      }
     }
   }
 }

+ 6 - 21
src/runtime/instance/state.js

@@ -10,8 +10,8 @@ import {
   hasOwn,
   isReserved,
   isPlainObject,
-  hyphenate,
-  bind
+  bind,
+  getPropValue
 } from '../util/index'
 
 export function initState (vm) {
@@ -27,31 +27,16 @@ function initProps (vm) {
   const data = vm.$options._renderData
   const props = vm.$options.props
   if (props) {
+    const keys = vm.$options.propKeys = Object.keys(props)
     withoutConversion(() => {
-      for (let key in props) {
-        defineReactive(vm, key, getPropValue(data, key))
+      for (let i = 0; i < keys.length; i++) {
+        let key = keys[i]
+        defineReactive(vm, key, getPropValue(data, key, vm))
       }
     })
   }
 }
 
-export function getPropValue (data, key) {
-  if (!data) return
-  const altKey = hyphenate(key)
-  const attrVal = getPropValueFromHash(data.attrs, key, altKey)
-  return attrVal === undefined
-    ? getPropValueFromHash(data.props, key, altKey)
-    : attrVal
-}
-
-function getPropValueFromHash (hash, key, altKey) {
-  return hash
-    ? hasOwn(hash, key)
-      ? hash[key]
-      : hash[altKey]
-    : undefined
-}
-
 function initData (vm) {
   var data = vm.$options.data
   data = vm._data = typeof data === 'function'

+ 3 - 1
src/runtime/observer/watcher.js

@@ -58,8 +58,10 @@ export default function Watcher (vm, expOrFn, cb, options) {
     if (!this.getter) {
       this.getter = function () {}
       process.env.NODE_ENV !== 'production' && warn(
+        'Failed watching path: ' + expOrFn +
         'Watcher only accepts simple dot-delimited paths. ' +
-        'For full control, use a function instead.'
+        'For full control, use a function instead.',
+        vm
       )
     }
   }

+ 1 - 0
src/runtime/util/index.js

@@ -3,4 +3,5 @@ export * from './env'
 export * from './dom'
 export * from './options'
 export * from './debug'
+export * from './props'
 export { defineReactive } from '../observer/index'

+ 13 - 18
src/runtime/util/options.js

@@ -9,8 +9,7 @@ import {
   isArray,
   isPlainObject,
   hasOwn,
-  camelize,
-  hyphenate
+  camelize
 } from './lang'
 
 /**
@@ -216,9 +215,6 @@ function guardComponents (options) {
     var components = options.components
     var ids = Object.keys(components)
     var def
-    if (process.env.NODE_ENV !== 'production') {
-      var map = options._componentNameMap = {}
-    }
     for (var i = 0, l = ids.length; i < l; i++) {
       var key = ids[i]
       if (isReservedTag(key)) {
@@ -228,11 +224,6 @@ function guardComponents (options) {
         )
         continue
       }
-      // record a all lowercase <-> kebab-case mapping for
-      // possible custom element case error warning
-      if (process.env.NODE_ENV !== 'production') {
-        map[key.replace(/-/g, '').toLowerCase()] = hyphenate(key)
-      }
       def = components[key]
       if (isPlainObject(def)) {
         components[key] = Vue.extend(def)
@@ -249,17 +240,19 @@ function guardComponents (options) {
  */
 
 function guardProps (options) {
-  const res = {}
   const props = options.props
-  let i, val
+  if (!props) return
+  const res = {}
+  let i, val, name
   if (isArray(props)) {
     i = props.length
     while (i--) {
       val = props[i]
       if (typeof val === 'string') {
-        res[camelize(val)] = null
-      } else if (val.name) {
-        res[camelize(val.name)] = val
+        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)) {
@@ -267,11 +260,13 @@ function guardProps (options) {
     i = keys.length
     while (i--) {
       val = props[keys[i]]
-      res[camelize(keys[i])] = typeof val === 'function'
-        ? { type: val }
-        : val
+      name = camelize(keys[i])
+      res[name] = isPlainObject(val)
+        ? val
+        : { type: val }
     }
   }
+  console.log(res)
   options.props = res
 }
 

+ 171 - 0
src/runtime/util/props.js

@@ -0,0 +1,171 @@
+import { hyphenate, hasOwn, isArray, isObject, isPlainObject } from './lang'
+import { warn } from './debug'
+
+export function getPropValue (data, key, vm) {
+  if (!data) return
+  const prop = vm.$options.props[key]
+  const altKey = hyphenate(key)
+  const attrVal = getPropValueFromHash(data.attrs, key, altKey)
+  let value = attrVal === undefined
+    ? getPropValueFromHash(data.props, key, altKey)
+    : attrVal
+  // check default value
+  if (value === undefined) {
+    value = getPropDefaultValue(vm, prop, key)
+  }
+  if (process.env.NODE_ENV !== 'production') {
+    assertProp(prop, key, value, vm)
+  }
+  return value
+}
+
+function getPropValueFromHash (hash, key, altKey) {
+  return hash
+    ? hasOwn(hash, key)
+      ? hash[key]
+      : hash[altKey]
+    : undefined
+}
+
+/**
+ * Get the default value of a prop.
+ *
+ * @param {Vue} vm
+ * @param {Object} prop
+ * @return {*}
+ */
+
+function getPropDefaultValue (vm, prop, name) {
+  // no default, return undefined
+  if (!hasOwn(prop, 'default')) {
+    // absent boolean value defaults to false
+    return prop.type === Boolean
+      ? false
+      : undefined
+  }
+  var def = prop.default
+  // warn against non-factory defaults for Object & Array
+  if (isObject(def)) {
+    process.env.NODE_ENV !== 'production' && warn(
+      'Invalid default value for prop "' + name + '": ' +
+      'Props with type Object/Array must use a factory function ' +
+      'to return the default value.',
+      vm
+    )
+  }
+  // call factory function for non-Function types
+  return typeof def === 'function' && prop.type !== Function
+    ? def.call(vm)
+    : def
+}
+
+/**
+ * Assert whether a prop is valid.
+ *
+ * @param {Object} prop
+ * @param {*} value
+ * @param {Vue} vm
+ */
+
+function assertProp (prop, name, value, vm) {
+  if (prop.required && value == null) {
+    return false
+  }
+  var type = prop.type
+  var valid = !type
+  var expectedTypes = []
+  if (type) {
+    if (!isArray(type)) {
+      type = [type]
+    }
+    for (var i = 0; i < type.length && !valid; i++) {
+      var assertedType = assertType(value, type[i])
+      expectedTypes.push(assertedType.expectedType)
+      valid = assertedType.valid
+    }
+  }
+  if (!valid) {
+    if (process.env.NODE_ENV !== 'production') {
+      warn(
+        'Invalid prop: type check failed for prop "' + name + '".' +
+        ' Expected ' + expectedTypes.map(formatType).join(', ') +
+        ', got ' + formatValue(value) + '.',
+        vm
+      )
+    }
+    return false
+  }
+  var validator = prop.validator
+  if (validator) {
+    if (!validator(value)) {
+      process.env.NODE_ENV !== 'production' && warn(
+        'Invalid prop: custom validator check failed for prop "' + name + '".',
+        vm
+      )
+      return false
+    }
+  }
+  return true
+}
+
+/**
+ * Assert the type of a value
+ *
+ * @param {*} value
+ * @param {Function} type
+ * @return {Object}
+ */
+
+function assertType (value, type) {
+  var valid
+  var expectedType
+  if (type === String) {
+    expectedType = 'string'
+    valid = typeof value === expectedType
+  } else if (type === Number) {
+    expectedType = 'number'
+    valid = typeof value === expectedType
+  } else if (type === Boolean) {
+    expectedType = 'boolean'
+    valid = typeof value === expectedType
+  } else if (type === Function) {
+    expectedType = 'function'
+    valid = typeof value === expectedType
+  } else if (type === Object) {
+    expectedType = 'object'
+    valid = isPlainObject(value)
+  } else if (type === Array) {
+    expectedType = 'array'
+    valid = isArray(value)
+  } else {
+    valid = value instanceof type
+  }
+  return {
+    valid,
+    expectedType
+  }
+}
+
+/**
+ * Format type for output
+ *
+ * @param {String} type
+ * @return {String}
+ */
+
+function formatType (type) {
+  return type
+    ? type.charAt(0).toUpperCase() + type.slice(1)
+    : 'custom type'
+}
+
+/**
+ * Format value
+ *
+ * @param {*} value
+ * @return {String}
+ */
+
+function formatValue (val) {
+  return Object.prototype.toString.call(val).slice(8, -1)
+}