react advanced features

After using react for so long, I didn't know that react had so many advanced features. I had nothing to do during the vacation. I tried some advanced features of react. Here is the trial record.

overview

Characteristic characterization Usage scenarios
Code segmentation Provide asynchronous components for unpacking Use when you need to optimize package size
Context Transfer data across levels Optimizing Multilevel Transfer Pros Problem
PropTypes for type checking You can add a validator to the type of props Hope to expose props type errors early
Error boundary Provides a lifetime without subcomponent errors and returns the specified state in error Hope to provide degraded UI or report errors when rendering errors
Fragments Provides the ability to return multiple elements in a component You want to return multiple elements in a component
Portals Provides the ability to render elements outside of parent elements Toast, Modal, etc.
forwardRef Forwarding incoming ref s Hope to transfer ref s passed from outside to other elements, not to yourself

Code segmentation

Packing a huge single-page application into a huge js can make the first screen load very bad. At this time, code segmentation may be considered, that is, packaging js separately according to module or routing, and loading components asynchronously on demand.

With the help of webpack and some asynchronous component libraries (such as react-loadable You can also implement asynchronous components by yourself) and you can easily do this. For example, as follows:

// router.js
import React from 'react';
import Loadable from 'react-loadable';

const Loading = () => <div>Loading...</div>;

/////////////// Page routing configuration////////////////

const Routers = {
    // home page
    '/': Loadable({
        loader: () => import(/* webpackChunkName: "index" */'./pages/Index.jsx'),
        loading: Loading,
      }),
    // home page
    '/index': Loadable({
        loader: () => import(/* webpackChunkName: "index" */'./pages/Index.jsx'),
        loading: Loading,
    }),
    '/404': Loadable({
        loader: () => import(/* webpackChunkName: "404" */'./pages/404/index'),
        loading: Loading, 
    })
}

export default Routers;

// App.js
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";

import Routers from './router';

class App extends Component {
  componentDidMount() {

  }
  render() {
    return (
      <Router>
          <Switch>
            <Route path="/" exact component={Routers["/"]} />
            <Route path="/index" exact component={Routers["/index"]} />
            <Route component={Routers['/404']} />
          </Switch>
      </Router>
    );
  }
}

export default App;

We use Loadable directly to create asynchronous components. When appropriate, webpack will help me do code segmentation. Loadable can help us maintain the status of asynchronous components and support the definition of components under loading. See the full version of demo above. web-test.

Actually, react already provides native support for asynchronous components, which are roughly the same as Loadable, but look more elegant.

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';

const Home = lazy(() => import(/* webpackChunkName: "home" */'./pages/Home'));
const About = lazy(() => import(/* webpackChunkName: "about" */'./pages/About'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/about" component={About} />
      </Switch>
    </Suspense>
  </Router>
);

export default App;

Here we use the React.lazy method to create asynchronous components. Similar to Loadable, we also use import method. Web pack will help us handle this import. The difference is that he does not support defining load. Customization of loading can use Suspense component. Customized loading component can be created in its fallback. The complete version of this demo can be referred to. react-demo.

Context

The first contact with Context was discovered by looking at the redux source code. Context feature is one of the core of redux implementation. Context can make the transmission of deep props simple and elegant, no longer need to transfer step by step.

Assuming the following components, component D needs to take data from component A. It may need to pass from A to B through props, from B to C, and from C to D through props. It is very troublesome.

<A>
  <B>
    <C>
      <D>
      </D>
    </C>
  </B>
</A>

Take a look at how this can be achieved through the Context feature.

// MyContext.js
import React from 'react';

const MyContext = React.createContext("I am from A Default value");
export default MyContext;

// A.js
import React from 'react';
import B from './B';
import MyContext from './MyContext';
export default class A extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <div>
                <MyContext.Provider value={'I am from A Data'}>
                    <B />
                </MyContext.Provider>
            </div>
        )
    }
}

// B.js
import React from 'react';
import C from './C';
class B extends React.Component {
    render() {
        return (
            <div>
                <h3>I am B assembly</h3>
                <C />
            </div>
        );
    }
}
export default B;

// C.js
import React from 'react';
import MyContext from './MyContext';
import D from './D';

function C() {
    return (
        <MyContext.Consumer>
            {
                (value) => (
                    <div>
                        <h3>I am C assembly</h3>
                        <div>I am from A Data: {value}</div>
                        <D />
                    </div>
                )
            }
        </MyContext.Consumer>
    )
}

export default C;

// D.js
import React from 'react';
import MyContext from './MyContext';

class D extends React.Component {
    render() {
        let context = this.context;
        return (
            <div>
                <h3>I am D assembly</h3>
                <div>I got it. A Data passed in</div>
                {context}
            </div>
        );
    }
}

D.contextType = MyContext;

export default D;

