This article introduces some best practices for using TypeScript in React projects.
I. Project initialization
1. Build projects
The quickest way to initialize a React with TypeScript application is to use facebook’s official scaffolding create-React -app, which provides TypeScript templates. Run the following commands:
$ npx create-react-app my-app --template typescript
Copy the code
The generated project file is a little different, with the following configuration added:
.tsx
Use:TypeScript
的JSX
File extension;tsconfig.json
: Generated with default configurationTypeScript
Configuration file;react-app-env.d.ts
:TypeScript
The contents of the comment are used as compiler instructions.
2. VSCode extension and Settings
Install plug-in:
ESLint
Prettier - Code formatter
In the VSCode configuration file (.vscode/settings.json, the shortcut Command is Command + Shift + P/Ctrl + Shift + P) add the following configuration:
{
"eslint.options": {
"configFile": ".eslintrc.js"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"files.autoSave": "onFocusChange"."editor.formatOnType": true."editor.formatOnSave": true."eslint.alwaysShowStatus": true
}
Copy the code
The purpose of adding these configurations is to make VSCode better display ESLint errors/warnings, making it easier to find code errors, format code, and so on.
3. Configure ESLint/Prettier
- add
ESlint
Related dependencies:
$ yarn add @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react --dev
Copy the code
The dependency to install ESLint is not selected here because the create-react-app scaffolding, the react-scripts package, already relies on ESLint and may cause version inconsistencies if installed repeatedly.
- Create one in the root directory
.eslintrc.js
File and add the following:
module.exports = {
parser: '@typescript-eslint/parser'.// Specify the ESLint parser
extends: [
'plugin:react/recommended'.// Use the recommended rules from @eslint-plugin-react
'plugin:@typescript-eslint/recommended'.// Use the recommended rules from @typescript-eslint/eslint-plugin].parserOptions: {
ecmaVersion: 2018.// Allows parsing of the latest ECMAScript features
sourceType: 'module'.// Import is allowed
ecmaFeatures: {
jsx: true.// Allow parsing of JSX}},rules: {
// Custom rules
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
'linebreak-style': 'off'.'prettier/prettier': [
'error',
{
endOfLine: 'auto',}]},settings: {
react: {
version: 'detect'.// tell eslint-plugin-react to automatically detect the react version,}}};Copy the code
- add
Prettier
Rely on:
$ yarn add prettier eslint-config-prettier eslint-plugin-prettier --dev
Copy the code
- Create one in the root directory
.prettierrc.js
File and add the following:
module.exports = {
semi: true.trailingComma: 'all'.singleQuote: true.printWidth: 120.tabWidth: 2};Copy the code
- update
.eslintrc.js
File:
module.exports = {
parser: '@typescript-eslint/parser'.// Specify the ESLint parser
extends: [
'plugin:react/recommended'.// Use the recommended rules from @eslint-plugin-react
'plugin:@typescript-eslint/recommended'.// Use the recommended rules from @typescript-eslint/eslint-plugin
'prettier/@typescript-eslint'.// Use eslint-config-prettier to disable the ESLint rule from @typescript-eslint where prettier conflicts with ESLint
'plugin:prettier/recommended',].parserOptions: {
ecmaVersion: 2018.// Allows parsing of the latest ECMAScript features
sourceType: 'module'.// Import is allowed
ecmaFeatures: {
jsx: true.// Allow parsing of JSX}},rules: {
// Custom rules
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
'linebreak-style': 'off'.'prettier/prettier': [
'error',
{
endOfLine: 'auto',}]},settings: {
react: {
version: 'detect'.// tell eslint-plugin-react to automatically detect the react version,}}};Copy the code
- increase
lint
和format
Command:
"scripts": {
"format": "prettier --write src/**/*.ts{,x}"."lint": "tsc --noEmit && eslint src/**/*.ts{,x}"
}
Copy the code
It is recommended that lint checks be added to commit hooks to avoid bad code or non-conforming code commits and pushes.
Second, best practices
1. Writing of components
Functional components are usually written in one of two ways:
import React from 'react';
// function declaration
function Heading() :React.ReactNode {
return <h1>My Website Heading</h1>;
}
// Function extension
const OtherHeading: React.FC = () = > <h1>My Website Heading</h1>;
Copy the code
- The first is written declaratively, stating that the return value of the function is
React.ReactNode
Type; - The second way to write: use the function expression writing method, return afunctionNot a value or expression, so specify that the return value of this function is
React.FC
Type.
While developing, you can write either way, but the development team needs to be consistent and stick to one.
2, Props,
In TypeScript, we can use the interface or type keyword to declare props as generic.
type RowProps<Type> = {
input: Type | Type[],
onClick: () = > void, color? : string, };const Row: React.FC<RowProps<number | string>> = ({
input,
onClick,
color = 'blue',}) = > {
if (Array.isArray(input)) {
return (
<div>
{input.map((i, idx) => (
<div key={idx} onClick={onClick}>
{i}
</div>
))}
</div>
);
}
return <div style={{ color}} >{input}</div>;
};
const Rows = () = > {
return (
<Fragment>
<Row input={[1]} onClick={()= >{}} / ><Row input={1} onClick={()= > {}} color={'red'} />
<Row input={1} onClick={()= >{}} / ><Row input='1' onClick={()= >{}} / ><Row input={['1']} onClick={()= >{}} / ></Fragment>
);
};
Copy the code
Props can be divided into several subsets, which can be classified into the following four categories based on actual application scenarios:
-
OwnProps, properties passed directly to the current component;
-
StateProps, properties obtained by connecting to store in redux;
-
DispatchProps, which is also a property obtained via connect to Redux;
-
RouteProps, a property passed through the route route in the react-router-dom
Multiple props can be connected using an ampersand:
type Props = OwnProps & RouteProps & StateProps & DispatchProps;
Copy the code
As for how to choose interface and type, my personal opinion is that both are ok, but the team members need to maintain a unified style. If you use interface, you can use the extends keyword to connect multiple props:
interface Props extends OwnProps, RouteProps, StateProps, DispatchProps {}
Copy the code
Here are two limitationsprops
Two common methods of property.
- use
Partial
All of theprops
Properties become optional
If all properties of props are optional, we can do this with Partial.
import React from 'react';
import { MouseEvent } from 'react';
type IProps = {
color: 'red' | 'blue' | 'yellow';
text: string;
onClick(event: MouseEvent<HTMLDivElement>): void;
};
const Com: React.FC<Partial<IProps>> = ({ onClick, text, color }) = > {
return (
<div onClick={onClick} style={{ color}} >
{text}
</div>
);
};
const App: React.FC = () = > {
return <Com text="click" color={'blue'} / >;
};
export default App;
Copy the code
- use
Required
Will allprops
Properties are set to mandatory
If all properties of props were mandatory, we could implement this with Required:
import React from 'react';
import { MouseEvent } from 'react';
type IProps = {
color: 'red' | 'blue' | 'yellow';
text: string;
onClick(event: MouseEvent<HTMLDivElement>): void;
};
const Com: React.FC<Required<IProps>> = ({ onClick, text, color }) = > {
return (
<div onClick={onClick} style={{ color}} >
{text}
</div>
);
};
const App: React.FC = () = > {
return <Com text="click" color={'blue'} / >;
};
export default App;
Copy the code
Set up theProps
The default value of
There are two ways to set the default values for Props.
- pass
props
, set the default value
/* eslint-disable react/prop-types */
import React from 'react'; type IProps = { color? :'red' | 'blue' | 'yellow';
text: string;
};
// Set the default value when passing props
const Com: React.FC<IProps> = ({ text, color = 'red' }) = > {
return <div style={{ color}} >{text}</div>;
};
const App: React.FC = () = > {
return (
<div>
<Com text="click" />
<Com text="click" color="blue" />
</div>
);
};
export default App;
Copy the code
- through
defaultProps
Setting defaults
import React from 'react'; type IProps = { color? :'red' | 'blue' | 'yellow'; text? : string; };const Com: React.FC<IProps> = ({ text, color }) = > {
return <div style={{ color}} >{text}</div>;
};
// Set the default value using defaultProps
Com.defaultProps = {
color: 'red'.text: 'hello'};const App: React.FC = () = > {
return (
<div>
<Com text="hi" />
<Com color="blue" />
</div>
);
};
export default App;
Copy the code
3. Use in Hooks
useState
With useState, TypeScript can automatically infer types:
const App: React.FC = () = > {
const [value, setValue] = useState(' ');
return (
<button
onClick={()= > {
setValue(1); // Error: Argument of type 'number' is not assignable to parameter of type 'SetStateAction<string>'
}}
>
click
</button>
);
};
Copy the code
Sometimes we need to initialize state with a null value, which can be passed using generics:
type User = {
email: string; id: number; error? : string; };const App: React.FC = () = > {
const [user, setUser] = useState<User | null> (null);
return (
<button
onClick={()= > {
setUser({
email: '[email protected]',
id: 1427,
});
}}
>
click
</button>
);
};
Copy the code
useEffect
There is no return value and no need to pass the type.
useLayoutEffect
There is no return value and no need to pass the type.
useReducer
The useReducer accepts two parameters, Reducer and initialState. The reducer type can be inferred only by type constraints on the input state and action of the Reducer function passed in to the useReducer. Associative types are useful when using useReducer or Redux. Look at the following example:
import React, { useReducer } from 'react';
const initialState = { count: 0 };
/ * * joint type, use | link * /
type ACTIONTYPE =
| { type: 'increment'; payload: number }
| { type: 'decrement'; payload: string }
| { type: 'reset'; payload: number };
function reducer(state: typeof initialState, action: ACTIONTYPE) {
switch (action.type) {
case 'increment':
return { count: state.count + action.payload };
case 'decrement':
return { count: state.count - Number(action.payload) };
case 'reset':
return { count: action.payload };
default:
throw new Error();
}
}
const Counter: React.FC = () = > {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={()= > dispatch({ type: 'decrement', payload: '5' })}>-</button>
<button onClick={()= > dispatch({ type: 'increment', payload: 5 })}>+</button>
<button onClick={()= > dispatch({ type: 'reset', payload: 0 })}>reset</button>
</>
);
};
export default Counter;
Copy the code
When ACTIONTYPE is set to the union type, a variable of the union type is assigned to infer a type according to the rules of type inference.
useRef
UseRef can infer the type when passing a non-empty initial value. It can define the type by passing the first generic parameter, constraining the type of ref.current.
import React, { useLayoutEffect } from 'react';
export const App: React.FC = () = > {
const h1Ref = React.useRef<HTMLDivElement | null> (null);
function changeInnerText(el: HTMLDivElement, value: string) {
el.innerText = value;
}
useLayoutEffect(() = > {
if (null! == h1Ref.current) { changeInnerText(h1Ref.current,'hello world'); }} []);return <h1 ref={h1Ref}>My title</h1>;
};
export default App;
Copy the code
More useRef examples:
// <div> reference type
const divRef = React.useRef<HTMLDivElement | null> (null);
// <button> reference type
const buttonRef = React.useRef<HTMLButtonElement | null> (null);
// <br /> reference type
const brRef = React.useRef<HTMLBRElement | null> (null);
// <a> reference type
const linkRef = React.useRef<HTMLLinkElement | null> (null);
Copy the code
useCallback
UseCallback does not need to pass a type. It can infer the type from the return value of the function, and TypeScript will report an error if the type is passed incorrectly. Note, however, that the input parameter to the function needs to define a type, otherwise it is inferred to be any.
const multiplier = 2;
const multiply = useCallback((value: number) = > value * multiplier, [multiplier]);
Copy the code
useMemo
UseMemo does not need to pass in the type and can infer the type from the return value of the function.
const memoizedValue = React.useMemo(() = > {
computeExpensiveValue(a, b)
}, [a, b])
Copy the code
To set the type on useMemo, simply pass the return value data type that you want to record to <>.
useContext
Here’s an example:
import React, { useContext } from 'react';
type IArticle = {
id: number;
title: string;
};
type ArticleState = {
articles: IArticle[];
setArticles: React.Dispatch<React.SetStateAction<IArticle[]>>;
};
const ArticleContext = React.createContext<ArticleState>({ articles: [].setArticles: (a) :void= >{}});const ArticleProvider: React.FC<React.ReactNode> = ({ children }) = > {
const [articles, setArticles] = React.useState<IArticle[] | []>([
{ id: 1.title: 'post 1' },
{ id: 2.title: 'post 2'},]);return <ArticleContext.Provider value={{ articles.setArticles}} >{children}</ArticleContext.Provider>;
};
const ShowArticles: React.FC = () = > {
const { articles } = useContext<ArticleState>(ArticleContext);
return (
<div>
{articles.map((article: IArticle) => (
<p key={article.id}>{article.title}</p>
))}
</div>
);
};
const ChangeArticles: React.FC = () = > {
const { setArticles } = useContext<ArticleState>(ArticleContext);
return <button onClick={()= > setArticles([])}>Empty Acticles</button>;
};
export const Article: React.FC = () = > {
return (
<ArticleProvider>
<h1>My title</h1>
<ShowArticles />
<ChangeArticles />
</ArticleProvider>
);
};
export default Article;
Copy the code
4. Event handling
One of the most common cases is the form’s onChange event:
import React from 'react';
const MyInput: React.FC = () = > {
const [value, setValue] = React.useState(' ');
// The event type is ChangeEvent.
// We pass "HTMLInputElement" to the input
const onChange = (e: React.ChangeEvent<HTMLInputElement>) = > {
setValue(e.target.value);
};
return <input value={value} onChange={onChange} id="input-example" />;
};
export default MyInput;
Copy the code
For a tag and button tag click events, you can also use the union type to duplicate event handling:
import React from 'react';
const MyButton: React.FC = () = > {
const handleClick = (event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>) = > {
console.log(event);
};
return (
<div>
<button onClick={handleClick}>click</button>
<a href="" onClick={handleClick}>
link
</a>
</div>
);
};
export default MyButton;
Copy the code
Common Event Event object types are as follows:
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
5. Add third-party dependency libraries
When we need to add third-party dependencies to our code base, the first thing we do is check to see if the library has a package with the TypeScript @types type definition, by running the following command:
# yarn
$ yarn add @types/<package-name>
# npm
$ npm install @types/<package-name>
Copy the code
For example, if we want to introduce JEST into our project, we can install jEST dependencies as follows:
#yarn
$ yarn add @types/jest
#npm
$ npm install @types/jest
Copy the code
What if I don’t find a package with TypeScript type definition @types, for example loadsh doesn’t have @types/loadsh on NPM?
Install the loadsh dependency using NPM or YARN:
#yarn
$ yarn add loadsh
#npm
$ npm install loadsh
Copy the code
Then find the react-app-env.d.ts file in the SRC folder of your project and add declare to the loadsh dependency, adding the following code:
declare module 'loadsh' {
const classes: any;
export default classes;
}
Copy the code
Then normal introduction can be:
import _ from 'loadsh';
Copy the code
Third, refer to the article
- How React and TypeScript Work Together
- Type-safe state modeling with TypeScript and React Hooks