浏览代码

types: ensure props are readonly

Evan You 6 年之前
父节点
当前提交
08bf9976ae

+ 4 - 1
packages/runtime-core/src/apiCreateComponent.ts

@@ -14,7 +14,10 @@ import { VNodeProps } from './vnode'
 // overload 1: direct setup function
 // (uses user defined props interface)
 export function createComponent<Props, RawBindings = object>(
-  setup: (props: Props, ctx: SetupContext) => RawBindings | RenderFunction
+  setup: (
+    props: Readonly<Props>,
+    ctx: SetupContext
+  ) => RawBindings | RenderFunction
 ): {
   new (): ComponentPublicInstance<
     Props,

+ 3 - 3
packages/runtime-core/src/apiOptions.ts

@@ -83,7 +83,7 @@ export type ComponentOptionsWithoutProps<
   M extends MethodOptions = {}
 > = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
   props?: undefined
-} & ThisType<ComponentPublicInstance<{}, RawBindings, D, C, M, Props>>
+} & ThisType<ComponentPublicInstance<{}, RawBindings, D, C, M, Readonly<Props>>>
 
 export type ComponentOptionsWithArrayProps<
   PropNames extends string = string,
@@ -91,7 +91,7 @@ export type ComponentOptionsWithArrayProps<
   D = {},
   C extends ComputedOptions = {},
   M extends MethodOptions = {},
-  Props = { [key in PropNames]?: any }
+  Props = Readonly<{ [key in PropNames]?: any }>
 > = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
   props: PropNames[]
 } & ThisType<ComponentPublicInstance<Props, RawBindings, D, C, M>>
@@ -102,7 +102,7 @@ export type ComponentOptionsWithObjectProps<
   D = {},
   C extends ComputedOptions = {},
   M extends MethodOptions = {},
-  Props = ExtractPropTypes<PropsOptions>
+  Props = Readonly<ExtractPropTypes<PropsOptions>>
 > = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
   props: PropsOptions
 } & ThisType<ComponentPublicInstance<Props, RawBindings, D, C, M>>

+ 2 - 8
packages/runtime-core/src/componentProps.ts

@@ -65,14 +65,8 @@ export type ExtractPropTypes<
   O,
   MakeDefaultRequired extends boolean = true
 > = O extends object
-  ? {
-      readonly [K in RequiredKeys<O, MakeDefaultRequired>]: InferPropType<O[K]>
-    } &
-      {
-        readonly [K in OptionalKeys<O, MakeDefaultRequired>]?: InferPropType<
-          O[K]
-        >
-      }
+  ? { [K in RequiredKeys<O, MakeDefaultRequired>]: InferPropType<O[K]> } &
+      { [K in OptionalKeys<O, MakeDefaultRequired>]?: InferPropType<O[K]> }
   : { [K in string]: any }
 
 const enum BooleanFlags {

+ 19 - 3
test-dts/createComponent.test-d.tsx

@@ -71,6 +71,9 @@ describe('with object props', () => {
       expectType<ExpectedProps['ccc']>(props.ccc)
       expectType<ExpectedProps['ddd']>(props.ddd)
 
+      // props should be readonly
+      expectError((props.a = 1))
+
       // should also expose declared props on `this`
       expectType<ExpectedProps['a']>(this.a)
       expectType<ExpectedProps['b']>(this.b)
@@ -80,10 +83,16 @@ describe('with object props', () => {
       expectType<ExpectedProps['ccc']>(this.ccc)
       expectType<ExpectedProps['ddd']>(this.ddd)
 
+      // props on `this` should be readonly
+      expectError((this.a = 1))
+
       // assert setup context unwrapping
       expectType<number>(this.c)
       expectType<string>(this.d.e)
 
+      // setup context properties should be mutable
+      this.c = 2
+
       return null
     }
   })
@@ -126,6 +135,9 @@ describe('type inference w/ optional props declaration', () => {
     },
     render() {
       expectType<string>(this.$props.msg)
+      // props should be readonly
+      expectError((this.$props.msg = 'foo'))
+      // should not expose on `this`
       expectError(this.msg)
       expectType<number>(this.a)
       return null
@@ -148,14 +160,18 @@ describe('type inference w/ array props declaration', () => {
   createComponent({
     props: ['a', 'b'],
     setup(props) {
-      props.a
-      props.b
+      // props should be readonly
+      expectError((props.a = 1))
+      expectType<any>(props.a)
+      expectType<any>(props.b)
       return {
         c: 1
       }
     },
     render() {
-      expectType<{ a?: any; b?: any }>(this.$props)
+      expectType<any>(this.$props.a)
+      expectType<any>(this.$props.b)
+      expectError((this.$props.a = 1))
       expectType<any>(this.a)
       expectType<any>(this.b)
       expectType<number>(this.c)