refactor: move prop decorator into core, expose initial props to initialziers

This commit is contained in:
Evan You 2019-02-26 21:33:50 -05:00
parent 2f165c1e87
commit ff9cddd46f
12 changed files with 53 additions and 62 deletions

View File

@ -1,3 +0,0 @@
__tests__/
__mocks__/
dist/packages

View File

@ -1 +0,0 @@
# @vue/decorators

View File

@ -1,7 +0,0 @@
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./dist/decorators.cjs.prod.js')
} else {
module.exports = require('./dist/decorators.cjs.js')
}

View File

@ -1,21 +0,0 @@
{
"name": "@vue/decorators",
"version": "3.0.0-alpha.1",
"description": "@vue/decorators",
"main": "index.js",
"module": "dist/decorators.esm-bundler.js",
"types": "dist/index.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue.git"
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vue/issues"
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/decorators#readme"
}

View File

@ -1,4 +1,4 @@
import { prop } from '../src/index' import { prop } from '../src/optional/propDecorator'
import { Component, createInstance } from '@vue/runtime-test' import { Component, createInstance } from '@vue/runtime-test'
test('without options', () => { test('without options', () => {
@ -32,16 +32,20 @@ test('without options', () => {
test('with options', () => { test('with options', () => {
let capturedThisValue let capturedThisValue
let capturedPropsValue let capturedPropsValue
let capturedDataValue
class Foo extends Component<{ p: number }> { class Foo extends Component<{ p: number }> {
@prop({ @prop({
default: 1 default: 1
}) })
p: number p: number
// data property should be able to make use of prop
d: number = this.p + 1
created() { created() {
capturedThisValue = this.p capturedThisValue = this.p
capturedPropsValue = this.$props.p capturedPropsValue = this.$props.p
capturedDataValue = this.d
} }
} }
@ -49,6 +53,7 @@ test('with options', () => {
createInstance(Foo) createInstance(Foo)
expect(capturedThisValue).toBe(1) expect(capturedThisValue).toBe(1)
expect(capturedPropsValue).toBe(1) expect(capturedPropsValue).toBe(1)
expect(capturedDataValue).toBe(2)
// explicit override // explicit override
createInstance(Foo, { createInstance(Foo, {
@ -56,4 +61,5 @@ test('with options', () => {
}) })
expect(capturedThisValue).toBe(2) expect(capturedThisValue).toBe(2)
expect(capturedPropsValue).toBe(2) expect(capturedPropsValue).toBe(2)
expect(capturedDataValue).toBe(3)
}) })

View File

@ -105,6 +105,10 @@ export const reservedMethods: ReservedKeys = {
renderTriggered: 1 renderTriggered: 1
} }
export function isReservedKey(key: string): boolean {
return key[0] === '_' || key[0] === '$' || reservedMethods.hasOwnProperty(key)
}
// This is a special marker from the @prop decorator. // This is a special marker from the @prop decorator.
// The decorator stores prop options on the Class' prototype as __prop_xxx // The decorator stores prop options on the Class' prototype as __prop_xxx
const propPrefixRE = /^__prop_/ const propPrefixRE = /^__prop_/

View File

@ -5,7 +5,8 @@ import {
PropOptions, PropOptions,
Prop, Prop,
PropType, PropType,
ComponentPropsOptions ComponentPropsOptions,
isReservedKey
} from './componentOptions' } from './componentOptions'
import { import {
EMPTY_OBJ, EMPTY_OBJ,
@ -43,6 +44,15 @@ export function initializeProps(
? immutable(attrs) ? immutable(attrs)
: attrs : attrs
: instance.$props : instance.$props
// expose initial props on the raw instance so that they can be accessed
// in the child class constructor by class field initializers.
if (options != null) {
for (const key in props) {
// it's okay to just set it here because props options are normalized
// and reserved keys should have been filtered away
;(instance as any)[key] = props[key]
}
}
} }
// resolve raw VNode data. // resolve raw VNode data.
@ -59,7 +69,7 @@ export function resolveProps(
rawData: any, rawData: any,
_options: NormalizedPropsOptions | void _options: NormalizedPropsOptions | void
): [Data, Data] { ): [Data, Data] {
const hasDeclaredProps = _options !== void 0 const hasDeclaredProps = _options != null
const options = _options as NormalizedPropsOptions const options = _options as NormalizedPropsOptions
if (!rawData && !hasDeclaredProps) { if (!rawData && !hasDeclaredProps) {
return EMPTY_PROPS return EMPTY_PROPS
@ -129,22 +139,32 @@ export function normalizePropsOptions(
if (__DEV__ && !isString(raw[i])) { if (__DEV__ && !isString(raw[i])) {
warn(`props must be strings when using array syntax.`, raw[i]) warn(`props must be strings when using array syntax.`, raw[i])
} }
normalized[camelize(raw[i])] = EMPTY_OBJ const normalizedKey = camelize(raw[i])
if (!isReservedKey(normalizedKey)) {
normalized[normalizedKey] = EMPTY_OBJ
} else if (__DEV__) {
warn(`Invalid prop name: "${normalizedKey}" is a reserved property.`)
}
} }
} else { } else {
if (__DEV__ && !isObject(raw)) { if (__DEV__ && !isObject(raw)) {
warn(`invalid props options`, raw) warn(`invalid props options`, raw)
} }
for (const key in raw) { for (const key in raw) {
const opt = raw[key] const normalizedKey = camelize(key)
const prop = (normalized[camelize(key)] = if (!isReservedKey(normalizedKey)) {
isArray(opt) || isFunction(opt) ? { type: opt } : opt) const opt = raw[key]
if (prop) { const prop = (normalized[normalizedKey] =
const booleanIndex = getTypeIndex(Boolean, prop.type) isArray(opt) || isFunction(opt) ? { type: opt } : opt)
const stringIndex = getTypeIndex(String, prop.type) if (prop) {
;(prop as NormalizedProp)[BooleanFlags.shouldCast] = booleanIndex > -1 const booleanIndex = getTypeIndex(Boolean, prop.type)
;(prop as NormalizedProp)[BooleanFlags.shouldCastTrue] = const stringIndex = getTypeIndex(String, prop.type)
booleanIndex < stringIndex ;(prop as NormalizedProp)[BooleanFlags.shouldCast] = booleanIndex > -1
;(prop as NormalizedProp)[BooleanFlags.shouldCastTrue] =
booleanIndex < stringIndex
}
} else if (__DEV__) {
warn(`Invalid prop name: "${normalizedKey}" is a reserved property.`)
} }
} }
} }

View File

@ -1,7 +1,7 @@
import { ComponentInstance } from './component' import { ComponentInstance } from './component'
import { isFunction, isReservedKey } from '@vue/shared' import { isFunction } from '@vue/shared'
import { isRendering } from './componentRenderUtils' import { isRendering } from './componentRenderUtils'
import { reservedMethods } from './componentOptions' import { isReservedKey, reservedMethods } from './componentOptions'
import { warn } from './warning' import { warn } from './warning'
const bindCache = new WeakMap() const bindCache = new WeakMap()

View File

@ -1,7 +1,6 @@
import { ComponentInstance } from './component' import { ComponentInstance } from './component'
import { observable } from '@vue/observer' import { observable } from '@vue/observer'
import { isReservedKey } from '@vue/shared' import { isReservedKey } from './componentOptions'
import { warn } from './warning'
export function initializeState( export function initializeState(
instance: ComponentInstance, instance: ComponentInstance,
@ -25,16 +24,8 @@ export function extractInitializers(
for (let i = 0; i < keys.length; i++) { for (let i = 0; i < keys.length; i++) {
const key = keys[i] const key = keys[i]
if (!isReservedKey(key)) { if (!isReservedKey(key)) {
// it's possible for a prop to be present here when it's declared with // it's possible for a prop to be present here when it's declared
// decorators and has a default value. if (!props || !props.hasOwnProperty(key)) {
if (props && props.hasOwnProperty(key)) {
__DEV__ &&
warn(
`Class property "${key}" is declared as a prop but also has an initializer. ` +
`If you are trying to provide a default value for the prop, use the ` +
`prop's "default" option instead.`
)
} else {
data[key] = (instance as any)[key] data[key] = (instance as any)[key]
} }
} }

View File

@ -31,6 +31,7 @@ export { KeepAlive } from './optional/keepAlive'
export { mixins } from './optional/mixins' export { mixins } from './optional/mixins'
export { EventEmitter } from './optional/eventEmitter' export { EventEmitter } from './optional/eventEmitter'
export { memoize } from './optional/memoize' export { memoize } from './optional/memoize'
export { prop } from './optional/propDecorator'
// flags & types // flags & types
export { ComponentType, ComponentClass, FunctionalComponent } from './component' export { ComponentType, ComponentClass, FunctionalComponent } from './component'

View File

@ -1,4 +1,6 @@
import { PropValidator, Component } from '@vue/runtime-core' import { Component } from '../component'
import { PropValidator } from '../componentOptions'
import { camelize } from '@vue/shared'
export function prop( export function prop(
target: Component | PropValidator<any>, target: Component | PropValidator<any>,
@ -16,7 +18,7 @@ export function prop(
function applyProp(target: any, key: string, options: PropValidator<any> = {}) { function applyProp(target: any, key: string, options: PropValidator<any> = {}) {
// here `target` is the prototype of the component class // here `target` is the prototype of the component class
Object.defineProperty(target, `__prop_${key}`, { Object.defineProperty(target, `__prop_${camelize(key)}`, {
value: options value: options
}) })
} }

View File

@ -5,7 +5,6 @@ export const NOOP = () => {}
export const reservedPropRE = /^(?:key|ref|slots)$|^vnode/ export const reservedPropRE = /^(?:key|ref|slots)$|^vnode/
export const isOn = (key: string) => key[0] === 'o' && key[1] === 'n' export const isOn = (key: string) => key[0] === 'o' && key[1] === 'n'
export const isReservedKey = (key: string) => key[0] === '_' || key[0] === '$'
export const isArray = Array.isArray export const isArray = Array.isArray
export const isFunction = (val: any): val is Function => export const isFunction = (val: any): val is Function =>