TypeScript is a superset of JS types and supports generics, types, namespaces, enumerations and other features, which make up for the disadvantages of JS in large-scale application development. This article explores the postures of writing React components in TypeScript versions.

Before starting to integrate TypeScript into existing React projects, take a look at how create-React-app works.

Take a look at create-React-app

Start by creating a new project called my-app:

create-react-app my-app --scripts-version=react-scripts-ts
Copy the code

React-scripts-ts is a family of adapters that take advantage of the standard create-React-app engineering pipeline and mix in TypeScript. The engineering structure should be as follows:

My - app / ├ ─. Gitignore ├ ─ node_modules / ├ ─ public / ├ ─ SRC / │ └ ─... ├─ Package. json ├─ tsconfig.json ├─ tslint.jsonCopy the code

Note:

  • tsconfig.jsonIncludes typescript-specific configuration options for projects.
  • tslint.jsonSaves the Settings for the code inspector to use,TSLint.
  • package.jsonIncludes dependencies, and shortcuts to commands such as test commands, preview commands, and publish application commands.
  • publicContains static resources such as HTML pages or images. In addition toindex.htmlExcept files, other files can be deleted.
  • srcIncludes TypeScript and CSS source code.index.tsxIs a mandatory entry file.

@types

Json file and check devDependencies to find a series of @types files as follows:

"devDependencies": {
    "@types/node": "^ 12.6.9"."@types/react": "^ 16.8.24"."@types/react-dom": "^ 16.8.5"."typescript": "^ 3.5.3." "
}
Copy the code

Using the @types/ prefix means that we need to get the React and react-dom declaration files in addition (see article for the declaration files). Usually when you import a path like “React”, it looks at the React package; However, not all packages contain declaration files, so TypeScript also looks at the @types/ React package.

Without these @types files, we would get an error if we introduced React or ReactDOM in the TSX component:

Cannot find module ‘react’

Cannot find module ‘react-dom’

React and react-dom are not developed using TS, so TS does not know the types of React and react-dom, and what the module exports. Fortunately, DefinitelyTyped is a declaration file for these commonly used modules already published in the community.

So if our project is not created using create-react-app, remember NPM install @types/ XXX.

tsconfig.json

If a tsconfig.json file exists in a directory, it means that the directory is the root of the TypeScript project. The tsconfig.json file specifies the root file and compilation options to compile this project.

Generate your own tsconfig.json configuration file by executing TSC –init, as shown below.

{
    "compilerOptions": {
        "outDir": "./dist/"."sourceMap": true."noImplicitAny": true."module": "commonjs"."target": "es5"."jsx": "react"
    },
    "include": [
        "./src/**/*"]}Copy the code
  • Target: By default, the build target is ES5, if you only want to publish to an ES6-compatible browser, you can also configure it to be ES6. However, if configured for ES6, some older browsers (such as Internet Explorer) will throw Syntax Error.
  • NoImplicitAny: whennoImplicitAnyMark isfalseIf the compiler cannot infer the type of the variable from its purpose, it will quietly default the variable type toany. This is theImplicit anyThe meaning of. whennoImplicitAnyMark istrueAnd when the TypeScript compiler can’t infer a type, it still generates JavaScript files. But it also doesReporting an error.

Use ESLint for code checking

Install esLint dependencies

npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-config-alloy  babel-eslint --save-dev
Copy the code
  1. @typescript-eslint/parser: Converts TypeScript to ESTree for ESLint to recognize
  2. @typescript-eslint/eslint-plugin: is just a list of rules that can be turned on or off

Create the configuration file.eslintrc.js and write the rules