You can see that the data in A is obtained without any props from C and D components. The full version of this demo is available for reference. react-demo This example may seem to directly put the shared variables into the global, but when placed in the global, setState cannot be re-rendered, and the data in the Context can be re-rendered through setState.

From the Demo above, the use of Context is very simple

  1. Create Context with React.createContext()
  2. Use Context.Provider to pass values in the parent component
  3. Consumption in subcomponents

    • For class components that can consume contextType as a static variable of life, see D
    • For functions that are components, you can consume them with Context.Consumer, see C

## Type checking using PropTypes

A called component can verify the props parameter type by PropTypes, and notify the caller of the type problem early. It can be easily implemented by specifying the static property propTypes to the component and combining with the prop-types library. Pro-types need to be installed separately.

Here are some validators provided by prop types from react Chinese document

import PropTypes from 'prop-types';

MyComponent.propTypes = {
  // You can declare properties as JS native types by default
  // These attributes are optional.
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // Any renderable element (including numbers, strings, elements or arrays)
  // (or Fragment) also includes these types.
  optionalNode: PropTypes.node,

  // A React element.
  optionalElement: PropTypes.element,

  // A React element type (MyComponent).
  optionalElementType: PropTypes.elementType,

  // You can also declare prop as an instance of a class, using
  // The instance of operator of JS.
  optionalMessage: PropTypes.instanceOf(Message),

  // You can make your prop only a specific value, specifying it as
  // Enumeration type.
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),

  // An object can be any of several types
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // You can specify that an array consists of elements of a certain type
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // You can specify that an object consists of a certain type of value
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // You can specify that an object consists of a specific type value
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),
  
  // An object with warnings on extra properties
  optionalObjectWithStrictShape: PropTypes.exact({
    name: PropTypes.string,
    quantity: PropTypes.number
  }),   

  // You can add `isRequired'after any PropTypes attribute to ensure that
  // When this prop is not provided, a warning message is printed.
  requiredFunc: PropTypes.func.isRequired,

  // Any type of data
  requiredAny: PropTypes.any.isRequired,

  // You can specify a custom validator. It should return an Error object when validation fails.
  // Do not use `console.warn'or throw exceptions, because this does not work in `onOfType'.
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },

  // You can also provide a custom `arrayOf'or `objectOf' validator.
  // It should return an Error object when validation fails.
  // The validator validates each value in an array or object. The first two parameters of the validator
  // The first is the array or the object itself.
  // The second is their current key.
  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  })
};

You can also specify default values for props

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

// Specify the default value of props:
Greeting.defaultProps = {
  name: 'Stranger'
};

// Render "Hello, Stranger":
ReactDOM.render(
  <Greeting />,
  document.getElementById('example')
);

Check and default values can also be written like this

class Greeting extends React.Component {
  static defaultProps = {
    name: 'stranger'
  }
  static propTypes = {
    name: PropTypes.string,
  }
  render() {
    return (
      <div>Hello, {this.props.name}</div>
    )
  }
}

Error boundary

Error boundary is a React component that captures and prints JavaScript errors that occur anywhere in its sub-component tree, and it renders the standby UI instead of the crashed sub-component tree. Error boundaries capture errors during rendering, lifecycle methods, and constructors of the entire component tree.

When a sub-component throws an error, the next two lifecycles are triggered, where errors can be handled, demoted UI s displayed, and errors reported to the server.

The core life cycle of the error boundary component is as follows

static getDerivedStateFromError()
componentDidCatch()

Here's a little demo

// index.js

import React from 'react';
import ErrorComponent from './ErrorComponent';

export default class Home extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            hasError: false,
        }
    }

    static getDerivedStateFromError() {
        console.log('getDerivedStateFromError');
        return { hasError: true };
    }
    componentDidCatch (error, info) {
        console.log('componentDidCatch');
        console.log({
            error,
            info,
        })
    }
    render() {
        if (this.state.hasError) {
            return <div>Something went wrong.</div>
        }
        return (
            <div>
                <h3>Error Boundary Testing</h3>
                <ErrorComponent />
            </div>
        )
    }
}

// ErrorComponent.js
import React from 'react';

export default class Home extends React.Component {
    state = {
        showError: false,
    }
    componentDidMount() {
    }
    click = () => {
        this.setState({
            showError: true,
        })
    }
    render() {
        if (this.state.showError) {
            throw new Error("Throw wrong");
        }
        return (
            <div onClick={this.click}>I am the component that generates errors</div>
        )
    }
}

We can get error information in componentDidCatch(error, info), error message, error stack, component stack info.componentStack, which can be displayed to users or reported to the server. We can return state in getDerived State FromError to render degraded components.

Fragments

Fragments solves the problem that a component cannot return multiple elements. Without Fragments, a component cannot return multiple elements. So we often use a div package. As a result, an extra dom node is added, and even illegal dom is generated, such as the following.

