Component creation


  1. React. CreateClass in the ES5 era
  2. The Class Component and Stateless Component under ES6+
  3. Function Component in the React Hooks era

React is now in the transition from class Component to React Hooks. Many of you know little about the Component writing form of the earliest ES5 syntax. To get a complete overview of the React component’s evolution, let’s start again! 👏

React.createClass

React was opened in May 2013, during the ES5 era, which resulted in the earliest form of the React component based on the ES5 syntax, React. CreateClass. The basic example code for creating a component is as follows:

// Create an Input component
const Input = React.createClass({
  // Set the parameter type for props
  propTypes: {
    initialValue: React.PropTypes.string,
  },

  // The component's default props value
  defaultProps: {
    initialValue: ' ',},// Set initial state
  getInitialState: function() {
    return {
      text: this.props.initialValue || ' ',}},// Define the input onChange method
  handleChange: function(e) {
    this.setState({
      text: e.target.value,
    })
  },

  render: function() {
    const { text } = this.state;

    return (
      <div>
        <p>{text}</p>
        <input type="text" value={text} onChange={this.handleChange} />
      </div>); }});Copy the code

The naming of this method is in keeping with the trend of the industry at the time to emulate class syntax! 😁

Once you’re comfortable with class Component, it’s pretty easy to look back at the ES5 form: The main logic is to call the React. CreateClass method, And pass in as an object the lifecycle methods, render methods, propTypes props types, defaultProps default values, getInitialState initial state values, and other logical functions that the component needs to implement. The parameters needed to create the component were passed in configuration form, which was in line with ES5 functional processing logic, which was also official at the time! Technology is changing fast! In the following years, ES6 quickly became popular in the front-end world with its novel and concise syntax. React introduced class Component and Stateless Component creation methods after V0.14, which were highly recommended by the authorities. Homestead replaced ES5’s React. CreateClass! In later iterations. So this method has been completely abandoned, students can understand! 😏

In order to efficiently build React applications, Facebook provides a prop-types tool to check the component props pass parameter types. In order to reduce the size of React Core package, the logic of prop-types was extracted into a separate NPM package after V15.5, but the usage remained unchanged. Instead, it leaves the choice up to the developer to choose between prop-types, TypeScript, or any other type checking tool!

class Component

Class Component is a product of the ES6 class syntax. Because it introduced the standard class syntax, it ended years of reinventing analog wheels. Class Component, as its name suggests, is implemented based on class syntax. Example code for creating a component using class syntax is as follows:

// Create an Input component
export default class Input extends React.Component {
  constructor(props) {
    super(props);

    // Initialize state
    this.state = {
      text: props.initialValue || 'placeholder'
    };

    // Manually bind this to the function
    this.handleChange = this.handleChange.bind(this);
  }

  // Define the onChange method for input
  handleChange(e) {
    this.setState({
      text: e.target.value,
    })
  },
  