module.exports = {
    parser: "@typescript-eslint/parser".extends: ["plugin:@typescript-eslint/recommended"."react-app"].plugins: ["@typescript-eslint"."react"].rules: {
        // ...}}Copy the code

TypeScript rules for AlloyTeam ESLint are used here

Then add configuration to package.json to check all ts files in the SRC directory.

"scripts": {
	"eslint": "eslint src --ext .ts,.js,.tsx,.jsx"
}
Copy the code

NPM run esLint checks all SRC files with the.ts,.js,.tsx,.jsx suffixes

Installing the prettier dependency

npm i prettier eslint-config-prettier eslint-plugin-prettier -D
Copy the code
  1. prettier: Formatting rule program
  2. eslint-config-prettier: will disable anything that may interfere with existingprettierThe rules oflintingThe rules
  3. eslint-plugin-prettierPrettier analysis will be run as part of ESlint.
module.exports = {
  parser: '@typescript-eslint/parser'.extends: [
    'plugin:@typescript-eslint/recommended'.'plugin:prettier/recommended',].plugins: ['@typescript-eslint'.'react'].rules: {}};Copy the code

When Prettier is used in older projects, it causes too many errors

Visual Studio Code integrates ESLint with Prettier

To enable typescript support for vscode’s eslint plugin, add the following configuration to.vscode/settings.json.

"eslint.validate": [
    "javascript"."javascriptreact",
    {
      "language": "typescript"."autoFix": true
    },
    {
      "language": "typescriptreact"."autoFix": true}]Copy the code

Configure the Loader in WebPack

Modify the webpack.config.js file

module.exports = {
    entry: "./src/index.tsx".output: {
        filename: "bundle.js".path: __dirname + "/dist"
    },

    devtool: "source-map".resolve: {
        extensions: [".ts".".tsx".".js".".json"]},module: {
        rules: [{test: /\.tsx? $/.loader: "awesome-typescript-loader" },
            { enforce: "pre".test: /\.js$/.loader: "source-map-loader"}},externals: {
        "react": "React"."react-dom": "ReactDOM"}};Copy the code

Awed-typescript-loader is used to compile TS files, and ts-loader can also be used. For the difference, see awed-typescript-loader & TS-loader

Component development

Stateful component development

Define the interface

When we pass props to a component, if we want the props to apply interface, we force the props to conform to the structure of the interface, ensuring that the members are declared and preventing the undesired props from being passed.

An interface can be defined outside of a component or in a separate file. You can define an interface like this

interface FormProps { first_name: string; last_name: string; age: number; agreetoterms? : boolean; }Copy the code

Here we create a FormProps interface that contains some values. We can also apply an interface to the state of the component

interface FormState { submitted? : boolean; full_name: string; age: number; }Copy the code

Apply interfaces to components

We can apply interfaces to both class and stateless components. For class components, we use Angle bracket syntax to apply our props and state interfaces, respectively.

export class MyForm extends React.Component<FormProps, FormState> {
	...
}
Copy the code

Note: In the case of props, where there is only state but no props, the positions of props can be filled with {} or object, both of which represent valid empty objects.

For purely functional components, we can pass the props Interface directly

function MyForm(props: FormProps) {
	...
}
Copy the code

The introduction of interface

By convention, we create a ** SRC /types/** directory to group all your interfaces:

// src/types/index.tsx
export interface FormProps {
    first_name: string; last_name: string; age: number; agreetoterms? : boolean; }Copy the code

It then introduces the interface required by the component

// src/components/MyForm.tsx
import React from 'react';
import { StoreState } from '.. /types/index'; .Copy the code

Stateless component development

Stateless components are also called presentation components, and a presentation component can be written as a purely functional component if it has no internal state. SFC

= StatelessComponent

; . When we write function components, we can specify our component as SFC or StatelessComponent. This one has already predefined children and so on, so we don’t have to specify the type of children each time.

Node_modules /@types/react/index.d.ts

type SFC<P = {}> = StatelessComponent<P>; interface StatelessComponent<P = {}> { (props: P & { children? : ReactNode }, context? : any): ReactElement<any> |null; propTypes? : ValidationMap<P>; contextTypes? : ValidationMap<any>; defaultProps? : Partial<P>; displayName? : string; }Copy the code

Use SFC for stateless component development.

import React, { ReactNode, SFC } from 'react';
import style from './step-complete.less';

export interface IProps  {
  title: string | ReactNode;
  description: string | ReactNode;
}
const StepComplete:SFC<IProps> = ({ title, description, children }) = > {
  return (
    <div className={style.complete}>
      <div className={style.completeTitle}>
        {title}
      </div>
      <div className={style.completeSubTitle}>
        {description}
      </div>
      <div>
        {children}
      </div>
    </div>
  );
};
export default StepComplete;
Copy the code

The event processing

We often use event event objects in event handlers when registering events. For example, when using mouse events, we use clientX, clientY to get pointer coordinates.

You could have thought of setting the event to type ANY, but that would have lost the point of statically checking our code.

function handleEvent (event: any) {
  console.log(event.clientY)
}
Copy the code

Imagine registering a Touch event and mistakenly getting the value of its clientY property from the event object in the event handler. In this case, we’ve set the event to any, so TypeScript doesn’t tell us when we compile. There is a problem when we access it via event.clientY, because the event object for the Touch event does not have clientY.

Writing a type declaration for an event object through an interface is a waste of time. Fortunately, the React declaration file provides a type declaration for the event object.

Event Indicates the type of the Event object

Common Event Event object types:

  • ClipboardEvent<T = Element>Clipboard event object
  • DragEvent<T = Element>Drag and drop event objects
  • ChangeEvent<T = Element>Change event object
  • KeyboardEvent<T = Element>Keyboard event object
  • MouseEvent<T = Element>Mouse event object
  • TouchEvent<T = Element>Touch event object
  • WheelEvent<T = Element>Wheel event object
  • AnimationEvent<T = Element>Animate event object
  • TransitionEvent<T = Element>Transition event object

Example:

import { MouseEvent } from 'react';

interface IProps {
  onClick (event: MouseEvent<HTMLDivElement>): void,}Copy the code

Promise type

We often use async functions for asynchronous operations, which return a Promise object when called. We can add callbacks using the then method.

Promise

is a generic type, and the T generic variable is used to determine the parameter type of the first callback function (onfulfilled) received when using the THEN method.

interface IResponse<T> { message: string, result: T, success: boolean, } async function getResponse (): Promise<IResponse<number[]>> {return {message: 'get success ', result: [1, 2, 3], success: true, } } getResponse() .then(response => { console.log(response.result) })Copy the code

We first declare a generic interface for IResponse to define the type of Response, and use the T generic variable to determine the type of result.

We then declare an asynchronous function getResponse and define the type of the return value of the function as Promise

>.

Finally, calling the getResponse method returns a promise, called through then. The then method receives the first callback function response of type {message: string, result: Number [], success: Boolean}.

Generic components

Tool generics usage tips

typeof

We usually define types first and then assign them to use, but with Typeof we can reverse the order of use.

const options = {
  a: 1
}
type Options = typeof options
Copy the code

Use string literal types to limit values to fixed string arguments

Limit the value of props. Color to the strings red, blue, and yellow.

interface IProps {
  color: 'red' | 'blue' | 'yellow',}Copy the code

Use numeric literal types to limit values to fixed numeric parameters

Limit the value of props. Index to the numbers 0, 1, 2.

interface IProps {
 index: 0 | 1 | 2,
}
Copy the code

usePartialAll of thepropsProperties become optional

Partial ` implementation source ` node_modules/typescript/lib/lib. Es5. Which stype Partial<T> = { [P inkeyof T]? : T[P] };Copy the code

Keyof T retrieves all property names of T, then in iterates, assigning values to P, and finally T[P] retrieves the values of the corresponding property, middle? Used to set to optional values.

We can do this with Partial if all properties of props are optional.

import { MouseEvent } from 'react'
import * as React from 'react'
interface IProps {
  color: 'red' | 'blue' | 'yellow',
  onClick (event: MouseEvent<HTMLDivElement>): void,
}
const Button: SFC<Partial<IProps>> = ({onClick, children, color}) => {
  return (
    <div onClick={onClick}>
      { children }
    </div>
  )
Copy the code

useRequiredWill allpropsProperties are set to mandatory

Required to achieve the source code node_modules/typescript/lib/lib. Es5. Which s.

type Required<T> = { [P inkeyof T]-? : T[P] };Copy the code

See here, friends may be a little confused, -? What does it do, actually? Is the function of the optional property, right? Removing this property makes it mandatory, and the corresponding +? , function and -? Instead, you make the property optional.

Conditions in the

TypeScript2.8 introduces conditional types, which can be used to determine the type based on the features of other types.

T extends U ? X : Y
Copy the code

The original

interface Id { id: number, /* other fields */ }
interface Name { name: string, /* other fields */ }
declare function createLabel(id: number): Id;
declare function createLabel(name: string): Name;
declare function createLabel(name: string | number): Id | Name;
Copy the code

Use condition type

type IdOrName<T extends number | string> = T extends number ? Id : Name;
declare function createLabel<T extends number | string>(idOrName: T): T extends number ? Id : Name;
Copy the code

Exclude<T,U>

Exclude from T those types that can be assigned to U.

Exclude implementation source node_modules/typescript/lib/lib. Es5. Which s.

type Exclude<T, U> = T extends U ? never : T;
Copy the code

Example:

type T = Exclude<1|2|3|4|5, 3|4>  // T = 1|2|5 
Copy the code

In this case, the value of type T can only be 1, 2, or 5. If other values are used, an error message will be displayed.

Error:(8, 5) TS2322: Type '3' is not assignable to type '1 | 2 | 5'.
Copy the code

Extract<T,U>

Extract the types from T that can be assigned to U.

Extract implementation source node_modules/typescript/lib/lib. Es5. Which s.

type Extract<T, U> = T extends U ? T : never;
Copy the code

Example:

type T = Extract<1|2|3|4|5, 3|4>  // T = 3|4
Copy the code

In this case, the value of type T can only be 3 or 4. If other values are used, TS will display an error message:

Error:(8, 5) TS2322: Type '5' is not assignable to type 3 | '4'.
Copy the code

Pick<T,K>

Take a set of properties of K from T.

Pick implementation source node_modules/typescript/lib/lib. Es5. Which s.

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
Copy the code

Example:

If we now have a type with name, age, and sex attributes, we can generate a new type that supports only name and age as follows:

interface Person {
  name: string,
  age: number,
  sex: string,
}
let person: Pick<Person, 'name' | 'age'> = {
  name: 'wang',
  age: 21,
}
Copy the code

Record<K,T>

Convert the values of all attributes in K to type T.

Record implementation source node_modules/typescript/lib/lib. Es5. Which s.

type Record<K extends keyof any, T> = {
    [P in K]: T;
};
Copy the code

Example:

Set the name and age attributes to string.

let person: Record<'name' | 'age', string> = {
  name: 'wang',
  age: '12',}Copy the code

Omit<T,K> (not built-in)

Exclude key from object T as an attribute of K.

Since TS is not built in, we need to use Pick and Exclude to implement it.

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
Copy the code

Example:

Exclude the name attribute.

interface Person {
  name: string,
  age: number,
  sex: string,
}


let person: Omit<Person, 'name'> = {
  age: 1,
  sex: 'male'
}
Copy the code

NonNullable<T>

Exclude T from null and undefined.

NonNullable implementation source node_modules/typescript/lib/lib. Es5. Which s.

type NonNullable<T> = T extends null | undefined ? never : T;
Copy the code

Example:

type T = NonNullable<string | string[] | null | undefined>; // string | string[]
Copy the code

ReturnType<T>

Gets the type of the return value of function T.

ReturnType implementation source node_modules/typescript/lib/lib. Es5. Which s.

typeReturnType<T extends (... args: any[]) => any> = T extends (... args: any[]) => infer R ? R : any;Copy the code

Infer R is equivalent to declaring a variable and receiving the return value type of the incoming function.

Example:

type T1 = ReturnType<() => string>; // string
type T2 = ReturnType<(s: string) => void>; // void
Copy the code