Browse Source

prop validation

dev
Evan You 9 years ago
parent
commit
91455a6618
  1. 25
      src/runtime/instance/render.js
  2. 27
      src/runtime/instance/state.js
  3. 4
      src/runtime/observer/watcher.js
  4. 1
      src/runtime/util/index.js
  5. 31
      src/runtime/util/options.js
  6. 171
      src/runtime/util/props.js

25
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)
}
}
}
}

27
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'

4
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
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'

31
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
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)
}
Loading…
Cancel
Save