Series of the introduction
Recently, I prepared to train newcomers. In order to facilitate newcomers to develop React components and write high-quality component codes, I sorted out some documents related to the practices and specifications of React component design based on my own practical experience and shared some chapters. Due to the limited experience, the article may have some mistakes, I hope we point out, mutual exchange.
Because it is too long, it is divided into several articles. The main themes are as follows:
- 01 Type Check
- 02 Component organization
- 03 Style management
- 04 Component thinking
- 05 Status Management
Type checking
Static type checking is increasingly indispensable to today’s front-end projects, especially large ones. It can avoid many types of problems at development time and reduce low-level errors; In addition, the efficiency of coding can be improved by type intelligent prompt. Facilitate self-describing code (types are documents); Easy code refactoring (automatic refactoring with the IDE). The benefits of static type checking are not discussed here, but you can check out the flow.js/typescript… .
Javascript type checkers include Typescript and Flow. I’ve used both. Typescript is more powerful, avoids many pitfalls, has a better ecosystem (such as third-party library type declarations), and VSCode has built-in support. For Flow, even Facebook’s own open source projects (Yarn, Jest) have abandoned it, so it is not recommended to pit. So this article uses Typescript(V3.3) to declare type checking for the React component
It is recommended to learn about Typescript through official documentation. I have also compiled a MindNode for Typescript.
Of course Flow has some features that Typescript doesn’t: typescript-vs-flowType
The React component type checking relies on @types/ React and @types/ React -dom
Direct hands-on use trial
directory
- Series of the introduction
- Type checking
- 1. Function components
- 1 ️ ⃣use
ComponentNameProps
The Props type is named, and exported - 2 ️ ⃣Preferred to use
FC
Type to declare a function component - 3 ️ ⃣Don’t use it directly
export default
Export components. - 4 ️ ⃣Default props declaration
- 5 ️ ⃣Generic function components
- 6 ️ ⃣Subcomponent declaration
- 7 ️ ⃣Forwarding Refs
- 1 ️ ⃣use
- 2. The type of component
- 1 ️ ⃣Inherit Component or PureComponent
- 2 ️ ⃣use
static defaultProps
Define the default props - 3 ️ ⃣Subcomponent declaration
- 4 ️ ⃣The generic
- 3. Advanced components
- 4. Render Props
- 5. Context
- 6. Miscellaneous
- 1 ️ ⃣use
handleEvent
Named event handler. - 2 ️ ⃣The type of built-in event handler
- 3 ️ ⃣Custom components expose event handler types
- 4 ️ ⃣Gets the native element props definition
- 5 ️ ⃣Don’t use PropTypes
- 6 ️ ⃣styled-components
- 7 ️ ⃣Customize module declarations for third-party libraries that do not provide Typescript declaration files
- 8 ️ ⃣Prepare for document generation
- 9 ️ ⃣Enable strict mode
- 1 ️ ⃣use
- Extended information
- 1. Function components
1. Function components
Function components are getting more exposure now that React Hooks are in place. Because function components are just ordinary functions, they are very easy to type declare
1 ️ ⃣useComponentNameProps
The Props type is named, and exported
2 ️ ⃣Preferred to useFC
Type to declare a function component
FC is short for FunctionComponent. This type defines the defaultProps (such as children) and some static properties (such as defaultProps)
import React, { FC } from 'react'; /* / export interface MyComponentProps {className? : string; style? : React.CSSProperties; } export const MyComponent: FC<MyComponentProps> = props => { return <div>hello react</div>; };Copy the code
You can also use normal functions to declare components directly, as we’ll see later that this is more flexible:
export interface MyComponentProps { className? : string; style? : React.CSSProperties; // Manually declare children children? : React.ReactNode; } export function MyComponent(props: MyComponentProps) { return <div>hello react</div>; }Copy the code
3 ️ ⃣Don’t use it directlyexport default
Export components.
Components exported in this way are displayed as Unknown in the React Inspector
export default (props: {}) => {
return <div>hello react</div>;
};
Copy the code
If you must do this, use the named function definition:
export default function Foo(props: {}) {
return <div>xxx</div>;
}
Copy the code
4 ️ ⃣Default props declaration
DefaultProps isn’t exactly supported so far for the above function component that uses FC declarations:
import React, { FC } from 'react'; export interface HelloProps { name: string; } export const Hello: FC<HelloProps> = ({ name }) => <div>Hello {name}! </div>; Hello.defaultProps = { name: 'TJ' }; / / ❌! missing name <Hello />;Copy the code
This is how I like to declare the default props:
export interface HelloProps { name? : string; Export const Hello: FC<HelloProps> = ({name = 'TJ'}) => <div>Hello {name}! </div>;Copy the code
If you have to use defaultProps, you can declare 👇. Typescript can infer and define properties on functions, a feature supported in Typescript 3.1.
import React, { PropsWithChildren } from 'react'; export interface HelloProps { name: string; // PropsWithChildren<P> = P & {// PropsWithChildren? : ReactNode; // } const Hello = ({ name }: PropsWithChildren<HelloProps>) => <div>Hello {name}! </div>; Hello.defaultProps = { name: 'TJ' }; / / ✅ ok! <Hello />;Copy the code
The type of defaultProps is not related to the props of the component itself. This makes it impossible for the defaultProps to have a type constraint, so if necessary, further explicitly declare the type of the defaultProps:
Hello.defaultProps = { name: 'TJ' } as Partial<HelloProps>;
Copy the code
5 ️ ⃣Generic function components
Generics are commonly used in one of the following phenotypes or containerized components, and using FC directly is not sufficient:
import React from 'react';
export interface ListProps<T> {
visible: boolean;
list: T[];
renderItem: (item: T, index: number) = > React.ReactNode;
}
export function List<T> (props: ListProps<T>) {
return <div />;
}
// Test
function Test() {
return (
<List
list={[1.2.3]}
renderItem={i= > {
/* Automatically infer that I is of type number */
}}
/>
);
}
Copy the code
6 ️ ⃣Subcomponent declaration
Using JSX in the form of Parent.Child makes the parent-child relationship more intuitive, acting like a namespace mechanism that avoids naming conflicts. Parent.Child is more elegant than ParentChild. It’s also possible to make your code verbose.
import React, { PropsWithChildren } from 'react'; Export interface LayoutProps {} export interface LayoutHeaderProps {} // Name export interface as ParentChildProps LayoutFooterProps {} export function Layout(props: PropsWithChildren<LayoutProps>) { return <div className="layout">{props.children}</div>; } // The parent component property layout. Header = (props: PropsWithChildren<LayoutHeaderProps>) => { return <div className="header">{props.children}</div>; }; Layout.Footer = (props: PropsWithChildren<LayoutFooterProps>) => { return <div className="footer">{props.children}</div>; }; // Test <Layout> <Layout.Header>header</Layout.Header> <Layout.Footer>footer</Layout.Footer> </Layout>;Copy the code
7 ️ ⃣Forwarding Refs
React.forwardRef added in 16.3 for forwarding refs, HOC and function components.
Function components don’t support refs until 16.8.4. With the help of the forwardRef and useImperativeHandle, function components can expose methods
/***************************** * MyModal.tsx ****************************/ import React, { useState, useImperativeHandle, FC, useRef, useCallback } from 'react'; export interface MyModalProps { title? : React.ReactNode; onOk? : () => void; onCancel? : () => void; } /** * {ComponentName}Methods' form name */ export interface MyModalMethods {show(): void; } export const MyModal = React.forwardRef<MyModalMethods, MyModalProps>((props, ref) => { const [visible, setVisible] = useState(); UseImperativeHandle (ref, () => ({show: () => setVisible(true),})); return <Modal visible={visible}>... </Modal>; }); /******************* * Test.tsx *******************/ const Test: FC < {} > = props = > {/ / const reference modal = useRef < MyModalMethods | null > (null); const confirm = useCallback(() => { if (modal.current) { modal.current.show(); }} []); const handleOk = useCallback(() => {}, []); return ( <div> <button onClick={confirm}>show</button> <MyModal ref={modal} onOk={handleOk} /> </div> ); };Copy the code
2. The type of component
Class-based type checking may be better understood than functions (for example, by developers familiar with traditional object-oriented programming languages).
1 ️ ⃣Inherit Component or PureComponent
import React from 'react'; {ComponentName}Props */ export interface CounterProps {defaultCount: number; // Optional props, no need? */ interface State {count: number; } /** * class extends Props and State */ export class Counter extends React.Component<CounterProps, State> {/** * default parameter */ public static defaultProps = {defaultCount: 0,}; /** * public State = {props: this.props. /** * componentDidMount() {} /** * componentDidMount() {} Public componentWillUnmount() {} public componentDidCatch() {} public componentDidUpdate(prevProps: CounterProps, prevState: Public render() {return (<div> {this.state.count} <button onClick={this.increment}>Increment</button> <button onClick={this.decrement}>Decrement</button> </div> ); } private increment = () => {this.setState(({count}) => ({count: count + 1 })); }; private decrement = () => { this.setState(({ count }) => ({ count: count - 1 })); }; }Copy the code
2 ️ ⃣usestatic defaultProps
Define the default props
Typescript 3.0 starts to support inferences about JSX props using defaultProps. The props defined in defaultProps do not need ‘? ‘Optional operator modifier. Code as above 👆
3 ️ ⃣Subcomponent declaration
Class components can declare child components in the form of static properties
export class Layout extends React.Component<LayoutProps> { public static Header = Header; public static Footer = Footer; public render() { return <div className="layout">{this.props.children}</div>; }}Copy the code
4 ️ ⃣The generic
export class List<T> extends React.Component<ListProps<T>> {
public render() {}
}
Copy the code
3. Advanced components
Before React Hooks came along, higher-order components were an important way to reuse logic in React. Higher-order components are heavier, harder to understand and prone to creating nested hell. Also not friendly to Typescript typing (previously, you’d use Omit to calculate the derived props). React Hooks are recommended for the new project.
A simple higher-order component:
import React, { FC } from 'react';
/** * declare Props */ for injection
export interface ThemeProps {
primary: string;
secondary: string;
}
/** * injects' theme '*/ to the specified component
export function withTheme<P> (Component: React.ComponentType<P & ThemeProps>) {
/** * WithTheme Props */
interface OwnProps {}
/** * Functions of higher-order components, ignoring ThemeProps. These properties are not passed externally */
type WithThemeProps = P & OwnProps;
/** * higher-order components */
const WithTheme = (props: WithThemeProps) = > {
// Assume the theme is fetched from the context
const fakeTheme: ThemeProps = {
primary: 'red',
secondary: 'blue'};return<Component {... fakeTheme} {... props} />; }; WithTheme.displayName =`withTheme${Component.displayName}`;
return WithTheme;
}
// Test
const Foo: FC<{ a: number } & ThemeProps> = props= > <div style={{ color: props.primary }} />;
const FooWithTheme = withTheme(Foo);
(a)= > {
<FooWithTheme a={1} / >; };Copy the code
Let’s refactor:
/** * Extract the generic higher-order component type */
type HOC<InjectedProps, OwnProps = {}> = <P>(
Component: React.ComponentType<P & InjectedProps>,
) => React.ComponentType<P & OwnProps>;
/** * declare Props */ for injection
export interface ThemeProps {
primary: string;
secondary: string;
}
export const withTheme: HOC<ThemeProps> = Component= > props => {
// Assume the theme is fetched from the context
const fakeTheme: ThemeProps = {
primary: 'red',
secondary: 'blue'};return<Component {... fakeTheme} {... props} />; };Copy the code
There are also some pain points with higher-order components:
- Inability to use refs perfectly (this is no longer a pain point)
- Before the React. ForwardRef release, some libraries use innerRef or wrapperRef to forward to the ref of the encapsulated component.
- The type of the ref reference component cannot be inferred and requires an explicit declaration.
- Higher-order component type errors are hard to understand
4. Render Props
React props(including children) is not of restricted type; it can be a function. So you have render props, which is just as common a pattern as higher-order components:
import React from 'react';
export interface ThemeConsumerProps {
children: (theme: Theme) => React.ReactNode;
}
export const ThemeConsumer = (props: ThemeConsumerProps) => {
const fakeTheme = { primary: 'red', secondary: 'blue' };
return props.children(fakeTheme);
};
// Test
<ThemeConsumer>
{({ primary }) => {
return <div style={{ color: primary }} />;
}}
</ThemeConsumer>;
Copy the code
5. Context
Context provides a mechanism for sharing state across components
import React, { FC, useContext } from 'react'; export interface Theme { primary: string; secondary: string; } /** * Declare the Context type and Name it {Name}ContextValue */ export interface ThemeContextValue {theme: theme; onThemeChange: (theme: Theme) => void; } /** * create Context and set the default value, Named after the Context {Name} * / export const ThemeContext = React. CreateContext < ThemeContextValue > ({theme: {primary: 'red', secondary: 'blue', }, onThemeChange: noop, }); /** * Provider, Name it {Name}Provider */ export const ThemeProvider: FC<{theme: theme; onThemeChange: (theme: Theme) => void }> = props => { return ( <ThemeContext.Provider value={{ theme: props.theme, onThemeChange: props.onThemeChange }}> {props.children} </ThemeContext.Provider> ); }; */ export function useTheme() {return useContext(ThemeContext); }Copy the code
6. Miscellaneous
1 ️ ⃣usehandleEvent
Named event handler.
If multiple Event handlers are the same, they are named after handle{Type}{Event}, such as handleNameChange.
export const EventDemo: FC<{}> = props= > {
const handleClick = useCallback<React.MouseEventHandler>(evt= > {
evt.preventDefault();
// ...} []);return <button onClick={handleClick} />;
};
Copy the code
2 ️ ⃣The type of built-in event handler
@types/ React has the following event handler types built in 👇
type EventHandler<E extends SyntheticEvent<any>> = { bivarianceHack(event: E): void} ['bivarianceHack'];
type ReactEventHandler<T = Element> = EventHandler<SyntheticEvent<T>>;
type ClipboardEventHandler<T = Element> = EventHandler<ClipboardEvent<T>>;
type CompositionEventHandler<T = Element> = EventHandler<CompositionEvent<T>>;
type DragEventHandler<T = Element> = EventHandler<DragEvent<T>>;
type FocusEventHandler<T = Element> = EventHandler<FocusEvent<T>>;
type FormEventHandler<T = Element> = EventHandler<FormEvent<T>>;
type ChangeEventHandler<T = Element> = EventHandler<ChangeEvent<T>>;
type KeyboardEventHandler<T = Element> = EventHandler<KeyboardEvent<T>>;
type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>>;
type TouchEventHandler<T = Element> = EventHandler<TouchEvent<T>>;
type PointerEventHandler<T = Element> = EventHandler<PointerEvent<T>>;
type UIEventHandler<T = Element> = EventHandler<UIEvent<T>>;
type WheelEventHandler<T = Element> = EventHandler<WheelEvent<T>>;
type AnimationEventHandler<T = Element> = EventHandler<AnimationEvent<T>>;
type TransitionEventHandler<T = Element> = EventHandler<TransitionEvent<T>>;
Copy the code
Event handler types can be declared succinctly:
import { ChangeEventHandler } from 'react';
export const EventDemo: FC<{}> = props= > {
/** * can specify the type of Target */
const handleChange = useCallback<ChangeEventHandler<HTMLInputElement>>(evt= > {
console.log(evt.target.value); } []);return <input onChange={handleChange} />;
};
Copy the code
3 ️ ⃣Custom components expose event handler types
As with native HTML elements, custom components should expose their own event handler types, especially for more complex event handlers, to avoid the developer manually declaring a type for each event handler parameter
Custom Event Handler types are named after {ComponentName}{Event}Handler. To distinguish it from the native EventHandler type, the EventHandler suffix is not used
import React, { FC, useState } from 'react'; export interface UploadValue { url: string; name: string; size: number; } /** * export type UploadChangeHandler = (value? : UploadValue, file? : File) => void; export interface UploadProps { value? : UploadValue; onChange? : UploadChangeHandler; } export const Upload: FC<UploadProps> = props => { return <div>... </div>; };Copy the code
4 ️ ⃣Gets the native element props definition
There are some scenarios where we want the native elements to extend some props. All native elements extend props from React.HTMLAttributes, and some special elements extend their own attributes, such as InputHTMLAttributes. See the implementation of the React. CreateElement method
import React, { FC } from 'react'; export function fixClass< T extends Element = HTMLDivElement, Attribute extends React.HTMLAttributes<T> = React.HTMLAttributes<T> >(cls: string, type: keyof React.ReactHTML = 'div') { const FixedClassName: FC<Attribute> = props => { return React.createElement(type, { ... props, className: `${cls} ${props.className}` }); }; return FixedClassName; } /** * Test */ const Container = fixClass('card'); const Header = fixClass('card__header', 'header'); const Body = fixClass('card__body', 'main'); const Footer = fixClass('card__body', 'footer'); const Test = () => { return ( <Container> <Header>header</Header> <Body>header</Body> <Footer>footer</Footer> </Container> ); };Copy the code
5 ️ ⃣Don’t use PropTypes
With Typescript, it’s safe to constrain Props and State, there’s no need to introduce React.PropTypes, and it’s weak
6 ️ ⃣styled-components
Styled components is by far the most popular CSS-in-JS library, and Typescript supports generic tag templates in 2.9. This means that the styled- Components created can simply be type constrained
// Dependent on @types/styled- Components
import styled from 'styled-components/macro';
constTitle = styled.h1<{ active? :boolean} >`
color: ${props => (props.active ? 'red' : 'gray')};
`;
// Extend existing components
const NewHeader = styled(Header)<{ customColor: string} >`
color: ${props => props.customColor};
`;
Copy the code
Learn more about Styled components and Typescript
7 ️ ⃣Customize module declarations for third-party libraries that do not provide Typescript declaration files
I usually place a global.d.ts in the project root directory (in the same directory as tsconfig.json). Place the global declaration file for the project
// /global.d.ts
// Custom module declarations
declare module 'awesome-react-component' {
// Declarations of dependencies on other modules
import * as React from 'react';
export const Foo: React.FC<{ a: number; b: string} >. }Copy the code
Learn more how to define a declaration file
8 ️ ⃣Prepare for document generation
There are several react component document generation solutions, such as Docz, Styleguidist, and Storybook. They both use react-Docgen-typescript to parse typescript underneath. For now, it’s a bit of a bummer, and parsing is slow. Regardless of whether it prevents us from commenting the code in its style:
import * as React from 'react'; import { Component } from 'react'; /** * Props Props */ export interface ColumnProps extends React.HTMLAttributes<any> {/** prop1 description */ prop1? : string; /** prop2 description */ prop2: number; /** * prop3 description */ prop3: () => void; /** prop4 description */ prop4: 'option1' | 'option2' | 'option3'; */ export class Column extends Component<ColumnProps, {}> {render() {return <div>Column</div>; }}Copy the code
9 ️ ⃣Enable strict mode
To really use Typescript, always turn on strict mode and avoid any declarations.
Extended information
- piotrwitek/react-redux-typescript-guide
- How does TypeScript write HOC in React perfectly?
- Typescript official documentation
- Typescript-deep-dive
- Typescript mind maps