The introduction

We all know that cross-cutting concerns arise during business development when disparate components have similar capabilities.

In React, there are some best practices for dealing with crosscutting concerns that can help us better reuse our code logically.

Mixins

Introducing mixins is a good solution to this problem when creating React components using createReactClass.

React includes some first-aid solutions to make it easier to adapt to and learn React in the initial stage. The Mixin system is one of them.

So we can wrap general-shared methods into Mixins methods and inject each component into a logical reuse implementation.

The principle of

const mixin = function(obj, mixins) {
  const newObj = obj;
  newObj.prototype = Object.create(obj.prototype);
  for (let prop in mixins) {
    if(mixins.hasOwnProperty(prop)) { newObj.prototype[prop] = mixins[prop]; }}return newObj;
}
Copy the code

The above code implements a simple mixin function that essentially assigns the method traversal from the mixins to newobj.Prototype so that any function returned from the mixin creates an object that has the methods from the mixins, that is, mixes in all the extra functionality.

Now that we have a rough idea of what mixins do, let’s take a look at how mixins are used in React.

application

var RowMixin = {
  renderHeader: function() {
    return (
      <div className='row-header'>
        <h1>
          {this.getHeaderText()}
        </h1>
      </div>); }};var UserRow = React.createClass({
  mixins: [RowMixin], // Mix in the renderHeader method
  getHeaderText: function() {
    return this.props.user.fullName;
  },
  render: function() {
    return (
      <div>
        {this.renderHeader()}
        <h2>{this.props.user.biography}</h2>
      </div>)}});Copy the code

Using React. CreateClass, the official mixins interface is provided. The logic of the code that needs to be reused can be mixed in here.

This is an ES5 notation, which has actually been deprecated since Version 16.

ES6 itself does not include any mixin support. Therefore, when you use ES6 classes in React, mixins are not supported.

Officials have also found many code libraries that use mixins and then go wrong. It is not recommended to use them in new code.

disadvantages

Mixins Considered Harmful

  • Mixins introduce implicit dependencies

  • Mixins cause name clashes

  • Mixins cause snowballing complexity

From the official blog: reactjs.org/blog/2016/0…

An article on the official blog details the reasons for the abandonment. It lists three charges, as described above.

During actual development, we can’t predict what properties and states someone mixin into the code. If you want the mixin’s own functionality, you may have conflicts, or even need to decouple the previous code.

This approach also breaks the encapsulation of components and makes it difficult to refactor the code because the dependencies between the codes are invisible. If you make changes to the component, it is likely that the mixin methods will be faulty or invalidated.

Later in the development and maintenance process, this leads to snowballing complexity.

The name of the conflict

Component contains multiple mixins —

  • React throws an exception when different mixins have non-lifecycle functions with the same name (instead of the later function overwriting the previous function > number).

  • Different mixins have lifecycle functions with the same name that do not throw exceptions. The same lifecycle functions in mixins (except the Render method) are called in the order of the mixins array passed in to the createClass, and the same declaration cycle functions within the component are called when they are all called.

  • React raises an exception if the same key exists in the default props or initial state of different mixins.

Mixins deal with name conflicts in different cases. Only when lifecycle functions with the same name are called in the declared order, the function with the same name is called in the end. Any other case will throw an exception.

Mixins this mixed with pattern, increasing new methods and properties to components, the component itself can sense not only, even need to do related processing (for example naming conflicts, state maintenance), once with module gains, the whole component is difficult to maintain, is why so many React library were conducted using high order component development.

HOC

After mixin, many open source component libraries use higher-order component writing.

Higher-order components belong to the idea of functional programming.

The presence of the higher-order component is not perceived by the wrapped component, and the component returned by the higher-order component is enhanced on top of the original component.

Higher-order functions

Speaking of higher-order components, let’s first talk about the definition of higher-order functions.

In mathematics and computer science, a higher-order function is one that satisfies at least one of the following conditions:

  • Takes one or more functions as input

  • Output a function

Simply put, a higher-order function is one that takes a function as input or output.

const add = (x,y,f) = > f(x)+f(y);
add(- 5.6.Math.abs);
Copy the code

High order component

A higher-order component is a function that takes a component and returns a new component.

A higher-order component is a function that takes a component and returns a new component. Note that despite its name, a higher-order component is a function that enhances or gives a new function to the component it wraps around.

It is not part of the React API, but is derived from the React ecosystem and is officially favored as a way to reuse combinations. It corresponds to the decorator pattern in the design pattern.

Higher-order components, there are two main ways to handle wrapped components, namely property proxy and reverse inheritance.

