-
-
Notifications
You must be signed in to change notification settings - Fork 512
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
Fix content/en/guide/v10/typescript.md #718
Changes from 12 commits
33650a8
01ea56a
ac54f2a
781ae68
52af7d5
fe43988
7ddcf5d
35dd24a
47e2cb5
099101c
181a7df
d5edc78
0a6ee2b
297fded
f5e0535
5382742
b7accfa
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 |
---|---|---|
|
@@ -5,17 +5,17 @@ description: "Preact has built-in TypeScript support. Learn how to make use of i | |
|
||
# TypeScript | ||
|
||
Preact ships TypeScript type definitions, which are used by the library itself! | ||
This guide explains how to configure to use Typescript and use Preact's TypeScript type definitions. | ||
|
||
When you use Preact in a TypeScript-aware editor (like VSCode), you can benefit from the added type information while writing regular JavaScript. If you want to add type information to your own applications, you can use [JSDoc annotations](https://fettblog.eu/typescript-jsdoc-superpowers/), or write TypeScript and transpile to regular JavaScript. This section will focus on the latter. | ||
> 💁 You can also use Preact's type definitions in plain JavaScript with [JSDoc annotations](https://fettblog.eu/typescript-jsdoc-superpowers/). | ||
|
||
--- | ||
|
||
<div><toc></toc></div> | ||
|
||
--- | ||
|
||
## TypeScript configuration | ||
## Configuration | ||
|
||
TypeScript includes a full-fledged JSX compiler that you can use instead of Babel. Add the following configuration to your `tsconfig.json` to transpile JSX to Preact-compatible JavaScript: | ||
|
||
|
@@ -30,6 +30,7 @@ TypeScript includes a full-fledged JSX compiler that you can use instead of Babe | |
} | ||
} | ||
``` | ||
|
||
```json | ||
// TypeScript >= 4.1.1 | ||
{ | ||
|
@@ -56,86 +57,147 @@ If you use TypeScript within a Babel toolchain, set `jsx` to `preserve` and let | |
|
||
Rename your `.jsx` files to `.tsx` for TypeScript to correctly parse your JSX. | ||
|
||
## Typing components | ||
## Type Definitions | ||
|
||
Preact's packages (preact, preact/compat, hooks, etc.) provide TypeScript type definitions. These type definitions are explained at below. | ||
|
||
There are different ways to type components in Preact. Class components have generic type variables to ensure type safety. TypeScript sees a function as functional component as long as it returns JSX. There are multiple solutions to define props for functional components. | ||
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.
Unless a function is matched 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. Since there is the same content at below, I think this line is unnecessary. |
||
## Functional Components | ||
|
||
### Function components | ||
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. They are "Functional Components" and "Class Components" in other page. |
||
The type corresponding to functional components is `FunctionComponent` type. This is type definition of `FunctionComponent` type: | ||
|
||
Typing regular function components is as easy as adding type information to the function arguments. | ||
```ts | ||
interface FunctionComponent<P = {}> { | ||
(props: RenderableProps<P>, context?: any): VNode<any> | null; | ||
displayName?: string; | ||
defaultProps?: Partial<P>; | ||
} | ||
``` | ||
|
||
> 💁 FunctionalComponent type is alias of FunctionComponent type. | ||
|
||
You can implement a functional component in TypeScript, as below. | ||
|
||
```tsx | ||
type MyComponentProps = { | ||
import { h, FunctionComponent } from 'preact'; | ||
|
||
type Props = { | ||
name: string; | ||
age: number; | ||
}; | ||
|
||
function MyComponent({ name, age }: MyComponentProps) { | ||
const MyComponent: FunctionComponent<Props> = function ({ name, age }) { | ||
return ( | ||
<div> | ||
My name is {name}, I am {age.toString()} years old. | ||
My name is {name}, I am {age} years old. | ||
</div> | ||
); | ||
} | ||
``` | ||
|
||
You can set default props by setting a default value in the function signature. | ||
|
||
```tsx | ||
type GreetingProps = { | ||
name?: string; // name is optional! | ||
} | ||
|
||
function Greeting({ name = "User" }: GreetingProps) { | ||
// name is at least "User" | ||
return <div>Hello {name}!</div> | ||
} | ||
``` | ||
Comment on lines
-82
to
-93
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. This is explanation for Default parameters. 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. I think the section was great and we should keep it. People switching from prop types over to TS will naturally ask these questions as most linters treat prop-types and default props as the same. We should cater to those users too 👍 |
||
|
||
Preact also ships a `FunctionComponent` type to annotate anonymous functions. `FunctionComponent` also adds a type for `children`: | ||
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.
|
||
`children` is `ComponentChildren` type. `children?` as `ComponentChildren` type is added to `FunctionComponent`'s `props` by `RenderableProps` type: | ||
|
||
```tsx | ||
import { h, FunctionComponent } from "preact"; | ||
|
||
const Card: FunctionComponent<{ title: string }> = ({ title, children }) => { | ||
return ( | ||
<div class="card"> | ||
<h1>{title}</h1> | ||
{children} | ||
</div> | ||
); | ||
}; | ||
```ts | ||
type RenderableProps<P, RefType = any> = P & Readonly<Attributes & { children?: ComponentChildren; ref?: Ref<RefType> }>; | ||
``` | ||
|
||
`children` is of type `ComponentChildren`. You can specify children on your own using this type: | ||
|
||
When `children` is required, it need to be specified: | ||
|
||
```tsx | ||
import { h, ComponentChildren } from "preact"; | ||
import { h, FunctionComponent } from 'preact'; | ||
|
||
type ChildrenProps = { | ||
type Props = { | ||
title: string; | ||
children: ComponentChildren; | ||
} | ||
|
||
function Card({ title, children }: ChildrenProps) { | ||
const Card: FunctionComponent<Props> = function ({ title, children }) { | ||
return ( | ||
<div class="card"> | ||
<div> | ||
<h1>{title}</h1> | ||
{children} | ||
</div> | ||
); | ||
}; | ||
``` | ||
|
||
### Class components | ||
## Class Components | ||
|
||
Preact's `Component` class is a [generic class](https://www.typescriptlang.org/docs/handbook/generics.html#generic-classes). This type has two generic type parameters which correspond `props` and `state`. These generic type parameters are empty objects by default. `children?` as `ComponentChildren` type is added to `Component`'s `props` by `RenderableProps` type: | ||
|
||
```ts | ||
interface Component<P = {}, S = {}> { | ||
componentWillMount?(): void; | ||
componentDidMount?(): void; | ||
componentWillUnmount?(): void; | ||
getChildContext?(): object; | ||
componentWillReceiveProps?(nextProps: Readonly<P>, nextContext: any): void; | ||
shouldComponentUpdate?( | ||
nextProps: Readonly<P>, | ||
nextState: Readonly<S>, | ||
nextContext: any | ||
): boolean; | ||
componentWillUpdate?( | ||
nextProps: Readonly<P>, | ||
nextState: Readonly<S>, | ||
nextContext: any | ||
): void; | ||
getSnapshotBeforeUpdate?(oldProps: Readonly<P>, oldState: Readonly<S>): any; | ||
componentDidUpdate?( | ||
previousProps: Readonly<P>, | ||
previousState: Readonly<S>, | ||
snapshot: any | ||
): void; | ||
componentDidCatch?(error: any, errorInfo: any): void; | ||
} | ||
|
||
abstract class Component<P, S> { | ||
constructor(props?: P, context?: any); | ||
|
||
static displayName?: string; | ||
static defaultProps?: any; | ||
static contextType?: Context<any>; | ||
|
||
static getDerivedStateFromProps?( | ||
props: Readonly<object>, | ||
state: Readonly<object> | ||
): object | null; | ||
static getDerivedStateFromError?(error: any): object | null; | ||
|
||
state: Readonly<S>; | ||
props: RenderableProps<P>; | ||
context: any; | ||
base?: Element | Text; | ||
|
||
setState<K extends keyof S>( | ||
state: | ||
| (( | ||
prevState: Readonly<S>, | ||
props: Readonly<P> | ||
) => Pick<S, K> | Partial<S> | null) | ||
| (Pick<S, K> | Partial<S> | null), | ||
callback?: () => void | ||
): void; | ||
|
||
forceUpdate(callback?: () => void): void; | ||
|
||
abstract render( | ||
props?: RenderableProps<P>, | ||
state?: Readonly<S>, | ||
context?: any | ||
): ComponentChild; | ||
} | ||
``` | ||
|
||
Preact's `Component` class is typed as a generic with two generic type variables: Props and State. Both types default to the empty object, and you can specify them according to your needs. | ||
You can implement a class component: | ||
|
||
```tsx | ||
import { h, Component } from 'preact'; | ||
|
||
// Types for props | ||
// children is required. | ||
type ExpandableProps = { | ||
title: string; | ||
children: ComponentChildren; | ||
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. This line is unnecessary. I made mistake. |
||
}; | ||
|
||
// Types for state | ||
|
@@ -144,25 +206,22 @@ type ExpandableState = { | |
}; | ||
|
||
|
||
// Bind generics to ExpandableProps and ExpandableState | ||
// ExpandableProps and ExpandableState are passed as generic type parameter. | ||
class Expandable extends Component<ExpandableProps, ExpandableState> { | ||
constructor(props: ExpandableProps) { | ||
super(props); | ||
// this.state is an object with a boolean field `toggle` | ||
// due to ExpandableState | ||
// `this.state` is a object with `toggle` property which is boolean type by ExpandableState. | ||
this.state = { | ||
toggled: false | ||
}; | ||
} | ||
// `this.props.title` is string due to ExpandableProps | ||
render() { | ||
return ( | ||
<div class="expandable"> | ||
<div> | ||
<h2> | ||
// `this.props.title` is string type by ExpandableProps. | ||
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. I need to check if our syntax highlighter can handle line comments in JSX |
||
{this.props.title}{" "} | ||
<button | ||
onClick={() => this.setState({ toggled: !this.state.toggled })} | ||
> | ||
<button onClick={() => this.setState({ toggled: !this.state.toggled })}> | ||
Toggle | ||
</button> | ||
</h2> | ||
|
@@ -173,18 +232,17 @@ class Expandable extends Component<ExpandableProps, ExpandableState> { | |
} | ||
``` | ||
|
||
Class components include children by default, typed as `ComponentChildren`. | ||
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.
type RenderableProps<P, RefType = any> = P & Readonly<Attributes & { children?: ComponentChildren; ref?: Ref<RefType> }>; |
||
## Events | ||
|
||
## Typing events | ||
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. I think it is necessary to describe about |
||
|
||
Preact emits regular DOM events. As long as your TypeScript project includes the `dom` library (set it in `tsconfig.json`), you have access to all event types that are available in your current configuration. | ||
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.
There is "standard DOM event" in other page. |
||
Preact uses standard DOM events. Include `"DOM"` in TypeScript's [lib](https://www.typescriptlang.org/tsconfig#lib) compiler option to enable standard DOM event types. | ||
|
||
```tsx | ||
import { h, Component } from 'preact'; | ||
|
||
export class Button extends Component { | ||
handleClick(event: MouseEvent) { | ||
event.preventDefault(); | ||
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. This line is unnecessary. Therefore, I removed it. |
||
if (event.target instanceof HTMLElement) { | ||
alert(event.target.tagName); // Alerts BUTTON | ||
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. Another one uses 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. Good call! |
||
console.log(event.target.tagName); // "BUTTON" | ||
} | ||
} | ||
|
||
|
@@ -194,52 +252,67 @@ export class Button extends Component { | |
} | ||
``` | ||
|
||
You can restrict event handlers by adding a type annotation for `this` to the function signature as the first argument. This argument will be erased after transpilation. | ||
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. This content is an explanation about this parameters. |
||
You can provide a more specific type than `EventTarget` by using [this parameters](https://www.typescriptlang.org/docs/handbook/functions.html#this-parameters): | ||
|
||
```tsx | ||
import { h, Component } from 'preact'; | ||
|
||
export class Button extends Component { | ||
// Adding the this argument restricts binding | ||
// Define `this parameters` | ||
handleClick(this: HTMLButtonElement, event: MouseEvent) { | ||
event.preventDefault(); | ||
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. This line is unnecessary. Therefore, I removed it. |
||
if (event.target instanceof HTMLElement) { | ||
console.log(event.target.localName); // "button" | ||
} | ||
console.log(this.tagName); // "BUTTON" | ||
} | ||
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. I think this example should use 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. I agree, this is much more concise and showcases this binding much better 👍 |
||
|
||
render() { | ||
return ( | ||
<button onClick={this.handleClick}>{this.props.children}</button> | ||
); | ||
Comment on lines
-210
to
-212
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. Another one does not use 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. This is the formatting that prettier applies by default and we have that on other pages of our docs |
||
return <button onClick={this.handleClick}>{this.props.children}</button>; | ||
} | ||
} | ||
``` | ||
|
||
## Typing references | ||
You can provide a more specific type than `EventTarget` by using `TargetedEvent`. `TargetedEvent` type has two generic type parameters which corresponds `currentTarget`'s type and Event type: | ||
|
||
The `createRef` function is also generic, and lets you bind references to element types. In this example, we ensure that the reference can only be bound to `HTMLAnchorElement`. Using `ref` with any other element lets TypeScript thrown an error: | ||
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.
This is explanation about TypeScript |
||
```tsx | ||
import { h, Component, JSX } from 'preact'; | ||
|
||
export class Button extends Component { | ||
handleClick({ currentTarget }: JSX.TargetedEvent<HTMLButtonElement, Event> { | ||
console.log(currentTarget.tagName); // "BUTTON" | ||
} | ||
|
||
render() { | ||
return <button onClick={this.handleClick}>{this.props.children}</button>; | ||
} | ||
} | ||
``` | ||
|
||
## References | ||
|
||
`createRef()`is [generic type](https://www.typescriptlang.org/docs/handbook/generics.html#generic-types). You can specify a reference type by passing it as `createRef()`'s generic type parameter: | ||
|
||
```ts | ||
function createRef<T = any>(): RefObject<T>; | ||
type RefObject<T> = { current: T | null }; | ||
``` | ||
|
||
The following code is usage: | ||
|
||
```tsx | ||
import { h, Component, createRef } from "preact"; | ||
|
||
class Foo extends Component { | ||
ref = createRef<HTMLAnchorElement>(); | ||
export class Button extends Component { | ||
ref = createRef<HTMLButtonElement>(); | ||
|
||
componentDidMount() { | ||
// current is of type HTMLAnchorElement | ||
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.
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. Good catch! |
||
console.log(this.ref.current); | ||
console.log(this.ref.current.tagName); // "BUTTON" | ||
} | ||
|
||
render() { | ||
return <div ref={this.ref}>Foo</div>; | ||
// ~~~ | ||
// 💥 Error! Ref only can be used for HTMLAnchorElement | ||
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. This content is repeating. |
||
return <button ref={this.ref}>{this.props.children}</button>; | ||
} | ||
} | ||
``` | ||
|
||
This helps a lot if you want to make sure that the elements you `ref` to are input elements that can be e.g. focussed. | ||
|
||
## Typing context | ||
## Context | ||
|
||
`createContext` tries to infer as much as possible from the intial values you pass to: | ||
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. TypeScript infers, not |
||
|
||
|
@@ -323,7 +396,7 @@ function App() { | |
|
||
All values become optional, so you have to do null checks when using them. | ||
|
||
## Typing hooks | ||
## Hooks | ||
|
||
Most hooks don't need any special typing information, but can infer types from usage. | ||
|
||
|
@@ -350,7 +423,7 @@ const Counter = ({ initial = 0 }) => { | |
|
||
`useEffect` does extra checks so you only return cleanup functions. | ||
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. It seems this line is mere explanation about |
||
|
||
```typescript | ||
```ts | ||
useEffect(() => { | ||
const handler = () => { | ||
document.title = window.innerWidth.toString(); | ||
|
@@ -412,7 +485,7 @@ function TextInputWithFocusButton() { | |
|
||
For the `useReducer` hook, TypeScript tries to infer as many types as possible from the reducer function. See for example a reducer for a counter. | ||
|
||
```typescript | ||
```ts | ||
// The state type for the reducer function | ||
type StateType = { | ||
count: number; | ||
|
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.
This is a description of the VSCode features and methods of adding types.
I think it is unnecessary in Preact document.
Therefore, I removed this line.
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.
A thought: may we could have this just be a short "tip" like this:
the markup I used there was:
> 💁 You can also use Preact's type definitions in plain JavaScript with [JSDoc annotations](https://fettblog.eu/typescript-jsdoc-superpowers/).
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.
I changed to your suggestion.