Pre – work
- Read the TS section in React Doc
- Read the React section of TypeScript Playground
React: Always use namespace import.
import * as React from 'react'; import { useState } from 'react'; Import * as ReactDom from 'react-dom';Copy the code
Import React from ‘React’ is not recommended. 👉 Why?
This introduction is called default export. The reason it’s not recommended is that React exists only as a namespace, and we don’t use React as a value. React 16.13.0 export {XXX,… } in the form of (commit). The reason why default export can still be used is that the React build product is compliant with Commonjs and webPack and other build tools.
Component development
1. Use Function Component declarations as often as possible, i.e. React.FC:
export interface Props {
/** The user's name */
name: string;
/** Should the name be rendered in bold */priority? :boolean
}
const PrintName: React.FC<Props> = (props) = > {
return (
<div>
<p style={{ fontWeight: props.priority ? "bold" : "normal}} ">{props.name}</p>
</div>)}Copy the code
I usually use vscode snippets for quick generation at development time:
"Functional, Folder Name": {
"prefix": "ff"."body": [
"import * as React from 'react';".""."const { useRef, useState, useEffect, useMemo } = React;"."".""."interface ${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/}Props {".""."}".""."const defaultProps: ${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/}Props = {};".""."const ${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/}: React.FC<${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/}Props> = (props: React.PropsWithChildren<${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/}Props> = defaultProps) => {"." const { } = props;".""." return ("."".");"."};.""."export default ${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/};".""]."description": "Generate a functional component template"
},
Copy the code
Hook associated
Install the vscode plugin: React Hooks to quickly write Hooks.
2. UseState: It is recommended to write the full generic when the initial state value is empty, otherwise the type can be inferred automatically.
Cause: Some states with null initial values need to explicitly declare the type:
const [user, setUser] = React.useState<User | null> (null)
Copy the code
Note: you do not need to add undefined to the generic if the initial value is undefined.
3. UseMemo () and useCallback() implicitly infer types and recommend not passing generics
Note: Do not use useCallback too often, as it also adds overhead. Only if:
- Packing in
React.memo()
The component in (shouldComponentUpdate) accepts a callback prop; - When functions are used as dependencies for other hooks
useEffect(... , the callback)
.
4. Add a const assertion manually to a custom hook if it returns an array:
function useLoading() {
const [isLoading, setLoading] = React.useState(false);
const load = (aPromise: Promise<any>) = > {
setLoading(true)
return aPromise.then(() = > setLoading(false));
}
// Actual: [Boolean, typeof load] type
/ / instead of automatic deduction: (Boolean | typeof load) []
return [isLoading, load] as const;
}
Copy the code
Or you can define the return type directly:
export function useLoading() :boolean,
(aPromise: Promise<any>) = >Promise<any>]{
const [isLoading, setState] = React.useState(false)
const load = (aPromise: Promise<any>) = > {
setState(true)
return aPromise.then(() = > setState(false))}return [isLoading, load]
}
Copy the code
other
5. Use default parameter values instead of default properties
interfaceGreetProps { age? :number }
const defaultProps: GreetProps = { age: 21 };
const Greet = (props: GreetProps = defaultProps) = > {
/ *... * /
}
Copy the code
Reason: The Function Component defaultProps will eventually be deprecated and is not recommended. If you still want to use defaultProps, the following is recommended:
interface IProps {
name: string
}
const defaultProps = {
age: 25};// Type definition
type GreetProps = IProps & typeof defaultProps;
const Greet = (props: GreetProps) = > <div></div>
Greet.defaultProps = defaultProps;
/ / use
const TestComponent = (props: React.ComponentProps<typeof Greet>) = > {
return <h1 />
}
const el = <TestComponent name="foo" />
Copy the code
6. You are advised to use Interface to define component props (TS), and type is optional
The difference between type and interface: The type type cannot be edited twice, while the interface can be extended at any time.
7. Use ComponentProps to obtain the component parameter types that are not exported, and use ReturnType to obtain the returned value types
Get component parameter types:
// Get the parameter type
import { Button } from 'library' // But props type is not exported
type ButtonProps = React.ComponentProps<typeof Button> / / get props
type AlertButtonProps = Omit<ButtonProps, 'onClick'> / / remove the onClick
const AlertButton: React.FC<AlertButtonProps> = props= > (
<Button onClick={()= >alert('hello')} {... props} />
)
Copy the code
Get the return value type:
// Get the return value type
function foo() {
return { baz: 1}}type FooReturn = ReturnType<typeof foo> // { baz: number }
Copy the code
8. Use /** */ when commenting on type or interface to get a better indication of the type
/ * ✅ * /
/ * * *@param A note 1 *@param B Note 2 */
type Test = {
a: string;
b: number;
};
const testObj: Test = {
a: '123'.b: 234};Copy the code
Type hints are friendlier when hover to Test:
9. Type specification of component Props TS
type AppProps = {
/** string */
message: string;
/** number */
count: number;
/** boolean */
disabled: boolean;
/** Array of primitive types */
names: string[];
/** String literal */
status: 'waiting' | 'success';
/** object: Lists all attributes of the object */
obj3: {
id: string;
title: string;
};
/** item is an array of objects */
objArr: {
id: string;
title: string; } [];/ * / * * dictionary
dict: Record<string, MyTypeHere>;
/** Any function that will not be called at all */
onSomething: Function;
/** Function with no arguments & return value */
onClick: () = > void;
/** Function that takes arguments */
onChange: (id: number) = > void;
/** functions that carry click events, not e: any */
onClick(e: React.MouseEvent<HTMLButtonElement>): void;
/** Optional attribute */optional? : OptionalType; children: React.ReactNode;// Best, support all types (jsx.element, jsx.element [], string)
renderChildren: (name: string) = >React.ReactNode; style? : React.CSSProperties; onChange? : React.FormEventHandler<HTMLInputElement>;// Form events
};
Copy the code
10. Component event handling
Common Eventl types:
React.SyntheticEvent<T = Element> React.ClipboardEvent<T = Element> React.DragEvent<T = Element> React.FocusEvent<T = Element> React.FormEvent<T = Element> React.ChangeEvent<T = Element> React.KeyboardEvent<T = Element> React.MouseEvent<T = Element> React.TouchEvent<T = Element> React.PointerEvent<T = Element> React.UIEvent<T = Element> React.WheelEvent<T = Element> React.AnimationEvent<T = Element> React.TransitionEvent<T = Element>Copy the code
Define the event callback function:
type changeFn = (e: React.FormEvent<HTMLInputElement>) = > void;
Copy the code
If you don’t care too much about the event type, you can use react. SyntheticEvent directly. If the target form has custom named input that you want to access, you can use type extensions:
const App: React.FC = () = > {
const onSubmit = (e: React.SyntheticEvent) = > {
e.preventDefault();
const target = e.target as typeof e.target & {
password: { value: string; };
}; // Type extension
const password = target.password.value;
};
return (
<form onSubmit={onSubmit}>
<div>
<label>
Password:
<input type="password" name="password" />
</label>
</div>
<div>
<input type="submit" value="Log in" />
</div>
</form>
);
};
Copy the code
11. Use optional Channing whenever possible
12. Try to use React.ComponentProps to reduce the export of unnecessary props
13. Do not use function declarations in type or interface
/ * * ✅ * /
interface ICounter {
start: (value: number) = > string
}
❌ / * * * /
interface ICounter1 {
start(value: number) :string
}
Copy the code
14. When local components are combined with multiple components for inter-component state communication, mobx is not needed if it is not particularly complex, or it is recommended to use it together with useReducer() and useContext().
Store.ts
import * as React from 'react';
export interface State {
state1: boolean;
state2: boolean;
}
export const initState: State = {
state1: false.state2: true};export type Action = 'action1' | 'action2';
export const StoreContext = React.createContext<{
state: State; dispatch: React.Dispatch<Action>; ({} >state: initState, dispatch: value= > { /** noop */}});export const reducer: React.Reducer<State, Action> = (state, action) = > {
// One of the benefits of reducer is that you can get the previous state
switch (action) {
case 'action1':
return { ...state, state1: !state.state1 };
case 'action2':
return { ...state, state2: !state.state2 };
default:
returnstate; }};Copy the code
WithProvider.tsx
import * as React from 'react';
import { StoreContext, reducer, initState } from './store';
const { useReducer } = React;
const WithProvider: React.FC<Record<string, unknown>> = (props: React.PropsWithChildren<Record<string, unknown>>) = > {
// Inject state into child components as the state of the root node
const [state, dispatch] = useReducer(reducer, initState);
const { children } = props;
return <StoreContext.Provider value={{ state.dispatch}} >{children}</StoreContext.Provider>;
};
export default WithProvider;
Copy the code
The parent component:
import * as React from 'react';
import WithProvider from './withProvider';
import Component1 from './components/Component1';
import Component2 from './components/Component2';
const { useRef, useState, useEffect, useMemo } = React;
interface RankProps {}
const defaultProps: RankProps = {};
const Rank: React.FC<RankProps> = (props: React.PropsWithChildren<RankProps> = defaultProps) = > {
const {} = props;
return (
<WithProvider>
<Component1 />
<Component2 />
</WithProvider>
);
};
export default Rank;
Copy the code
The child component simply needs to import StoreContext and useContext() to get state and dispatch
import * as React from 'react';
import { StoreContext } from '.. /.. /store';
const { useContext } = React;
interface Component1Props {}
const defaultProps: Component1Props = {};
const Component1: React.FC<Component1Props> = (props: React.PropsWithChildren<Component1Props> = defaultProps) = > {
const { state, dispatch } = useContext(StoreContext);
const {} = props;
return (
<>
state1: {state.state1 ? 'true' : 'false'}
<button
type="button"
onClick={() : void= > {
dispatch('action1');
}}
>
changeState1 with Action1
</button>
</>
);
};
export default React.memo(Component1); // It is recommended to use the memo if context is available
Copy the code
Store.ts and WithProvider.tsx can be configured to vsCode snippets and used directly when needed.
reference
[1] React + TypeScript practice
[2] Intensive Reading of React Hooks Best Practices
Feel free to discuss in the comments or issues, pointing out irrationalities and adding other best practices
This article was originally published on personal blog: moltemort. Top/post/typesc…