Properties Proxy (Props Proxy)

Essentially operating props by wrapping the original component

  • Operating props

  • Get the REFS reference

  • Abstract the state

  • Wrap components with other elements

export default function withHeader(WrappedComponent) {
  return class HOC extends Component {
    render() {
      const newProps = {
        test:'hoc'
      }
      // Pass through props, and pass the new newProps
      return <div>
        <WrappedComponent {. this.props} {. newProps} / >
      </div>}}}Copy the code

The property broker, in effect, injects some additional props or state by wrapping the original component.

To enhance maintainability, there are some built-in conventions, such as using the withSomething format when naming higher-order components.

It is best to pass through the props as it is passed, without breaking the properties and state of the component itself.

Inheritance Inversion

  • Rendering hijacked

  • Operation props and state

export default function (WrappedComponent) {
  return class Inheritance extends WrappedComponent {
    componentDidMount() {
      // You can easily get state to make some more in-depth changes.
      console.log(this.state);
    }
    render() {
      return super.render(); }}}Copy the code

Reverse inheritance fetches all methods on the superclass prototype object through the super keyword (properties or methods on the superclass instance cannot be fetched). In this way, their relationships look like inverse.

Reverse inheritance can hijack rendering and can do things like delayed rendering/conditional rendering.

convention

  • Convention: Pass unrelated props to the wrapped component

  • Convention: Wrap the display name for easy debugging

  • Convention: Maximize composability

// Instead of this...
const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent))

/ /... You can write composite utility functions
// compose(f, g, h) is equal to (... args) => f(g(h(... args)))
const enhance = compose(
  // These are single-parameter HOC
  withRouter,
  connect(commentSelector)
)
const EnhancedComponent = enhance(WrappedComponent)
Copy the code

For example, compose(a, B, C) returns a new function D, which still takes a function as an input, but internally calls c, B, and A in order to remain transparent to the user from the presentation layer. This feature allows you to easily enhance or degrade the characteristics of a component by changing the number of parameters in the compose function.

Application scenarios

  • Module reuse

  • Page authentication

  • Log and performance tracking

example


export const withTimer = (interval) = > (wrappedComponent) => {

  return class extends wrappedComponent {
    constructor(props) {
      super(props);
    }
    // Pass endTime to calculate the remaining timestamp
    endTimeStamp = DateUtils.parseDate(this.props.endTime).getTime();

    componentWillMount() {
      // If it does not expire, call the timer manually to start counting down
      if (Date.now() < this.endTimeStamp) {
        this.onTimeChange();
        this.setState({expired: false});
        this.__timer = setInterval(this.onTimeChange, interval);
      }
    }

    componentWillUnmount() {
      // Clean up the timer
      clearInterval(this.__timer);
    }

    onTimeChange = (a)= > {
      const now = Date.now();
      // Calculate the hours, minutes, and seconds injected into the target component according to the remaining timestamp
      const ret = Helper.calc(now, this.endTimeStamp);
      if (ret) {
        this.setState(ret);
      } else {
        clearInterval(this.__timer);
        this.setState({expired: true});
      }
    }

    render() {
      // Reverse inheritance
      return super.render(); }}; };Copy the code

@withTimer()
export class Card extends React.PureComponent {
  render() {
    const {data, endTime} = this.props;
    // Get the state of the AD hoc injection directly
    const {expired, minute, second} = this.state;
    // Omit the render logic
    return(...). ; }}Copy the code

The requirement is to have a timer countdown, and many components need to inject the countdown function. So let’s extract it as a higher-order component.

This is a reverse inheritance method that takes the properties and state of the component itself, and then injects states such as minutes and seconds into the component.

The original component can be enhanced by using ES7’s decorator syntax.

The component itself only needs to have an endTime property, and then the higher-order component can count the minutes and seconds and count down.

In other words, the higher-order component gives the original component the function of countdown.

Pay attention to

There are a few caveats when writing higher-order components.

  • Do not use higher-order components in render functions
render() {
  // Each call to the Render function creates a new EnhancedComponent
  // EnhancedComponent1 ! == EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  // This will cause subtrees to be unmounted and remounted every time they are rendered!
  return <EnhancedComponent />;
}
Copy the code

If created in the Render function, a new component is rerendered each time. This is not just a performance issue; every time you reset the state of the component, you can also cause code logic errors.

  • Static methods must be copied
// Define a static function
WrappedComponent.staticMethod = function() {/ *... * /}
// Now use HOC
const EnhancedComponent = enhance(WrappedComponent);

