preface

Recently I took a look at the new documentation for Update 16.3 and found that officials have quietly changed a lot of things. Of course, the one I’m most interested in is the new Context API. So I wrote this article to summarize and share. Other changes may be mentioned in this article.

This article can be found on my Github, please mark this address for reprinting.

What is the Context API

Context API is a cross-node data access method provided by React. React is known to be a one-way data flow, and the Props in Vue borrows from this idea.

But a lot of the time, this one-way data flow becomes unfriendly. We often need to get some data from higher-level nodes, and with traditional PROP passing data, each layer needs to be passed down manually. For higher-level components, this approach is annoying and greatly reduces productivity.

So React uses the Context API. The Context API has been around for a long time, but the old Context API has many problems and is not particularly convenient to use. It is not recommended to use the old version of the Context API. So many developers turn to state management tools like Redux.

Influenced by Redux, React introduced a new Context API in version 16.3.0.

Something you need to know in advance

  1. As we all know, JavaScript has not had a module system for a long time. Nodejs uses require as a remedy. After ECMAScript6, a new import syntax standard was introduced. The import syntax standard has one particularly important difference (as opposed to require) : import imports data by reference. This means that multiple files import the same data, not copies of the import, but references to the import.

  2. The [email protected] declaration file (d.ts) doesn’t appear to have been updated, which means that if you’re using Typescript now, an error may be reported.

  3. React now recommends the use of render props, which provides a new way to reuse code and transfer code for component rendering. In fact, it is essential to control component rendering by passing HOC functions through props.

  4. You may have heard that the Context API is used to replace Redux, but that’s not the case. Redux and the Context API solve different problems, perhaps because they are used in a slightly different way.

  5. React16.3 has several new features, the main change is the Context, and is abolished several life cycle, such as ComponentWillReceiveProps (to tell the truth, in the actual project, the life cycle can use ComponentWillUpdate to replace)

  6. Refs in Act 16.3 no longer recommends passing a function directly, but uses the new React. CreateRef instead. Of course, the previous method is still applicable, after all, for compatibility.

Begin to use

React.createContext

CreateContext is used to create a Context that takes a parameter that is passed as the default value of the Context. Note that if the argument you pass is an Object, then when you change the Context, object. is is called internally to compare the objects for equality. This can cause some performance problems. Of course, this doesn’t matter, because in most cases, this performance penalty is negligible.

Let’s take a look at this example, which is a Context that provides a topic (Light/Dark) type.

// context.js
import * as React from 'react';
// The default theme is Light
export const { Provider, Consumer } = React.createContext("Light");

Copy the code

Next we just need to import the required files

Provider

The Provider is the root component of all components that need to use the Context. It takes a value as props, which is the value that the Context is passing, and it’s going to modify the default value that you set when you create the Context.

import { Provider } from './context';
import * as React from 'react';
import { render } from 'react-dom';
import App from './app';


const root = (
    <Provider value='Dark'>
        <App />
    </Provider>
);

render(root, document.getElementById('root'));


Copy the code

Consumer

The Consumer stands for Consumer, which accepts a render props as the only child. It’s a function that takes data from the Context as an argument and needs to return a component.

// app.jsx

import { Consumer } from './context';
import * as React from 'react';

export default class App extends React.Component {
    render() {
        return (
            <Consumer>
                {
                    theme => <div>Now, the theme is { theme }</div>
                }
            </Consumer>)}}Copy the code

Some caveats

Multilayer nested

Context To ensure fast rerendering, React needs to ensure that each Consumer is an independent node.

const ThemeContext = React.createContext('light');
const UserContext = React.createContext();

function Toolbar(props) {
  return( <ThemeContext.Consumer> {theme => ( <UserContext.Consumer> {user => ( <ProfilePage user={user} theme={theme} /> )} </UserContext.Consumer> )} </ThemeContext.Consumer> ); } class App extends React.Component { render() { const {signedInUser, theme} = this.props; return ( <ThemeContext.Provider value={theme}> <UserContext.Provider value={signedInUser}> <Toolbar /> </UserContext.Provider> </ThemeContext.Provider> ); }}Copy the code

