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.json
Includes typescript-specific configuration options for projects.tslint.json
Saves the Settings for the code inspector to use,TSLint.package.json
Includes dependencies, and shortcuts to commands such as test commands, preview commands, and publish application commands.public
Contains static resources such as HTML pages or images. In addition toindex.html
Except files, other files can be deleted.src
Includes TypeScript and CSS source code.index.tsx
Is 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: when
noImplicitAny
Mark isfalse
If 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. whennoImplicitAny
Mark istrue
And 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
@typescript-eslint/parser
: Converts TypeScript to ESTree for ESLint to recognize@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
prettier
: Formatting rule programeslint-config-prettier
: will disable anything that may interfere with existingprettier
The rules oflinting
The ruleseslint-plugin-prettier
Prettier 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 objectDragEvent<T = Element>
Drag and drop event objectsChangeEvent<T = Element>
Change event objectKeyboardEvent<T = Element>
Keyboard event objectMouseEvent<T = Element>
Mouse event objectTouchEvent<T = Element>
Touch event objectWheelEvent<T = Element>
Wheel event objectAnimationEvent<T = Element>
Animate event objectTransitionEvent<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
usePartial
All of theprops
Properties 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
useRequired
Will allprops
Properties 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