// There is no staticMethod for the enhanced component
typeof EnhancedComponent.staticMethod === 'undefined' // true
Copy the code

When you apply HOC to a component, the original component is wrapped with a container component. This means that the new component does not have any static methods of the original component.

You can automatically copy all non-React static methods using hoist non-react statics:

  • Refs will not be passed

In general, higher-order components can pass all props properties to wrapped components, but not refs references. Because refs is not a pseudo-property like key, React makes it special.

If you add a REF application to an element of a component created by a high-level component, ref points to the outermost container component instance, not the wrapped component.

React Hooks

Use state and other React features without writing a class.

A Hook is a function that lets you Hook react state, lifecycle, etc. It cannot be used in a class component.

motivation

  • Reuse state logic between components

    • render props

      Any function that is used to tell a component what to render is technically called render prop

      If you create anonymous functions in the Render method, using Render Prop negates the benefits of using the React.PureComponent. The Render method needs to be created as an instance function or passed in as a global variable.

    • hoc

    • providers

    • consumers

    The components of these abstraction layers form a nested hell, so React needs to provide a better native way to share state logic.

  • Enhance code maintainability

  • Class is hard to understand

The React community has accepted the React hooks proposal, which will reduce the number of concepts that need to be considered when writing React applications.

Hooks enable you to always use functions without having to switch between functions, classes, higher-order components, and reader props.

Hooks

  • Based on the hooks

    • useState

    • useEffect

      Enable the Bare-deps rule in eslint-plugin-react-hooks. This rule warns when you add false dependencies and suggests how to fix them.

    • useContext

  • Additional hooks

    • useReducer

    • useCallback

    • useMemo

    • useRef

    • useImperativeHandle

    • useLayoutEffect

    • useDebugValue

  • Custom Hook useSomething

    A custom Hook is a mechanism for reusing state logic, with all states and side effects completely isolated.

UseEffect is equivalent to componentDidMount, componentDidUpdate, and componentWillUnmount.

In addition to the official Hook API, you can use custom hooks.

Custom hooks need not have a special identity. We are free to decide what its arguments are and what it should return (if necessary).

In other words, it’s just like a normal function. But its name should always start with use, so it can be seen at a glance that it meets Hook rules.

Animations, subscription declarations, and timers are common operations for custom hooks.

Next, we’ll rewrite the previous advanced component demo with React Hook.

example

export function useTimer(endTime, interval, callback) {
  interval = interval || 1000;
  
  // Use useState Hook get/set state
  const [expired, setExpired] = useState(true);
  const endTimeStamp = DateUtils.parseDate(endTime).getTime();

  function _onTimeChange () {
    const now = Date.now();
    // Count the minutes and seconds
    const ret = Helper.calc(now, endTimeStamp);
    if (ret) {
      // Call back to the desired state for outgoingcallback({... ret, expired}); }else {
      clearInterval(this.__timer);
      setExpired(true); callback({expired}); }}// Use useEffect instead of lifecycle calls
  useEffect((a)= > {
    if (Date.now() < endTimeStamp) {
      _onTimeChange();
      setExpired(false);
      this.__timer = setInterval(_onTimeChange, interval);
    }

    return (a)= > {
      // Clear the timer
      clearInterval(this.__timer); }})}Copy the code
export function Card (props) {
  const {data, endTime} = props;
  const [expired, setExpired] = useState(true);
  const [minute, setMinute] = useState(0);
  const [second, setSecond] = useState(0);

  useTimer(endTime, 1000, ({expired, minute, second}) => {
    setExpired(expired);
    setMinute(minute);
    setSecond(second);
  });
  return(...). ;Copy the code

In addition to naming custom hooks, you need to follow the rules, and the incoming and returned parameters can be determined according to the specific situation.

Here, I pass out a callback after the timer returns every second, passing out parameters such as time, minute and second.

In addition, you can see that there is no class lifecycle. UseEffect to complete the side effects.

convention

Use an eslint-plugin-react-hooksesLint plugin to enforce these rules

  1. Use hooks only at the top level

Never call hooks in loops, conditions, or nested functions. Make sure you always call them at the top of the React function.

Because React calls hooks in the order in which you declared them, there is no guarantee that they will be rendered in the same order every time they are not called at the top level.

React follows the rules to keep the hook state correct between multiple calls to useState and useEffect.

  1. Only the React function calls the Hook

    • Call a Hook in the React function component

    • Call other hooks in custom hooks

reference

React

Mixins Considered Harmful

React advanced components

React Advanced Components (HOC) Introduction guide

Making Sense of React Hooks