github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/public/libs/vue-1.0.24/src/compiler/compile-props.js (about) 1 import config from '../config' 2 import { parseDirective } from '../parsers/directive' 3 import { isSimplePath } from '../parsers/expression' 4 import { defineReactive, withoutConversion } from '../observer/index' 5 import propDef from '../directives/internal/prop' 6 import { 7 warn, 8 camelize, 9 hyphenate, 10 getAttr, 11 getBindAttr, 12 isLiteral, 13 toBoolean, 14 toNumber, 15 stripQuotes, 16 isArray, 17 isPlainObject, 18 isObject, 19 hasOwn 20 } from '../util/index' 21 22 const propBindingModes = config._propBindingModes 23 const empty = {} 24 25 // regexes 26 const identRE = /^[$_a-zA-Z]+[\w$]*$/ 27 const settablePathRE = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\[[^\[\]]+\])*$/ 28 29 /** 30 * Compile props on a root element and return 31 * a props link function. 32 * 33 * @param {Element|DocumentFragment} el 34 * @param {Array} propOptions 35 * @param {Vue} vm 36 * @return {Function} propsLinkFn 37 */ 38 39 export function compileProps (el, propOptions, vm) { 40 var props = [] 41 var names = Object.keys(propOptions) 42 var i = names.length 43 var options, name, attr, value, path, parsed, prop 44 while (i--) { 45 name = names[i] 46 options = propOptions[name] || empty 47 48 if (process.env.NODE_ENV !== 'production' && name === '$data') { 49 warn('Do not use $data as prop.', vm) 50 continue 51 } 52 53 // props could contain dashes, which will be 54 // interpreted as minus calculations by the parser 55 // so we need to camelize the path here 56 path = camelize(name) 57 if (!identRE.test(path)) { 58 process.env.NODE_ENV !== 'production' && warn( 59 'Invalid prop key: "' + name + '". Prop keys ' + 60 'must be valid identifiers.', 61 vm 62 ) 63 continue 64 } 65 66 prop = { 67 name: name, 68 path: path, 69 options: options, 70 mode: propBindingModes.ONE_WAY, 71 raw: null 72 } 73 74 attr = hyphenate(name) 75 // first check dynamic version 76 if ((value = getBindAttr(el, attr)) === null) { 77 if ((value = getBindAttr(el, attr + '.sync')) !== null) { 78 prop.mode = propBindingModes.TWO_WAY 79 } else if ((value = getBindAttr(el, attr + '.once')) !== null) { 80 prop.mode = propBindingModes.ONE_TIME 81 } 82 } 83 if (value !== null) { 84 // has dynamic binding! 85 prop.raw = value 86 parsed = parseDirective(value) 87 value = parsed.expression 88 prop.filters = parsed.filters 89 // check binding type 90 if (isLiteral(value) && !parsed.filters) { 91 // for expressions containing literal numbers and 92 // booleans, there's no need to setup a prop binding, 93 // so we can optimize them as a one-time set. 94 prop.optimizedLiteral = true 95 } else { 96 prop.dynamic = true 97 // check non-settable path for two-way bindings 98 if (process.env.NODE_ENV !== 'production' && 99 prop.mode === propBindingModes.TWO_WAY && 100 !settablePathRE.test(value)) { 101 prop.mode = propBindingModes.ONE_WAY 102 warn( 103 'Cannot bind two-way prop with non-settable ' + 104 'parent path: ' + value, 105 vm 106 ) 107 } 108 } 109 prop.parentPath = value 110 111 // warn required two-way 112 if ( 113 process.env.NODE_ENV !== 'production' && 114 options.twoWay && 115 prop.mode !== propBindingModes.TWO_WAY 116 ) { 117 warn( 118 'Prop "' + name + '" expects a two-way binding type.', 119 vm 120 ) 121 } 122 } else if ((value = getAttr(el, attr)) !== null) { 123 // has literal binding! 124 prop.raw = value 125 } else if (process.env.NODE_ENV !== 'production') { 126 // check possible camelCase prop usage 127 var lowerCaseName = path.toLowerCase() 128 value = /[A-Z\-]/.test(name) && ( 129 el.getAttribute(lowerCaseName) || 130 el.getAttribute(':' + lowerCaseName) || 131 el.getAttribute('v-bind:' + lowerCaseName) || 132 el.getAttribute(':' + lowerCaseName + '.once') || 133 el.getAttribute('v-bind:' + lowerCaseName + '.once') || 134 el.getAttribute(':' + lowerCaseName + '.sync') || 135 el.getAttribute('v-bind:' + lowerCaseName + '.sync') 136 ) 137 if (value) { 138 warn( 139 'Possible usage error for prop `' + lowerCaseName + '` - ' + 140 'did you mean `' + attr + '`? HTML is case-insensitive, remember to use ' + 141 'kebab-case for props in templates.', 142 vm 143 ) 144 } else if (options.required) { 145 // warn missing required 146 warn('Missing required prop: ' + name, vm) 147 } 148 } 149 // push prop 150 props.push(prop) 151 } 152 return makePropsLinkFn(props) 153 } 154 155 /** 156 * Build a function that applies props to a vm. 157 * 158 * @param {Array} props 159 * @return {Function} propsLinkFn 160 */ 161 162 function makePropsLinkFn (props) { 163 return function propsLinkFn (vm, scope) { 164 // store resolved props info 165 vm._props = {} 166 var inlineProps = vm.$options.propsData 167 var i = props.length 168 var prop, path, options, value, raw 169 while (i--) { 170 prop = props[i] 171 raw = prop.raw 172 path = prop.path 173 options = prop.options 174 vm._props[path] = prop 175 if (inlineProps && hasOwn(inlineProps, path)) { 176 initProp(vm, prop, inlineProps[path]) 177 } if (raw === null) { 178 // initialize absent prop 179 initProp(vm, prop, undefined) 180 } else if (prop.dynamic) { 181 // dynamic prop 182 if (prop.mode === propBindingModes.ONE_TIME) { 183 // one time binding 184 value = (scope || vm._context || vm).$get(prop.parentPath) 185 initProp(vm, prop, value) 186 } else { 187 if (vm._context) { 188 // dynamic binding 189 vm._bindDir({ 190 name: 'prop', 191 def: propDef, 192 prop: prop 193 }, null, null, scope) // el, host, scope 194 } else { 195 // root instance 196 initProp(vm, prop, vm.$get(prop.parentPath)) 197 } 198 } 199 } else if (prop.optimizedLiteral) { 200 // optimized literal, cast it and just set once 201 var stripped = stripQuotes(raw) 202 value = stripped === raw 203 ? toBoolean(toNumber(raw)) 204 : stripped 205 initProp(vm, prop, value) 206 } else { 207 // string literal, but we need to cater for 208 // Boolean props with no value, or with same 209 // literal value (e.g. disabled="disabled") 210 // see https://github.com/vuejs/vue-loader/issues/182 211 value = ( 212 options.type === Boolean && 213 (raw === '' || raw === hyphenate(prop.name)) 214 ) ? true 215 : raw 216 initProp(vm, prop, value) 217 } 218 } 219 } 220 } 221 222 /** 223 * Process a prop with a rawValue, applying necessary coersions, 224 * default values & assertions and call the given callback with 225 * processed value. 226 * 227 * @param {Vue} vm 228 * @param {Object} prop 229 * @param {*} rawValue 230 * @param {Function} fn 231 */ 232 233 function processPropValue (vm, prop, rawValue, fn) { 234 const isSimple = prop.dynamic && isSimplePath(prop.parentPath) 235 let value = rawValue 236 if (value === undefined) { 237 value = getPropDefaultValue(vm, prop) 238 } 239 value = coerceProp(prop, value) 240 const coerced = value !== rawValue 241 if (!assertProp(prop, value, vm)) { 242 value = undefined 243 } 244 if (isSimple && !coerced) { 245 withoutConversion(() => { 246 fn(value) 247 }) 248 } else { 249 fn(value) 250 } 251 } 252 253 /** 254 * Set a prop's initial value on a vm and its data object. 255 * 256 * @param {Vue} vm 257 * @param {Object} prop 258 * @param {*} value 259 */ 260 261 export function initProp (vm, prop, value) { 262 processPropValue(vm, prop, value, value => { 263 defineReactive(vm, prop.path, value) 264 }) 265 } 266 267 /** 268 * Update a prop's value on a vm. 269 * 270 * @param {Vue} vm 271 * @param {Object} prop 272 * @param {*} value 273 */ 274 275 export function updateProp (vm, prop, value) { 276 processPropValue(vm, prop, value, value => { 277 vm[prop.path] = value 278 }) 279 } 280 281 /** 282 * Get the default value of a prop. 283 * 284 * @param {Vue} vm 285 * @param {Object} prop 286 * @return {*} 287 */ 288 289 function getPropDefaultValue (vm, prop) { 290 // no default, return undefined 291 const options = prop.options 292 if (!hasOwn(options, 'default')) { 293 // absent boolean value defaults to false 294 return options.type === Boolean 295 ? false 296 : undefined 297 } 298 var def = options.default 299 // warn against non-factory defaults for Object & Array 300 if (isObject(def)) { 301 process.env.NODE_ENV !== 'production' && warn( 302 'Invalid default value for prop "' + prop.name + '": ' + 303 'Props with type Object/Array must use a factory function ' + 304 'to return the default value.', 305 vm 306 ) 307 } 308 // call factory function for non-Function types 309 return typeof def === 'function' && options.type !== Function 310 ? def.call(vm) 311 : def 312 } 313 314 /** 315 * Assert whether a prop is valid. 316 * 317 * @param {Object} prop 318 * @param {*} value 319 * @param {Vue} vm 320 */ 321 322 function assertProp (prop, value, vm) { 323 if ( 324 !prop.options.required && ( // non-required 325 prop.raw === null || // abscent 326 value == null // null or undefined 327 ) 328 ) { 329 return true 330 } 331 var options = prop.options 332 var type = options.type 333 var valid = !type 334 var expectedTypes = [] 335 if (type) { 336 if (!isArray(type)) { 337 type = [type] 338 } 339 for (var i = 0; i < type.length && !valid; i++) { 340 var assertedType = assertType(value, type[i]) 341 expectedTypes.push(assertedType.expectedType) 342 valid = assertedType.valid 343 } 344 } 345 if (!valid) { 346 if (process.env.NODE_ENV !== 'production') { 347 warn( 348 'Invalid prop: type check failed for prop "' + prop.name + '".' + 349 ' Expected ' + expectedTypes.map(formatType).join(', ') + 350 ', got ' + formatValue(value) + '.', 351 vm 352 ) 353 } 354 return false 355 } 356 var validator = options.validator 357 if (validator) { 358 if (!validator(value)) { 359 process.env.NODE_ENV !== 'production' && warn( 360 'Invalid prop: custom validator check failed for prop "' + prop.name + '".', 361 vm 362 ) 363 return false 364 } 365 } 366 return true 367 } 368 369 /** 370 * Force parsing value with coerce option. 371 * 372 * @param {*} value 373 * @param {Object} options 374 * @return {*} 375 */ 376 377 function coerceProp (prop, value) { 378 var coerce = prop.options.coerce 379 if (!coerce) { 380 return value 381 } 382 // coerce is a function 383 return coerce(value) 384 } 385 386 /** 387 * Assert the type of a value 388 * 389 * @param {*} value 390 * @param {Function} type 391 * @return {Object} 392 */ 393 394 function assertType (value, type) { 395 var valid 396 var expectedType 397 if (type === String) { 398 expectedType = 'string' 399 valid = typeof value === expectedType 400 } else if (type === Number) { 401 expectedType = 'number' 402 valid = typeof value === expectedType 403 } else if (type === Boolean) { 404 expectedType = 'boolean' 405 valid = typeof value === expectedType 406 } else if (type === Function) { 407 expectedType = 'function' 408 valid = typeof value === expectedType 409 } else if (type === Object) { 410 expectedType = 'object' 411 valid = isPlainObject(value) 412 } else if (type === Array) { 413 expectedType = 'array' 414 valid = isArray(value) 415 } else { 416 valid = value instanceof type 417 } 418 return { 419 valid, 420 expectedType 421 } 422 } 423 424 /** 425 * Format type for output 426 * 427 * @param {String} type 428 * @return {String} 429 */ 430 431 function formatType (type) { 432 return type 433 ? type.charAt(0).toUpperCase() + type.slice(1) 434 : 'custom type' 435 } 436 437 /** 438 * Format value 439 * 440 * @param {*} value 441 * @return {String} 442 */ 443 444 function formatValue (val) { 445 return Object.prototype.toString.call(val).slice(8, -1) 446 }