  render() {
    const { text } = this.state;

    return (
      <div>
        <p>{text}</p>
        <input type="text" value={text} onChange={this.handleChange} />
      </div>); PropTypes = {initialValue: React.PropTypes. String}; // The default component props value input. defaultProps = {initialValue: "};Copy the code

The main idea of class Component is to use the ES6 class syntax to create components by extending react.components.ponet or react.pureComponent. In the actual project, we mainly implement render method and implement component lifecycle method on demand.

⚠️ Note: Be very careful with this pointing during function calls in JSX, or your code could explode at any time 💥!

Stateless Component

Class Component is all about object-oriented thinking. The Class syntax is also a big hurdle for many students who have not been exposed to object-oriented programming. Inheritance and constructors are annoying, and dealing with life cycle methods is even more annoying. And not all components need to manage their state; some components simply accept data and display it (more on that later). React introduced the Stateless Component after V0.14. Example code is as follows:

export default function Button(props) {
  const { color, text } = props;
  
  return (
    <button className={`btn btn-The ${color} `} >
      <em>{text}</em>
    </button>
  );
}

Button.propTypes = {
  color: PropTypes.string,
  text: PropTypes.string,
};

Button.defaultProps = {
  color: 'blue'.text: 'Click'};Copy the code

The way this component is created is very simple: it is a function, the data is passed entirely from the parent component, and the logic is simple: take the data and present it. There is no state of its own to manage, and this does not refer to the component itself and does not worry about the lifecycle. There is a downside: there is no good way to avoid useless rerender. Still, it’s a great asset for React developers!

The Class Component and Stateless Component have been Component development models for building React applications for a long time, but there is no good way to reuse Component logic (although the community and the authorities have been exploring). To take functional programming and component logic reuse to the extreme, Facebook has officially launched Hooks — functional programming for maximum reuse of reusable component logic.

Functional Component

React 18.6 added the Hooks feature that allows us to do away with class Component programming, not completely, making functional programming more radical and taking React to a whole new level by fixing the long-standing problem of reusing code logic. Example code for a functional component is as follows:

export default function CountButton(props) {
  const { color, text } = props;
  const [count, setCount] = React.useState(0);
  const setCountMemo = React.useCallback((a)= > setCount(count= > count + 1), []);
  
  return (
    <>
      <p>{count}</p>
      <button
        className={`btn btn-The ${color} `}onClick={setCountMemo}
      >
        <em>{text}</em>
      </button>
    </>
  );
}

CountButton.propTypes = {
  color: PropTypes.string,
  text: PropTypes.string,
};

CountButton.defaultProps = {
  color: 'blue',
  text: 'Click',
};
Copy the code

Er… Is it deja vu? Here’s the Stateless Component you can scroll back to (if you’re familiar with it, you don’t need to). It just declares a function, but there’s something in the logic that the Stateless Component doesn’t have — it can own and manage its own state, and it adds memoize methods to optimize functions and components.

conclusion

Hooks are a continuation of React, but they are entirely optional. It is official that Hooks are completely optional, meaning you do not need to rewrite everything you already have using Hooks, and you do not have to learn and use them now. Hooks are 100% backward compatible, and there are no official plans to remove class Component. Therefore, in the actual project development, according to the actual situation of their own team and business needs, comprehensive consideration, choose suitable for their own project component development mode, rather than blindly follow!

Component Design Principles

Single responsibility

The single responsibility principle is one of the main ideas of object-oriented programming. The same principle applies to the React component! We want you to think of a component as a function or an object when planning it, scoping it according to the principle of a single function. That is, a component can in principle only be responsible for one function. If it needs more functionality, consider breaking it up into smaller components. The benefits of this design are many:

  • The complexity of components can be reduced. The logic of a component that is responsible for only one function is definitely much simpler than that of a component that is responsible for multiple functions.
  • Since the component is responsible for only one function, it must be small and beautiful, thus improving the readability of the component and enhancing the maintainability of the system.
  • The risk of change is reduced. While change is inevitable, if the single responsibility principle is followed well, when one function is modified, the impact on other functions can be significantly reduced;
  • The reusability of individual components is improved. Because a component is responsible for only one function, where that function is repeated, the logic of that component can be reused well.



Composition over Inheritance

This is the philosophy advocated by React officials. The origin of inheritance comes from the abstraction of the same characteristics and behaviors in multiple classes. Subclasses can achieve code reuse by inheriting from the parent class and calling methods and properties defined in the parent class. In addition to reusing the code of the parent class, subclasses can extend their own properties and methods to describe subclass-specific characteristics and behavior. Through inheritance, code can be reused to a large extent, and subclasses can easily extend their own specific properties and methods. Advantages of inheritance:

  • Most of the functionality of the parent class can be automatically transferred to the child class through inheritance.
  • It is easier to modify or extend inherited properties and methods.

But there are drawbacks:

  • Reuse of multiple class code cannot be achieved through inheritance
  • The method subclass of the parent class inherits unconditionally, which is easy to cause method pollution
  • Methods inherited from a parent class are static reuse. It cannot be changed at run time and is not flexible enough
  • Additionally, classes in JavaScript do not directly support multiple inheritance

Inheritance works, but you need to be careful with it. In general, there are two conditions for using inheritance:

  • All properties and methods in the parent class apply in the child class.
  • Subclasses do not need to reuse code from other classes.

If these two conditions are not met, aggregation/composition relationships should be used instead of inheritance to achieve code reuse. Composition is to extract some characteristics and behaviors, form utility classes, and then become the attributes of the current class through aggregation/composition, and then call the attributes and behaviors to achieve code reuse. The disadvantage of inheritance can be solved by aggregation/composition relationship. Since a class can have multiple attributes, it can aggregate multiple classes. So you can reuse code from multiple classes through aggregation/composition relationships.

Component classification





Any state

As we all know, the React component has two types of state data. One is the state owned and managed by the component itself. Once this data state is transmitted to the sub-component as a parameter, the sub-component receives another data state — props. The props is a state managed by the parent component, but the child component can also convert the props received to its own state, or pass it transparently to its grandson component as a parameter (see the extension for a detailed description of the data flow). Therefore, components can be divided into:

  • State component: As the name implies, a component that owns and manages its own state. These components are usually associated with specific business logic and are often used in real project scenarios. With the exception of the Stateless Component, a state Component can be created in any of the above ways.
  • Stateless components: A Stateless Component is simpler than a stateful Component. It accepts props passed by the parent Component, has no state of its own, and this during execution does not refer to the Component itself, making it unable to use Component instance-specific methods and lifecycle methods. But its extreme simplicity makes it extremely powerful and highly available, and makes it non-optimizable (something that was fixed in the Functional Component that supports Hooks).

Separate responsibilities

In real business scenarios, components play a critical role. Components often mix logic and presentation.

  • Logic: This generally refers to things unrelated to the UI, such as API calls, data manipulation, and event handling.
  • Presentation: The part of the render method that creates elements to display the UI.

If a project contains a large number of components that mix logic and presentation, you can imagine the high maintenance cost and poor logic reuse capability of the project. To improve project maintainability and reuse of component logic, we need to clearly define the boundaries between logic and presentation, and then separate them into two components with different functions:

  • Container component: Also known as smart component, it contains everything related to the specific business logic, such as API calls, data manipulation, and event processing, and passes the processed data state to the presentation component in the form of props. Normal container components are state components!
  • Presentation component: A.K.A. Dumb component, which simply contains the DEFINITION of the UI and whose data state is received primarily from the container component in the form of props. Components of this class are played by stateless components!

Whether or not a controlled

Form is the most common way to collect user information. A general form contains many form element components, such as input, checkbox, radio, etc., which can use the native implementation mode to control their own state without external intervention. However, in actual project development, in order to ensure the integrity of functions, default values of these components are generally provided or the requirement of modifying their state through external components. At this time, the management of the state of these components needs external intervention. To do this, we divide form element components into the following two categories:

  • Free component: The state of a component is completely managed by itself, without outside involvement.
class Uncontrolled extends React.Component {
  constructor(props) {
    super(props);
    
    this.state = {
      value: ' '
    }

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  
  handleChange(e) {
    this.setState({
      value: e.target.value
    });
  }
  
  handleSubmit(e) {
    e.preventDefault();
    console.log(this.state.value);
  }
  
  // ⚠️ note: the input value is completely self-contained, we do not set it by value,
  // Instead of listening for the value change through the onChange event, save the changed value
  render() {
    return (
      <form>
        <input type="text" onChange={this.handleChange} />
        <button onClick={this.handleSubmit}>submit</button>
      </form>)}}Copy the code
  • Controlled components: In contrast to a free component that manages state completely by itself, the state of a controlled component is externally controlled through props or state.
class Controlled extends React.Component {
  constructor(props) {
    super(props);
    
    this.state = {
      firstName: 'li'.lastName: 'lane'
    }

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  
  handleChange({ target }) {
    this.setState({
      [target.name]: target.value
    });
  }
  
  handleSubmit(e) {
    e.preventDefault();
    
    const { firstName, lastName } = this.state;
    console.log(`fullName: ${firstName} ${lastName}`);
  }
  
  // ⚠️ Note: In contrast to the free component above, where the data state is completely in your hands, it is obvious that the input is set to value in real time via value
  // The source of the value is listening for input via the onChange event
  render() {
    const { firstName, lastName } = this.state;
    
    return (
      <form>
        <input
          type="text"
          name="firstName"
          value={firstName}
          onChange={this.handleChange}
        />
        <input
          type="text"
          name="lastName"
          value={lastName}
          onChange={this.handleChange}
        />
        <button onClick={this.handleSubmit}>submit</button>
      </form>
    )
  }
}
Copy the code

conclusion

In actual project development, the usage scenarios of stateless component and state component can correspond to the usage scenarios of presentation component and container component respectively, which can be split and used according to actual requirements. For free and controlled components, controlled components are used more because they are very flexible and can provide a good API for external use and customization, while free components are the opposite!

Component design pattern



Mixin

React no longer supports mixins, but in order to trace the history of mixins, we need to know about them.

First, mixins can only be used with the React. CreateClass factory method.

We can create a Mixin by creating an object literal that has the same methods and properties as the component, such as:

// Define a Mixin
const WindowResize = {
  getInitialState() {
    return {
      innerWidth: window.innerWidth
    }
  },
    
  componentDidMount() {
    window.addEventListener('resize'.this.handleResize)
  },
    
  componentWillUnmount() {
    window.removeEventListener('resize'.this.handleResize)
  },
  
  handleResize() {
    this.setState({
      innerWidth: window.innerWidth
    })
  }
}

// Use mixins in components
const MyComponent = React.createClass({
  mixins: [WindowResize],
  render() {
    console.log('window.innerWidth'.this.state.innerWidth)
  }
})
Copy the code

In the component, there is a Mixins array property that holds the mixins that need to be injected. A Mixin can be introduced in multiple components, and the same component can introduce multiple mixins. Does this feel familiar (even if you haven’t used mixins in React) if you’ve used Vue? Of course, like mixins in Vue, you can combine lifecycle methods and initialization state. Why, one might ask, would such a useful feature be abandoned? Here are a few Mixin SINS:

  • Component communication: Mixins and components can communicate in two ways: internal functions and states. The implementation of internal functions is to make logic more flexible, but in the actual use process, we have to check the implementation of each related Mixin, which undoubtedly increases the mental burden of developers; In addition, because mixins are packaged logic, sometimes mixins update special properties in components, causing components to be re-rendered, but it is difficult for developers to control or locate the problem.
  • Logical redundancy: If a component uses multiple mixins but ultimately needs to implement different functions, it can be difficult to eliminate obsolete code when some mixins are removed or behavior changes.
  • Conflict: For this type of injected code reuse, conflict is an issue that must be considered. While React can intelligently merge lifecycle methods, there’s nothing React can do if multiple mixins define or call the same function, or use the same data in the state.
  • Interdependencies: This is especially painful when mixins are interdependent, making component refactoring and application scaling very difficult.

Of course, if you have used or are using a Vue development project and you have also used mixins, then you must have the above concerns, or even if you have encountered the above problems, then don’t be afraid to face it with a smile. 😄


HOC

Based on the above introduction to mixins, it’s not hard to see that mixins seem to cause problems far beyond their usefulness in sharing functionality between components. With the arrival of Class Components, mixins have been completely abandoned, but the pattern of exploring shared functionality between components needs to continue. Thus, the community derived the concept of HOC (higher-order component) from the concept of higher-order function. Higher-order functions are functions that enhance the passed function and return a new function with additional behavior added. The higher-order component is also a function that takes a component as an argument and returns an enhanced component, like this:

const Hoc = Component= >EnhancedComponent orconst Hoc = args= > Component => EnhancedComponent
Copy the code

HOC is defined in three forms:

  • Property broker: a common implementation of higher-order components. Typically, higher-order components take a component as an argument and return a new component. The new component returned is just a proxy for the parameter component passed in and rendered in the Render method. In addition to the logical processing of the higher-order components, the rest of the function is handed over to the parameter components. One way to pass properties through higher-order components is through property brokers.
const HOCComponent = (WrappedComponent) = > {
  return class NewComponent extends React.Component {
    // do something ...
    render() {
      return (
        <WrappedComponent { . this.props} / >)}}}Copy the code
  • Reverse inheritance: Also known as rendering hijacking, where the new component returned by the higher-order component inherits from the passed parameter component. Because the new component returned by the higher-order component passively inherits from the passed parameter component, all calls are reversed, so it is called reverse inheritance.
const HOCComponent = (WrappedComponent) = > {
  return class NewComponent extends WrappedComponent {
    // do something ...
    render() {
      if (this.props.isLogin) {
        return super.render()
      }
      return null}}}Copy the code
  • Higher-order component factory functions: These are designed to separate the input of parameters and components, such as:
const Hoc = args= > Component => EnhancedComponent
Copy the code

This approach exists in libraries like React-Redux and Ant Design, and I’m sure you’ve used it.

For more information on Mixin and the functions and usage scenarios of the three forms of HOC, you can skip to the extended reading section at ✋!


  • When multiple HOC is used together, it is not possible to directly determine which HOC is responsible for delivering the props of the child component
  • Multiple components are nested, making it easy to produce props with the same name
  • HOC can produce many useless components, deepening the layer of components (Wrapper hell)

Render Props

Render Props is a simple technique for sharing code between React components using a prop with a value of function. A component with Render Props accepts a function that returns a React element and calls it instead of implementing its own Render logic. Like:

const DataProvider = (props) = > {
	const{ render, ... restProps } = propsif (typeof render === 'function') {
    returnrender(... restProps) }return null
}

// Use the Render Props technique
const MyComponent = (a)= > (
  <DataProvider render={data= > (
    <h1>Hello {data.target}</h1>)} / >)Copy the code

Remember Context? It also uses Render Props, except that it assigns the component functions that need to be rendered to the Chlidren property.

const { Provider, Consumer } = React.createContext(defaultValue)

const Parent = (a)= > (
  <Provider value={/ * some value* /} >
  	<Child />
  </Provider>	
)

const Child = (a)= > (
  <Consumer>
    {value => /* render something based on the context value */}
  </Consumer>
)
Copy the code

The Render Props rely entirely on the extremely flexible nature of the React component for parameter transfer. It is still driven not by authorities but by the community! The Render Props mode was developed primarily to solve problems with HOC. It has the following advantages:

  • Support for ES6
  • Don’t worry about naming props, just take the required state in the render function
  • No unnecessary components are created to deepen the hierarchy
  • The Render props model is built dynamically, with all changes triggered in Render to take advantage of the life cycle within the component.

Hooks

Hooks are new in React 16.8. It lets you use state and other React features without having to write a class.

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable called "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={()= > setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
Copy the code

There is a lot to learn about the Hooks that are currently used to write React components. In the future, I will write a separate article about Hooks in detail, so stay tuned!

conclusion

In order to better realize the reuse of logic between components, one after another component design pattern was born, the background of the birth of these patterns condensed the wisdom and sweat of many people. Each model has its own practical value, and we should not look at them with colored eyes, but with respect. In the actual business scenario, we should weigh and discuss again and again, rather than use this one today and that one tomorrow, and should not blindly follow.

The full text summary

This is the end of the article! First speak to React the evolution of the components in the creation method, and then I think design components should ensure high availability of a few big principle, then speak to in the actual business development, we will often come into contact with the business component classification and comparison, finally spoke the React in order to reuse the logic between components and produce a series of design patterns. Although the explanation for no point is very rough, but at least the general idea is out, it is a good start. Again, there are two points: technology is not good or bad, only suitable; To fully understand a technology, you must go back to its roots 👏!

Q&A

1, Class Component this points to problem solving

  • Manually bind this in the constructor constructor:

The constructor constructor is executed only once when the component is initialized, so if you bind this to the function here, the bound function is fixed, as is referenced in JSX, avoiding unnecessary rerender because of function reference changes. But here’s the rub: every function that needs to be referenced in JSX has to be bind in the constructor constructor, which is a lot of work and easy to forget.

  • Bind this when referencing functions in JSX

JSX can be mixed with HTML/CSS /JavaScript. It is perfectly possible to bind attributes dynamically, so we can solve the problem with this binding by:

// Create an Input component
export default class Input extends React.Component {
  constructor(props) {
    super(props);

    // Initialize state
    this.state = {
      text: props.initialValue || 'placeholder'
    };

    // Manually bind this to the function
    // this.handleChange = this.handleChange.bind(this);
  }

  // Define the onChange method for input
  handleChange(e) {
    this.setState({
      text: e.target.value,
    })
  },
  
  render() {
    const { text } = this.state;

    return (
      <div>
        <p>{text}</p>{/* Dynamically bind this */} in JSX<input type="text" value={text} onChange={this.handleChange.bind(this)} />
      </div>); PropTypes = {initialValue: React.PropTypes. String}; // The default component props value input. defaultProps = {initialValue: "};Copy the code

This approach resolves the problem with the this binding, but this.handlechange.bind (this) evaluates the new value dynamically every time the diff is compared, meaning that the rerender is different every time. If it is the corresponding component will not hesitate to re-render.

  • Declare functions with arrow function syntax
// Create an Input component
export default class Input extends React.Component {
  constructor(props) {
    super(props);

    // Initialize state
    this.state = {
      text: props.initialValue || 'placeholder'
    };

    // Manually bind this to the function
    // this.handleChange = this.handleChange.bind(this);
  }

  // Define the onChange method for input
  // Declare functions with arrow function syntax
  handleChange = (e) = > {
    this.setState({
      text: e.target.value,
    })
  },
  
  render() {
    const { text } = this.state;

    return (
      <div>
        <p>{text}</p>
        <input type="text" value={text} onChange={this.handleChange} />
      </div>); PropTypes = {initialValue: React.PropTypes. String}; // The default component props value input. defaultProps = {initialValue: "};Copy the code

This method takes full advantage of the property of the arrow function: the arrow function itself has no this; its this inherits entirely from its definition context. Defining this in the component refers to the component instance, so the this reference in the logic is the component instance, which is a good solution to the this reference problem. Furthermore, the initialization of the component instance occurs as the component is mounted, so no matter how many times the component rerender is rerender after it is mounted, the handleChange is the same instantiated value, which is a good way to avoid useless rerender. Is recommended usage!

react.component.react.pureComponent

If you’re familiar with the React component, you’ll know how to avoid useless rerender via the lifecycle method — shouldComponentUpdate. This method accepts the new props and the new state, avoiding component rerender by comparing the old props and state to return true or false. The difference between Component and PureComponent is: The PureComponent already implicitly implements shouldComponentUpdate and doesn’t need to be repeated, whereas the Component needs to be implemented according to its own needs.

3. Stateless Component VS Functional Component

That’s right! Both simply declare a function, but there are obvious differences between them:

  • An FC can own and manage its own state via Hooks, whereas an SC can’t own its own state and simply display data
  • FC can optimize rerendering with Hooks dependencies and memoize methods
  • FC can also emulate the lifecycle methods in class Component via Hooks



read

  • React Data flow state
  • React Props for data flow
  • 3, the React of HOC
  • 4, Render Props
  • 5, the React of Hooks