@@ -202,6 +202,121 @@ function RouterLink(props: LinkProps | AnchorProps) {
202202}
203203```
204204
205+ <details >
206+ <summary ><b >Approach: Generic Components</b ></summary >
207+
208+ Here is an example solution, see the further discussion for other solutions. * thanks to [ @jpavon ] ( https://github.com/sw-yx/react-typescript-cheatsheet/issues/12#issuecomment-394440577 ) *
209+
210+ ``` tsx
211+ type Omit <T , K extends keyof T > = Pick <T , Exclude <keyof T , K >>
212+
213+ interface LinkProps {}
214+
215+ type AnchorProps = React .AnchorHTMLAttributes <HTMLAnchorElement >
216+ type RouterLinkProps = Omit <NavLinkProps , ' href' >
217+
218+ const Link = <T extends {}>(
219+ props : LinkProps & T extends RouterLinkProps ? RouterLinkProps : AnchorProps
220+ ) => {
221+ if ((props as RouterLinkProps ).to ) {
222+ return <NavLink { ... props as RouterLinkProps } />
223+ } else {
224+ return <a { ... props as AnchorProps } />
225+ }
226+ }
227+
228+ <Link <RouterLinkProps > to = " /" >My link</Link > // ok
229+ <Link <AnchorProps > href = " /" >My link</Link > // ok
230+ <Link <RouterLinkProps > to = " /" href = " /" >My link</Link > // error
231+ ```
232+
233+ </details >
234+
235+
236+
237+ <details >
238+ <summary ><b >Approach: Composition</b ></summary >
239+
240+ If you want to conditionally render a component, sometimes is better to use [ React's composition model] ( https://reactjs.org/docs/composition-vs-inheritance.html ) to have simpler components and better to understand typings:
241+
242+ ``` tsx
243+ type Omit <T , K extends keyof T > = Pick <T , Exclude <keyof T , K >>
244+
245+ type AnchorProps = React .AnchorHTMLAttributes <HTMLAnchorElement >
246+ type RouterLinkProps = Omit <NavLinkProps , ' href' >
247+
248+ interface Button {
249+ as: React .ComponentClass | ' a'
250+ }
251+
252+ const Button: React .FunctionComponent <Button > = (props ) => {
253+ const {as : Component, children, ... rest} = props
254+ return (
255+ <Component className = " button" { ... rest } >{ children } </Component >
256+ )
257+ }
258+
259+ const AnchorButton: React .FunctionComponent <AnchorProps > = (props ) => (
260+ <Button as = " a" { ... props } />
261+ )
262+
263+ const LinkButton: React .FunctionComponent <RouterLinkProps > = (props ) => (
264+ <Button as = { NavLink } { ... props } />
265+ )
266+
267+ <LinkButton to = " /login" >Login</LinkButton >
268+ <AnchorButton href = " /login" >Login</AnchorButton >
269+ <AnchorButton href = " /login" to = " /test" >Login</AnchorButton > // Error: Property 'to' does not exist on type...
270+ ```
271+ </details >
272+
273+
274+
275+ ## Props: One or the Other but not Both
276+
277+ Use the ` in ` keyword, function overloading, and union types to make components that take either one or another sets of props, but not both:
278+
279+ ``` tsx
280+ type Props1 = { foo: string }
281+ type Props2 = { bar: string }
282+
283+ function MyComponent(props : Props1 ): JSX .Element
284+ function MyComponent(props : Props2 ): JSX .Element
285+ function MyComponent(props : Props1 | Props2 ) {
286+ if (' foo' in props ) {
287+ // props.bar // error
288+ return <div >{ props .foo } </div >
289+ } else {
290+ // props.foo // error
291+ return <div >{ props .bar } </div >
292+ }
293+ }
294+ const UsageComponent: React .FC = () => (
295+ <div >
296+ <MyComponent foo = " foo" />
297+ <MyComponent bar = " bar" />
298+ { /* <MyComponent foo="foo" bar="bar"/> // invalid */ }
299+ </div >
300+ )
301+ ```
302+
303+
304+ ## Props: Must Pass Both
305+
306+ ``` tsx
307+ type OneOrAnother <T1 , T2 > =
308+ | (T1 & { [K in keyof T2 ]? : undefined })
309+ | (T2 & { [K in keyof T1 ]? : undefined })
310+
311+ type Props = OneOrAnother <{ a: string ; b: string }, {}>
312+
313+ const a: Props = { a: ' a' } // error
314+ const b: Props = { b: ' b' } // error
315+ const ab: Props = { a: ' a' , b: ' b' } // ok
316+ ```
317+
318+ Thanks [ diegohaz] ( https://twitter.com/kentcdodds/status/1085655423611367426 )
319+
205320## Omit attribute from a type
206321
207322Sometimes when intersecting types, we want to define our own version of an attribute. For example, I want my component to have a ` label ` , but the type I am intersecting with also has a ` label ` attribute. Here's how to extract that out:
0 commit comments