As the layers get more complex, it becomes annoying. Therefore, it is recommended that after more than two layers, it might be a good idea to create your own Render Prop. In practical engineering, multi-layer nesting is not really recommended. It is more appropriate to provide a Provier and Consumer pair, passing the corresponding instance of the state management tool.

Used in the life cycle

In the previous Context API, the parameters of a Context were exposed during some declaration cycles to make it easier for developers to access. The new API does not pass this parameter; it is more recommended to pass the Context value directly to the component via props. Specifically, as in the official example below.

class Button extends React.Component {
  componentDidMount() {
    // ThemeContext value is this.props.theme
  }

  componentDidUpdate(prevProps, prevState) {
    // Previous ThemeContext value is prevProps.theme
    // New ThemeContext value is this.props.theme
  }

  render() {
    const {theme, children} = this.props;
    return (
      <button className={theme ? 'dark' : 'light'} >
        {children}
      </button>); }}export defaultprops => ( <ThemeContext.Consumer> {theme => <Button {... props} theme={theme} />} </ThemeContext.Consumer> );Copy the code

Unlike before, which can be accessed directly through this.context, the new version of context can only be accessed inside the Render method. Because the Context is only exposed inside the Consumer Render Prop. Personally, this is a weakness of this version of the API. So we have to use this compromise and wrap a function component inside the props. Comparatively speaking, or a bit of trouble. Having a function component in the component tree is also a disadvantage.

Consumer packaging

You need to manually write Consumer and Redner Prop each time the value of a Context is used by multiple components. This is annoying, and programmers are lazy (at least I am), so use React HOC to simplify the process.

const ThemeContext = React.createContext('light');

function ThemedButton(props) {
  return( <ThemeContext.Consumer> {theme => <button className={theme} {... props} />} </ThemeContext.Consumer> ); }Copy the code

Now, when you need to use Context, you don’t have to write Consumer, okay

export default props => (
    ThemeButton(props)
);
Copy the code

Forwarding refs

After you wrap a Consumer, you might want to use ref to get the instance of the root component or the corresponding DOM in Consumer. If you use refs directly on the Consumer, you won’t get the results you want. Therefore, in Act16.3, a completely new technique (not sure if it was introduced in 16.3) was used, called forwarding Refs. Not just in Context, in fact, you can use forward refs any time you want to pass a ref to a child within a component.

React.forwardref ((props, ref) => react.reactelement)

class FancyButton extends React.Component {}

// Use context to pass the current "theme" to FancyButton.
// Use forwardRef to pass refs to FancyButton as well.
export default React.forwardRef((props, ref) = >( <ThemeContext.Consumer> { theme => ( <FancyButton {... props} theme={theme} ref={ref} /> ) } </ThemeContext.Consumer> ));Copy the code

React.forwardref () takes a function as an argument. In fact, you can think of this function as a function component that takes the same first argument as the function component. The difference is, it has an extra ref. This means that if you use a ref on a component created by the react. forwardRef, it will not be digested directly by the component, but will be forwarded internally for the component to digest.

If you find it hard to understand, this method can be replaced by another method. We know that in React, ref does not appear in props, it is treated specially. But we can just change the name.

It should be mentioned that in the past we used to get the ref by passing a function (strings are not recommended, this is a legacy of the past, and ref will not get the correct value in some cases. Vuejs work, don’t get confused). But this process is very annoying, we just need to assign the instance or DOM to the corresponding variable, and write the same template code every time, it is very annoying. In “A Thousand Calls,” React finally hears it. Now you just need React. CreateRef to simplify the process.

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this.myRef = React.createRef();
    }
    render() {
        return <div ref={this.myRef} />; }}Copy the code

It’s as simple as that. There’s nothing special about it.

Back to the topic above, now we use props to implement the forward refs function.

class Input extends React.Component {

    reder() {
		return (
			<label>Autofocus Input:</label>
			<input ref={this.props.forwardRef} type="text"/ >)}}function forwardRef(Component, ref) {
	return (<Component forwardRef={ref} />); ForwardRef let input = react.createref (); forwardRef(Input, input); // Input.current. Focus ();Copy the code

In the value returned by react. createRef, the current property represents the corresponding DOM or component instance. ForwardRef doesn’t have any special meaning, just a simple prop. This usage is like state promotion.