Spread operator in TypeScript is not sound!!
When I was building MigaCSS, I met a bug that I wondered why TypeScript couldnāt catch it.
Here is some code illustrating the issue.
tsx
importReact from 'react'ĀfunctionA () {constprops = {onClick : () => {},jser : 'dev'}Āreturn <B {...props }/>}ĀfunctionB (props : {onClick :() => void}) {return <div {...props }/>}
tsx
importReact from 'react'ĀfunctionA () {constprops = {onClick : () => {},jser : 'dev'}Āreturn <B {...props }/>}ĀfunctionB (props : {onClick :() => void}) {return <div {...props }/>}
I accidentally passed down props to child components, "jser"
is not on B
but
TypeScript doesnāt complain.
Iād expect it to be complained because it does if not with spread.
tsx
importReact from 'react'ĀfunctionA () {constprops = {onClick : () => {},jser : 'dev'}Āreturn <Type '{ onClick: () => void; jser: string; }' is not assignable to type 'IntrinsicAttributes & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement>'. Property 'jser' does not exist on type 'IntrinsicAttributes & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement>'.2322Type '{ onClick: () => void; jser: string; }' is not assignable to type 'IntrinsicAttributes & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement>'. Property 'jser' does not exist on type 'IntrinsicAttributes & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement>'.B onClick ={props .onClick }={ jser props .jser }/>}ĀfunctionB (props :React .ComponentProps <'div'>) {return <div {...props }/>}
tsx
importReact from 'react'ĀfunctionA () {constprops = {onClick : () => {},jser : 'dev'}Āreturn <Type '{ onClick: () => void; jser: string; }' is not assignable to type 'IntrinsicAttributes & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement>'. Property 'jser' does not exist on type 'IntrinsicAttributes & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement>'.2322Type '{ onClick: () => void; jser: string; }' is not assignable to type 'IntrinsicAttributes & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement>'. Property 'jser' does not exist on type 'IntrinsicAttributes & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement>'.B onClick ={props .onClick }={ jser props .jser }/>}ĀfunctionB (props :React .ComponentProps <'div'>) {return <div {...props }/>}
Considering JSX is merely a syntax suger, we can simplify the repro code as below.
ts
typeA = {a : string}Āconsta :A = {a : 'jser',Type '{ a: string; b: string; }' is not assignable to type 'A'. Object literal may only specify known properties, and 'b' does not exist in type 'A'.2322Type '{ a: string; b: string; }' is not assignable to type 'A'. Object literal may only specify known properties, and 'b' does not exist in type 'A'.b : 'dev'}Āconstb = {a : 'jser',b : 'dev'}Ā// Why doesn't following code get complained?constc :A = {...b }Ā
ts
typeA = {a : string}Āconsta :A = {a : 'jser',Type '{ a: string; b: string; }' is not assignable to type 'A'. Object literal may only specify known properties, and 'b' does not exist in type 'A'.2322Type '{ a: string; b: string; }' is not assignable to type 'A'. Object literal may only specify known properties, and 'b' does not exist in type 'A'.b : 'dev'}Āconstb = {a : 'jser',b : 'dev'}Ā// Why doesn't following code get complained?constc :A = {...b }Ā
After doing a little search I found this issue, and got to know that this is by design.
When you spread in c, you donāt know what properties it really has. So TypeScript doesnāt really know if you have excess properties in some cases.
Yeah, I donāt know much about the TypeScript internals, but Flow does a great job alerting on this issue.
Before TypeScript improves on this issue, just remember that spread leads to unsound type in TypeScript, it is best to explicit type the variable before spreading.
tsx
importReact from 'react'ĀfunctionA () {constprops :React .ComponentProps <typeofB > = {onClick : () => {},Type '{ onClick: () => void; jser: string; }' is not assignable to type '{ onClick: () => void; }'. Object literal may only specify known properties, and 'jser' does not exist in type '{ onClick: () => void; }'.2322Type '{ onClick: () => void; jser: string; }' is not assignable to type '{ onClick: () => void; }'. Object literal may only specify known properties, and 'jser' does not exist in type '{ onClick: () => void; }'.jser : 'dev'}Āreturn <B {...props }/>}ĀfunctionB (props : {onClick :() => void}) {return <div {...props }/>}
tsx
importReact from 'react'ĀfunctionA () {constprops :React .ComponentProps <typeofB > = {onClick : () => {},Type '{ onClick: () => void; jser: string; }' is not assignable to type '{ onClick: () => void; }'. Object literal may only specify known properties, and 'jser' does not exist in type '{ onClick: () => void; }'.2322Type '{ onClick: () => void; jser: string; }' is not assignable to type '{ onClick: () => void; }'. Object literal may only specify known properties, and 'jser' does not exist in type '{ onClick: () => void; }'.jser : 'dev'}Āreturn <B {...props }/>}ĀfunctionB (props : {onClick :() => void}) {return <div {...props }/>}