// Module 1
function Columns() {
    return (
        <div>
            <td>First column</td>
            <td>The second column</td>
        </div>
    )
}
// Module 2
function Table() {
  return (
    <table>
      <tr>
        <Columns/>
      </tr>
    </table>
  )
}

Because it is impossible to return multiple elements, div is used to wrap two TDS in the Columns component and then in the Table component, resulting in the incorrect structure of td in the tr. Using the Fragments feature can easily solve this problem. As follows, just wrap with <React.Fragment> and you can also write as <something</>.

function Columns() {
    return (
        <React.Fragment>
            <td>First column</td>
            <td>The second column</td>
        </React.Fragment>
    )
}

Portals

Portal provides a solution for rendering child nodes to DOM nodes that exist outside the parent component. A typical use scenario for portal is when the parent component has an overflow: hidden or z-index style, but you need the child component to be able to visually "jump" out of its container. For example, dialog boxes, suspension cards and prompt boxes.

Here is a toast component demo, full version reference react-demo

// Toast.js
import React from 'react';
import ReactDOM from 'react-dom';
import './Toast.css';

export default class Toast extends React.Component {
  constructor(props) {
    super(props);
    this.el = document.querySelector('body');
  }

  render() {
    return ReactDOM.createPortal(
      (
          <div className="toast">
              <div className="toast-inner">
                {this.props.text}
              </div>
          </div>
      ),
      this.el,
    );
  }
}
// PortalTest.js
import React from 'react';
import Toast from './Toast';
export default class PortalTest extends React.Component {
    render() {
        return (
            <div>
                <h1>PortalTest</h1>
                <Toast text="toast Tips"/>
            </div>
        )
    }
}

The result is shown in Fig.

You can see that the Toast component is not in its parent element, but in the body we expect. So whether the parent component writes overflow:hidden, or anything else, it will not affect the toast.

forwardRef

Forward Ref is a way of transferring ref to subcomponents.

Forward Ref has two main usage scenarios

  • I want to encapsulate some of the basic components, but I want the methods of the instances of the basic components to be invoked.
  • In higher-order components, you want ref to point to the wrapped component rather than the outer component
  1. About the first scenario

Before doing ReactNative, there was a FlatList component that wanted to encapsulate him, but also wanted the caller to use ref or FlatList instances to call the above method conveniently. Then forward Ref could be used. The following is an example of input, which we hope to encapsulate, but let the caller still get the focus of the dom call through Ref.

import React from 'react';

const LabelInput =
    React.forwardRef((props, ref) => {
        return <div>
            <label>{props.label}</label>
            <input ref={ref} className="input" style={{ border: '1px solid red' }} />
        </div>
    })

export default class Home extends React.Component {
    constructor(props) {
        super(props);
        this.ref = React.createRef();
    }

    focus = () => {
        try {
            this.ref.current.focus();
        } catch (e) {
            console.log(e);
        }
    }
    render() {
        return (
            <div>
                <h1>test forwardRef</h1>
                <LabelInput ref={this.ref} label="Cell-phone number"/>
                <button onClick={this.focus}>click input Focus can be obtained</button>
            </div>
        )
    }
}

In the Label Input component, ref is transferred to input so that the outside caller can drop the focus method directly. If no forwarding is done, ref will point to div. Finding the input in the Label Input component is more difficult, and the encapsulation of the component is destroyed.

  1. About the second scenario
import React from 'react';

function logProps(Component) {
    class LogProps extends React.Component {
        componentDidUpdate(prevProps) {
            console.log('old props:', prevProps);
            console.log('new props:', this.props);
        }

        render() {
            const { forwardedRef, ...rest } = this.props;

            // Define the custom prop attribute "forward Ref" as ref
            return <Component ref={forwardedRef} {...rest} />;
        }
    }

    // Notice the second parameter "ref" of the React.forwardRef callback.
    // We can pass it to LogProps as a regular prop attribute, such as "forward Ref"
    // It can then be mounted on a subcomponent wrapped in LogPros.
    return React.forwardRef((props, ref) => {
        return <LogProps {...props} forwardedRef={ref} />;
    });
}

class InnerComp extends React.Component {
    render() {
        return <div id="InnerComp">
            //Wrapped component - text={this.props.text}
        </div>
    }
}

const Comp = logProps(InnerComp);

export default class Home extends React.Component {
    constructor(props) {
        super(props);
        this.ref = React.createRef();
    }
    click = () => {
        console.log(this.ref.current);
    }
    render() {
        return (
            <div>
                <h1>test forwardRef</h1>
                <Comp ref={this.ref} text="test" />
                <button onClick={this.click}>Click Print ref</button>
            </div>
        )
    }
}

Click here to print the InnerComp component, and print the LogProps component if forward Ref is removed. It is clear that forward Ref can successfully deliver ref to the wrapped component.

Note that function components can not be ref, only class components can. Test found.

Tags: Javascript React Fragment Attribute Webpack

Posted on Sun, 06 Oct 2019 02:53:17 -0700 by PURU