React+Typescript best practices
preface
By the way, TypeScript is a typed superset of JavaScript that can be compiled into pure JavaScript. For example, create an index.ts file, create a function, CD it to that file, execute TSC index.ts, and compile to js. That is, of course, if typescript is installed globally. There are plenty of reasons on the web for typescript’s benefits that I won’t go over here, but from my development experience:
-
Typescript is a statically typed language that exposes many unintended problems during development, resulting in fewer bugs and less abusive testing;
-
Also, Typescript projects are highly maintainable because many types are defined during development;
-
Most importantly, Typescript has a great ability to infer types and interfaces, so you can hit the back of the car as long as you’ve defined the types and interfaces before you develop them. Of course WHAT I’m declaring here is the process of developing and writing Typescript, because I’ve seen a lot of Typescript written for Typescript’s sake. They implement features first and then fix TS.
Let’s take a look at Typescript from a real project.
Start the
-
If your project originally runs JS+React and plans to migrate to Typescript, I highly recommend that you do so by reading my other article on the AnDesignPro project migrating TS and formatting it with Eslint+Prettier code
-
If you’re building a project from scratch, execute the following command to generate a React+Typescript project. The formatting tool for esLint +prettier code is then configured, as shown in the previous article.
create-react-app my-app –template typescript
Best practices
Component definition
Component definitions can be divided into function components and class components.
- Functional components, by far the most popular way to define components, are written in two ways.
import React from 'react';
// function declaration
function Heading({}:IProps) :React.ReactNode {
return <h1>My Website Heading</h1>;
}
// Function extension
const OtherHeading: React.FC<IProps> = () = > <h1>My Website Heading</h1>;
Copy the code
The function returns a react. ReactNode type.
The second way to write it is to use a function expression that returns a function instead of a value or expression, so specify that the function returns a value of type react. FC.
- Class components
interface StateProps { }
interface IProps { }
class Page extends React.Component<IProps.StateProps> {
constructor(pops){}componentDidMount(){}render() {
return (
<div>
</div>); }}Copy the code
Props
In TypeScript, we can use the interface or type keyword to declare props as generic.
- First, the function component is written
import React from 'react';
import { connect } from "dva";
import { ThunkDispatch } from "redux-thunk";
import { AnyAction } from "redux";
// Global State type for the project
import { State } from '@/types';
// Derive the type extracted from redux from mapStateToProps, provided that the type has been declared in state
type MapStateToProps = Readonly<ReturnType<typeof mapStateToProps>>
// Derive the type of asynchronous methods from mapDispatchToProps
type MapDispatchToProps = Readonly<ReturnType<typeof mapDispatchToProps>>
// Deduce from the default the type that the parent passes to the child
type DefaultProps = Readonly<typeof defaultProps>
// make up IProps
type IProps =DefaultProps & MapStateToProps & MapDispatchToProps
const defaultProps = {
name: 'FrontDream'.age: 18
}
const Content = ({
//Deconstruct your props name, age}: IProps) = > {
return (
<div>
</div>)}const mapStateToProps = (state: State) = > {
return {
items: state.Info.items,
}
}
const mapDispatchToProps = (dispatch: ThunkDispatch<any.any, AnyAction>) = > ({
fetchData: () = > {
dispatch({
type: "receiptInfo/fetch",})}})// Set component defaults
Content.defaultProps = defaultProps
export default connect(
mapStateToProps,
mapDispatchToProps
)(Content)
Copy the code
- Then introduceClass components 的
IProps
import React from 'react';
import { connect } from "dva";
import { ThunkDispatch } from "redux-thunk";
import { AnyAction } from "redux";
// Global State type for the project
import { State } from '@/types';
// The default value passed by the parent component to that component
const defaultProps = {
name: 'FrontDream'.age: 18
}
const initState = {
content: ' '
}
// Derive the type extracted from redux from mapStateToProps, provided that the type has been declared in state
type MapStateToProps = Readonly<ReturnType<typeof mapStateToProps>>
// Derive the type of asynchronous methods from mapDispatchToProps
type MapDispatchToProps = Readonly<ReturnType<typeof mapDispatchToProps>>
// Deduce from the default the type that the parent passes to the child
type DefaultProps = Readonly<typeof defaultProps>
// make up IProps
type IProps = DefaultProps & MapStateToProps & MapDispatchToProps
// Deduce the component's state type from initState
type StateProps = Readonly<typeof initState>
class Content extends React.Component<IProps.StateProps> {
static readonly defaultProps: DefaultProps = defaultProps
readonly state: StateProps = initState
componentDidMount(){}render() {
return (
<div>
</div>); }}const mapStateToProps = (state: State) = > {
return {
items: state.Info.items,
}
}
const mapDispatchToProps = (dispatch: ThunkDispatch<any.any, AnyAction>) = > ({
fetchData: () = > {
dispatch({
type: "receiptInfo/fetch",})}})export default connect(
mapStateToProps,
mapDispatchToProps
)(Content)
Copy the code
The IProps can be divided into several subsets, which can be classified into the following four categories based on actual application scenarios:
DefaultProps
, a property passed directly from the parent component to the current component;
type DefaultProps = Readonly<typeof defaultProps> // Deduce from the default the type that the parent passes to the child
Copy the code
MapStateToProps
Through theconnect
到redux
In thestore
Acquired properties;
type MapStateToProps = Readonly<ReturnType<typeof mapStateToProps>> // Derive the type extracted from redux from mapStateToProps, provided that the type has been declared in state
Copy the code
MapDispatchToProps
Through theconnect
从mapDispatchToProps
Get asynchronous function;
type MapDispatchToProps = Readonly<ReturnType<typeof mapDispatchToProps>> // Derive the type of asynchronous methods from mapDispatchToProps
Copy the code
RouteProps
Through thereact-router-dom
In theroute
Attributes of route delivery
Multiple props can be connected using an ampersand, as in the example above.
It is important to note that the parameters passed by route are not taken into account in the above parameters. It is a bit more complicated to consider when you need to consider them.
import React from 'react';
import { connect } from "dva";
import { ThunkDispatch } from "redux-thunk";
import { AnyAction, compose } from "redux";
import {withRouter, WithRouterProps} from 'react-router'
// Global State type for the project
import { State } from '@/types';
// The default value passed by the parent component to that component
const defaultProps = {
name: 'FrontDream'.age: 18
}
const initState = {
content: ' '
}
// Derive the type extracted from redux from mapStateToProps, provided that the type has been declared in state
type MapStateToProps = Readonly<ReturnType<typeof mapStateToProps>>
// Derive the type of asynchronous methods from mapDispatchToProps
type MapDispatchToProps = Readonly<ReturnType<typeof mapDispatchToProps>>
// Deduce from the default the type that the parent passes to the child
type DefaultProps = Readonly<typeof defaultProps>
// make up IProps
type IProps = DefaultProps & MapStateToProps & MapDispatchToProps & WithRouterProps
// Deduce the component's state type from initState
type StateProps = Readonly<typeof initState>
class Content extends React.Component<IProps.StateProps> {
static readonly defaultProps: DefaultProps = defaultProps
readonly state: StateProps = initState
componentDidMount(){}render() {
return (
<div>
</div>); }}const mapStateToProps = (state: State) = > {
return {
items: state.Info.items,
}
}
const mapDispatchToProps = (dispatch: ThunkDispatch<any.any, AnyAction>) = > ({
fetchData: () = > {
dispatch({
type: "receiptInfo/fetch",})}})export default compose<React.ComponentClass<DefaultProps>>(
withRouter,
connect(
mapStateToProps,
mapDispatchToProps
)(Content)
)
Copy the code
Compare that carefully.
Now, a lot of people are wondering why this isn’t like the way I usually want to write things, like this:
// Examples copied from other people's best practices, I wonder, can such practices be called best practices?
typeProps = { color? :string; name? :string
children: React.ReactNode;
onClick: () => void;
}
Copy the code
I dare say this is called dumb Typescript, not smart Typescript! . Why do you say that? Let me tell you what the problem is.
- This is written without setting the component
defaultProps
And there is no setting initialinitstate
. The most important thing in a program is fault tolerance. When an error occurs, the system can not crash, and there is enough robustness. This is a good system, to the componentsprops
andstate
Setting the default and initial values improves the robustness and fault tolerance of the system. - Following the foolproof notation above, when the system needs to add new features, or new requirements, new ones are added
props
orstate
There are two things that need to be modified, one isstate
orprops
, one isstate
orprops
The correspondinginterface
. In other words, I want to change one feature, but I need to change two things. On the other hand, the derivation aboveTS
Write it, passReturnType
andtypeof
Combined with the derivation of the type, you can only write in one place, and the type is more accurate!
What do you think? Go for it?
Get the props done, and the rest of the development is done.
Use in Hooks
useState
-
String type
With useState, TypeScript can automatically infer the type, so just give an empty string
const [name, setName] = useState(' ');
Copy the code
-
object
You need to initialize state with a null value, which can be passed using generics.
type User = {
name: string;
age: number;
};
const [user, setUser] = useState<User | null> (null);
/ / or
const [user, setUser] = useState({} as User);
Copy the code
- An array of
type User = {
name: string;
age: number;
};
const [user, setUser] = useState<Array<User>>([]);
/ / or
const [user, setUser] = useState([] as User[]);
Copy the code
2.useEffect
There is no return value and no need to pass the type.
3.useLayoutEffect
There is no return value and no need to pass the type.
4.useRef
When useRef passes a non-empty initial value, it can infer the type, passing the first generic parameter to define the type of ref.current. The following is an example:
// Store div dom
const divRef = React.useRef<HTMLDivElement | null> (null);
// Store button DOM
const buttonRef = React.useRef<HTMLButtonElement | null> (null);
// Store the BR DOM
const brRef = React.useRef<HTMLBRElement | null> (null);
// Store a DOM
const linkRef = React.useRef<HTMLLinkElement | null> (null);
Copy the code
Other tags are similar
-
useCallback
There is no need to pass a type, but note that the function’s input parameter needs to define a type, otherwise it is inferred to be any.
const part = 2;
const res = useCallback((value: number) = > value * part, [part]);
Copy the code
6.useMemo
UseMemo does not need to pass in the type and can infer the type from the return value of the function.
const res = React.useMemo(() = > {
complexComputed(a, b)
}, [a, b])
Copy the code
7.useContext
CreateContext defines the types that need to be passed down through generics. Take ali’s AntDesign form as an example:
import React from 'react';
import { WrappedFormUtils } from 'antd/es/form/Form';
export interface Cubicle extends Partial<any> { }
export interfaceIFormContext { form? : WrappedFormUtils;type? :string; changeCubicles? (data: Cubicle[]):void;
}
const FormContext = React.createContext<IFormContext>({});
Copy the code
When used, it can be directly:
const { form, type } = useContext(FormContext);
Copy the code
8.userReducer
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. When using useReducer or Redux, you need to use the combined type.
Take the following example for reference
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
The event processing
- Form events
onChange
The event
import React from 'react';
const Input: React.FC = () = > {
const [value, setValue] = React.useState(' ');
const onChange = (e: React.ChangeEvent<HTMLInputElement>) = > {
setValue(e.target.value);
};
return <input value={value} onChange={onChange} />;
};
export default Input;
Copy the code
Forms are often used to collect information about internal state. They are primarily used for login and registration pages, so submitted information can be collected through forms and sent to the server for processing.
React.FormEvent Is usually used for element event types:
class App extends React.Component<> {
clickHandler = (e: React.FormEvent<HTMLButtonElement>) = > {
// ...
}
changeHandler = (e: React.FormEvent<HTMLInputElement>) = > {
// ...
}
render() {
return (
<div>
<button onClick={this.clickHandler}>Click</button>
<input type="text" onChange={this.changeHandler} />
</div>)}}Copy the code
Both clickHandler and changeHandler event arguments e have type react. FormEvent.
We can omit the arguments to the react. FormEvent input handler and use the return value of the input handler instead. This is done by using: React.ChangeEventHandler:
class App extends React.Component<> {
clickHandler: React.FormEvent<HTMLButtonElement> = (e) = > {
// ...
}
changeHandler: React.FormEvent<HTMLInputElement> = (e) = > {
// ...
}
render() {
return (
<div>
<button onClick={this.clickHandler}>Click</button>
<input type="text" onChange={this.changeHandler} />
</div>)}}Copy the code
a
andbutton
Click event of
import React from 'react';
const Button: React.FC = () = > {
const handleClick = (event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>) = >{};return (
<div>
<button onClick={handleClick}>click</button>
<a href="" onClick={handleClick}>
link
</a>
</div>
);
};
export default Button;
Copy the code
3.TextArea
This refers to the React.ChangeEvent of the text area element, which is an instance of HTMLTextAreaElement.
The generic T is HTMLTextAreaElement. Events that the React. ChangeEvent.
4.Select
Select the same as textarea, but the element type is HTMLSelectElement and the event is React.ChangeEvent.
Form
Similarly, the type is HTMLFormElement and the event is react.changeEvent.
Video, Audio
Similarly, the type is HTMLVideoElement and HTMLAudioElement, and the event is react.ChangeEvent.
7.React.SyntheticEvent
This type is when you don’t care about type:
class App extends React.Component<> {
submitHandler = (e: React.SyntheticEvent) = > {
// ...
}
render() {
return (
<form onSubmit={this.submitHandler}>.</form>)}}Copy the code
- Other events
DragEvent<T = Element> DrageEvent <T = Element> Change KeyboardEvent<T = Element> keyboard event object MouseEvent<T = Element> MouseEvent object TouchEvent<T = Element> TouchEvent object WheelEvent<T = Element> WheelEvent object AnimationEvent<T = Element> AnimationEvent object TransitionEvent<T = Element> TransitionEvent objectCopy the code
Axios request
Axios requests are made primarily by defining a global response type and leaving generics to allow users to define their own types for each interface, as you can see in the following example:
// Global background return type
interface ResponseData<T = any> {
code: number
result: T
message: string
}
// Each interface has its own return type
interface User {
name: string
age: number
}
function getUser<T> () {
return axios<ResponseData<T>>('/extend/user')
.then(res= > res.data)
.catch(err= > console.error(err))
}
async function test() {
const user = await getUser<User>()
if (user) {
console.log(user.result.name)
}
}
test()
Copy the code
Of course, if your asynchronous request tool is not Axios then it can do the same thing and also define the global response type and the response type of the business interface and pass it in through generics.
❤️ Love triple punch
1. Please click “Watching” to support me when you see here. Your “watching” is the motivation for my creation.
2. Pay attention to the public number front-end dreamers, “learn front-end together”!
3. Add wechat [QDW1370336125] to pull you into the technical exchange group to learn together.