Skip to content
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

Closed
wants to merge 17 commits into from
235 changes: 154 additions & 81 deletions content/en/guide/v10/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor Author

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.

Copy link
Member

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:

Screen Shot 2020-12-07 at 10 31 46 AM

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/).

Copy link
Contributor Author

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.

> 💁 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:

Expand All @@ -30,6 +30,7 @@ TypeScript includes a full-fledged JSX compiler that you can use instead of Babe
}
}
```

```json
// TypeScript >= 4.1.1
{
Expand All @@ -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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript sees a function as functional component as long as it returns JSX.

Unless a function is matched FunctionComponent or FunctionalComponent, it is not considered a functional components.
This is incorrect.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Contributor Author

@38elements 38elements Dec 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is explanation for Default parameters.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters
Therefore, this is not appropriate in TypeScript page of Preact document.

Copy link
Member

Choose a reason for hiding this comment

The 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`:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FunctionComponent type corresponds all functional components, not only an anonymous function.
This sentence is confusing.

`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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is unnecessary. I made mistake.

};

// Types for state
Expand All @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The 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>
Expand All @@ -173,18 +232,17 @@ class Expandable extends Component<ExpandableProps, ExpandableState> {
}
```

Class components include children by default, typed as `ComponentChildren`.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

children? is default, not children.

type RenderableProps<P, RefType = any> = P & Readonly<Attributes & { children?: ComponentChildren; ref?: Ref<RefType> }>;

## Events

## Typing events
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is necessary to describe about TargetedEvent, since TargetedEvent does not exist in React.


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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Preact core, onChange is the standard [DOM change event]

There is "standard DOM event" in other page.
Therefore, I change to "standard DOM event".
Element emits event.

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();
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another one uses console.log().
It is inconsistent.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call!

console.log(event.target.tagName); // "BUTTON"
}
}

Expand All @@ -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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This content is an explanation about this parameters.
Therefore, This is not appropriate in Preact document.

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();
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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"
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this example should use this.
Since this is HTMLButtonElement, instanceof is unnecessary.
Another example uses tagName.

Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor Author

@38elements 38elements Dec 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another one does not use ( and ) . It is inconsistent. This ( and ) are unnecessary. Therefore, I removed it.

Copy link
Member

Choose a reason for hiding this comment

The 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:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using ref with any other element lets TypeScript thrown an error.

This is explanation about TypeScript TypeError.
I think it is unnecessary in Preact document.
Therefore, I removed this sentence.

```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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.ref.current is div.
This comment is confusing.

Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript infers, not createContext.


Expand Down Expand Up @@ -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.

Expand All @@ -350,7 +423,7 @@ const Counter = ({ initial = 0 }) => {

`useEffect` does extra checks so you only return cleanup functions.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this line is mere explanation about useEffect. Since Hooks page is suitable, this line is unnecessary.


```typescript
```ts
useEffect(() => {
const handler = () => {
document.title = window.innerWidth.toString();
Expand Down Expand Up @@ -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;
Expand Down