-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Components: enhancements to TypeScript migration guidelines #41669
Changes from all commits
f071cc5
00e17b1
6425db7
be4c8d0
1d425ed
da279b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ For an example of a component that follows these requirements, take a look at [` | |
- [Documentation](#documentation) | ||
- [README example](#README-example) | ||
- [Folder structure](#folder-structure) | ||
- [TypeScript migration guide](#refactoring-a-component-to-typescript) | ||
|
||
## Compatibility | ||
|
||
|
@@ -489,13 +490,99 @@ Given a component folder (e.g. `packages/components/src/unit-control`): | |
2. Resume work on this component once all dependencies have been refactored. | ||
2. Alternatively: | ||
1. For each of those files, add `// @ts-nocheck` at the start of the file. | ||
2. Add the folders to the `tsconfig.json` file. | ||
3. If you’re still getting errors about a component’s props, the easiest way is to slightly refactor this component and perform the props destructuring inside the component’s body (as opposed as in the function signature) — this is to prevent TypeScript from inferring the types of these props. | ||
4. Continue with the refactor of the current component (and take care of the refactor of the dependent components at a later stage). | ||
2. If the components in the ignored files are destructuring props directly from the function's arguments, move the props destructuring to the function's body (this is to avoid TypeScript errors from trying to infer the props type): | ||
|
||
```jsx | ||
// Before: | ||
function MyComponent( { myProp1, myProp2, ...restProps } ) { /* ... */ } | ||
|
||
// After: | ||
function MyComponent( props ) { | ||
const { myProp1, myProp2, ...restProps } = props; | ||
|
||
/* ... */ | ||
} | ||
``` | ||
|
||
3. Add the folders to the `tsconfig.json` file. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Points 3, 4 and 5 here are unchanged apart from their index, that shiftet by 1 after adding a new point (now no. 2) |
||
4. If you’re still getting errors about a component’s props, the easiest way is to slightly refactor this component and perform the props destructuring inside the component’s body (as opposed as in the function signature) — this is to prevent TypeScript from inferring the types of these props. | ||
5. Continue with the refactor of the current component (and take care of the refactor of the dependent components at a later stage). | ||
6. Create a new `types.ts` file. | ||
7. Slowly work your way through fixing the TypeScript errors in the folder: | ||
1. Try to avoid introducing any runtime changes, if possible. The aim of this refactor is to simply rewrite the component to TypeScript. | ||
2. Make sure you have a **named** export for the component, not just the default export ([example](https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/divider/component.tsx)). This ensures that the docgen can properly extract the types data. The naming should be so that the connected/forwarded component has the plain component name (`MyComponent`), and the raw component is prefixed (`UnconnectedMyComponent` or `UnforwardedMyComponent`). This makes the component's `displayName` look nicer in React devtools and in the autogenerated Storybook code snippets. | ||
2. Extract props to `types.ts`, and use them to type components. The README can be of help when determining a prop’s type. | ||
3. Use existing HTML types when possible? (e.g. `required` for an input field?) | ||
4. Use the `CSSProperties` type where it makes sense. | ||
5. Extend existing components’ props if possible, especially when a component internally forwards its props to another component in the package. | ||
6. If the component forwards its `...restProps` to an underlying element/component, you should use the `WordPressComponentProps` type for the component's props: | ||
|
||
```jsx | ||
import type { WordPressComponentProps } from '../ui/context'; | ||
import type { ComponentOwnProps } from './types'; | ||
|
||
function UnconnectedMyComponent( | ||
// The resulting type will include: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add the stuff about the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pushed some changes in cb34385, let me know what you think! Main changes:
|
||
// - all props defined in `ComponentOwnProps` | ||
// - all HTML props/attributes from the component specified as the second | ||
// parameter (`div` in this example) | ||
// - the special `as` prop (which marks the component as polymorphic), | ||
// unless the third parameter is `false` | ||
props: WordPressComponentProps< ComponentOwnProps, 'div', true > | ||
) { /* ... */ } | ||
``` | ||
|
||
7. If the component doesn't forwards its ref yet, wrap the component in a `forwardRed` call. Alternatively, if you want to take advantage of the [Context system](#context-system), you can use the `contextConnect` utility function (which also takes care of adding ref forwarding) | ||
ciampo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```jsx | ||
// With `forwardRef` | ||
import type { ForwardedRef } from 'react'; | ||
import { forwardRef } from '@wordpress/element'; | ||
import type { WordPressComponentProps } from '../ui/context'; | ||
import type { ComponentOwnProps } from './types'; | ||
|
||
function UnforwardedMyComponent( | ||
props: WordPressComponentProps< ComponentOwnProps, 'div', true >, | ||
forwardedRef: ForwardedRef< any > | ||
) { /* ... */ } | ||
|
||
|
||
/** | ||
* MyComponent JSDoc | ||
*/ | ||
export const MyComponent = forwardRef( UnforwardedMyComponent ); | ||
|
||
export default MyComponent; | ||
``` | ||
|
||
```jsx | ||
// With `contextConnect` | ||
import type { ForwardedRef } from 'react'; | ||
import { | ||
contextConnect, | ||
useContextSystem, | ||
WordPressComponentProps, | ||
} from '../ui/context'; | ||
import type { ComponentOwnProps } from './types'; | ||
|
||
function UnconnectedMyComponent( | ||
props: WordPressComponentProps< ComponentOwnProps, 'div', true >, | ||
forwardedRef: ForwardedRef< any > | ||
) { | ||
const contextProps = useContextSystem( props, 'MyComponent' ); | ||
|
||
/* ... */ | ||
} | ||
|
||
|
||
/** | ||
* MyComponent JSDoc | ||
*/ | ||
export const MyComponent = contextConnect( UnconnectedMyComponent, 'MyComponent' ); | ||
|
||
export default MyComponent; | ||
``` | ||
|
||
8. As shown in the previous examples, make sure you have a **named** export for the component, not just the default export ([example](https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/divider/component.tsx)). This ensures that the docgen can properly extract the types data. The naming should be so that the connected/forwarded component has the plain component name (`MyComponent`), and the raw component is prefixed (`UnconnectedMyComponent` or `UnforwardedMyComponent`). This makes the component's `displayName` look nicer in React devtools and in the autogenerated Storybook code snippets. | ||
|
||
```jsx | ||
function UnconnectedMyComponent() { /* ... */ } | ||
|
@@ -505,14 +592,9 @@ Given a component folder (e.g. `packages/components/src/unit-control`): | |
export default MyComponent; | ||
``` | ||
|
||
3. Extract props to `types.ts`, and use them to type components. The README can be of help when determining a prop’s type. | ||
4. Use existing HTML types when possible? (e.g. `required` for an input field?) | ||
5. Use the `CSSProperties` type where it makes sense. | ||
6. Extend existing components’ props if possible, especially when a component internally forwards its props to another component in the package. | ||
7. Use `WordPressComponent` type if possible. | ||
8. Use JSDocs syntax for each TypeScript property that is part of the public API of a component. The docs used here should be aligned with the component’s README. Add `@default` values where appropriate. | ||
9. Prefer `unknown` to `any`, and in general avoid it when possible. | ||
8. On the component's main export, add a JSDoc comment that includes the main description and `@example` code snippet from the README ([example](https://github.com/WordPress/gutenberg/blob/943cec92f21fedcd256502ea72d9903941f3b05a/packages/components/src/unit-control/index.tsx#L290-L306)) | ||
9. Use JSDocs syntax for each TypeScript property that is part of the public API of a component. The docs used here should be aligned with the component’s README. Add `@default` values where appropriate. | ||
10. Prefer `unknown` to `any`, and in general avoid it when possible. | ||
8. On the component's main named export, add a JSDoc comment that includes the main description and the example code snippet from the README ([example](https://github.com/WordPress/gutenberg/blob/43d9c82922619c1d1ff6b454f86f75c3157d3de6/packages/components/src/date-time/date-time/index.tsx#L193-L217)). _At the time of writing, the `@example` JSDoc keyword is not recognized by StoryBook's docgen, so please avoid using it_. | ||
9. Make sure that: | ||
1. tests still pass; | ||
2. storybook examples work as expected. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good clarification with the example 👍