React learning

Time:2021-2-26

Event system

React implements a synthetic event layer based on virtual dom. The processor we defined will receive an instance of synthetic event object, which fully conforms to W3C standard and will not have any ie compatibility problem. It also has the same interface as native browser events and supports event bubbling mechanism. We can use stoppropagation() and preventdefault() to interrupt it. If you need to access the native event object, you can use the nativeevent property.

Binding method of composite event

React events are bound in a way similar to native HTML event listener properties.

<button onClick={this.handleClick}>Test</button>

Implementation mechanism of composite event

At the bottom of react, we mainly do two things for composite events: event delegation and automatic binding.

1. Event delegation

React does not bind the event processing function to the real node directly, but binds all the events to the outermost layer of the structure. It uses a unified event listener, which maintains a mapping to save all the internal event listening and processing functions of components. When a component is mounted or unloaded, only some objects are inserted or deleted from the unified event listener. When an event occurs, it is first processed by the unified event listener, and then the real event handler is found in the mapping and called. (implementation principle: bind the outermost container, and delegate by relying on the bubbling mechanism of events.) This simplifies the event handling and recovery mechanism, and greatly improves the efficiency.

2. Automatic binding

In react component, the context of each method will point to the instance of the component, that is, automatically bind this as the current component. And react also caches this reference. When using ES6 classes or pure functions, this kind of automatic binding no longer exists. We need to implement this binding manually.
Let’s look at several binding methods
Bind method

class App extends Component {
  constuctor() {
    super(props)
    this.handleClick = this.handleClick.bind(this)
  }
}

The arrow function can automatically bind this to the scope of this function

class App extends Component {
  handleClick= () => {}
}

Using native events in react

class NativeEventDemo extends Component {
  componentDidMount() {
    this.refs.button.addEventListener('click', this.handleClick)
  }
  componentWillUnmout() {
    this.refs.button.removeEventListener('click', this.handleClick)
  }
}

Compare react synthetic event with JavaScript native event

1. Event propagation and prevention of event propagation

The propagation of browser native DOM events can be divided into three stages: event capture stage, event handler call of the target object itself, and event bubbling. You can register the capture event handler for element E when the third parameter of e.addeventlistener is set to true. Event capture cannot be used below IE9. Event capture is of little significance in application development. React does not implement event capture in synthetic events, only supports event bubbling mechanism.

To prevent the propagation of native events, we need to use e.stoppropagation, but for browsers that do not support this method (below IE9), we can only use e.cancelbubble = true to prevent it. In the react synthesis event, you only need to use stoppropagation(). The behavior of preventing react events from bubbling can only be used in react synthetic event system, and there is no way to prevent native events from bubbling. On the contrary, native events prevent bubbling, which can prevent the propagation of react synthetic events.

2. Event type

The event type of react synthetic event is a subset of JavaScript native event types. It only implements the event interface of DOM Level 3 and unifies the compatibility of browsers. Some events react is not implemented, or cannot be implemented due to some restrictions, such as window reset event.

3. Event binding mode

Influenced by Dom standard, there are many ways for browsers to bind native events. The binding method of react synthetic event is much simpler

<button onClick={this.handleClick}>Test</button>

4. Event object

In react synthetic event system, there is no compatibility problem, and a synthetic event object can be obtained.

form

In react, all data are States, including form data. Next, let’s talk about how react handles forms.

Application form component

All components in HTML form are implemented in JSX of react, but there are some differences in usage, some in JSX syntax, and some in state processing of react.

1. Text box

import React, { Component } from 'react';

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      inputValue: '',
      textareaValue: ''
    }
  }

  handleInputChange = (e) => {
    this.setState({
      inputValue: e.target.value
    });
  }

  handleTextareaChange = (e) => {
    this.setState({
      textareaValue: e.target.value
    })
  }

  render() {
    const { inputValue, textareaValue } = this.state;
    return (
      <div>
        <p>
          Single line input box:
          <input type="text" value={inputValue} onChange={this.handleInputChange}/>
        </p>
        <p>
          Multiline input box:
          <textarea type="text" value={textareaValue} onChange={this.handleTextareaChange}/>
        </p>
      </div>
    )
  }

}

In HTML, the value of textarea is represented by children, while in react, the value of form is represented by a value prop.

2. Radio button and check box

In HTML, the radio button is represented by the input tag of type radio, and the check box is represented by the input tag of type checkbox. Generally, the value of these two forms will not change, but a boolean type checked prop is used to indicate whether it is selected. These are the same in JSX, but there are some differences in usage.

Examples of radio buttons

import React, { Component } from 'react';

class App extends Component {
  construtor(props) {
    super(props);
    this.state = {
      radioValue: '',
    }
  }

  handleChange = (e) => {
    this.setState(
      radioValue: e.target.value
    )
  }

  render() {
    const { radioValue } = this.state;

    return (
      <div>
        <p>gender:</p>
        <label>
          male:
          <input 
            type="radio"
            value="male"
            checked={radioValue === 'male'}
            onChange={this.handleChange}
          />
        </label>
        <label>
          female:
          <input 
            type="radio"
            value="female"
            checked={radioValue === 'female'}
            onChange={this.handleChange}
          />
        </label>
      </div>
    )
  }
}

Example of a check button

import React, { Component } from 'react';

class App extends Component {
  constructor(props) {
    super(props)
    
    this.state = {
      coffee: []
    }
  }

  handleChange = (e) => {
    const { checked, value } = e.target;
    let { coffee } = this.state;

    if (checked && coffee.indexOf(value) === -1) {
      coffee.push(value)
    } else {
      coffee = coffee.filter(i => i !== value)
    }

    this.setState({
      coffee,
    })
  }

  render() {
    const { coffee } = this.state;
    return (
      <div>
        <p>Please choose your favorite coffee</p>
        <label>
          <input 
            type="checkbox"
            value="Cappuccino"
            checked={coffee.indexOf('Cappuccino') !== -1}
            onChange={this.handleChange}
          />
          Cappuccino
        </label>
        <br />
        <label>
          <input 
            type="checkbox"
            value="CafeMocha"
            checked={coffee.indexOf('CafeMocha') !== -1}
            onChange={this.handleChange}
          />
          CafeMocha
        </label>
      </div>
    )
  }
}

3. Select components

In the select element of HTML, there are two kinds: single selection and multiple selection. In JSX syntax, you can also set multiple = {true} of the select tag to implement a multiple selection drop-down list.

class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      area: ''
    }
  }

  handleChange = (e) => {
    this.setState({
      area: e.target.value
    })
  }

  render() {
    const { area } = this.state;

    return (
      <select value={area} onChange={this.handleChange}>
        < option value '='Beijing' > < option > Beijing
        < option value '='shangehai' > Shanghai < / option >
      </select>
    )
  }
}

Example of setting multiple = {true} with select element

class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      area: ['beijing', 'shanghai']
    }
  }

  handleChange = (e) => {
    const { options } = e.target;
    const area = Object.keys(options)
      .filter(i => options[i].selected === true)
      .map(i => options[i].value);

    this.setState({
      area,
    })
  }

  render () {
    const { area } = this.state;

    return (
      <select multiple={true} value={area} onChange={this.handleChange}>
        < option value = "Beijing" > Beijing < / option >
        < option value = "Shanghai" > Shanghai < / option >
      </select>
    )
  }
}

In HTML, the option component needs a selected attribute to represent the default selected list item, while react’s processing method is to add value prop to the select component to represent the selected option, which unifies the interface to a certain extent.

In fact, it can also be written in this form, but the development experience will be much worse, and react will throw a warning.

<select multiple={true} onChange={this.handleChange}>
  <option value="beijing" selected={ area.indexOf ('Beijing ')! = = - 1} > Beijing < / option >
  <option value="shanghai" selected={ area.indexOf ('shanghai ')! = = - 1} > Shanghai < / option >
</select>

Controlled components

Whenever the state of the form changes, it will be written into the state of the component, which is called the controlled component in react. In a controlled component, the state rendered by the component corresponds to its value or checked prop. In this way, react eliminates the local state of the component and makes the whole state of the application more controllable.

Uncontrolled components

If a form component has no value prop (or checked prop), it can be called an uncontrolled component. Accordingly, you can use default value and default checked prop to represent the default state of the component.

class App extends Compoent {
  constructor(props) {
    super(props)

  }

  handleSubmit = (e) => {
    e.preventDefault();

    const { value } = this.refs.name;
    console.log(value)
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input ref="name" type="text" defaultValue="Hangzhou" />
        <button type="submit">submit</button>
      </form>
    )
  }
}

In react, an uncontrolled component is an anti pattern whose value is not controlled by its own state or props. Generally, you need to add ref prop to it to access the underlying DOM elements after rendering.

Compare controlled and uncontrolled components

The biggest difference between the controlled component and the uncontrolled component is that the state of the uncontrolled component is not controlled by the application state, and there are more local component states in the application, while the value of the controlled component comes from the state of the component.

1. Performance problems

After controlled component onchange, calling setstate will render again, which will cause some performance loss.

2. Whether event binding is required

Controlled components need to bind a change event to each component, and define an event handler to synchronize the form value and component state.

However, controlled components are still advocated in react, because it can make the whole state of the application more controllable.

Several important properties of form component

1. Status attribute

React’s form component provides several important properties to show the state of the component.
Value: input component, textarea component and select component of type text all use value prop to show the application status.
Checked: components of type radio or checkbox display the application status by means of checked prop with Boolean value.
Selected: this attribute can be applied to the options under the select component. React does not recommend using work in this way. Value is recommended

2. Event properties

When the state property changes, the onchange event property is triggered. In fact, the change event in the controlled component is more similar to the input event provided in the HTML DOM. React supports all form events defined in DOM level3.

Style processing

Basic style settings

React can set class for HTML by setting classname prop, and can also set in line style for components by setting style prop.

Using the classnames Library

We can set the class name of HTML through the classnames library

CSS Modules

There are many solutions for CSS modularization, but there are mainly two types.

  1. Inline Style。 This solution completely abandons CSS and uses JavaScript or JSON to write styles, which can provide CSS with the same powerful modularity as JavaScript. However, its disadvantages are also obvious. It can hardly make use of the characteristics of CSS itself, such as cascading, media query, etc. pseudo classes such as: hover and: active are more complex to handle. In addition, this solution needs to rely on framework implementation, among which radius, jsxstyle and react style are related to react
  2. CSS Modules。 CSS is still used, but JavaScript is used to manage style dependencies. CSS modules can maximize the combination of existing CSS ecology and JavaScript modularization ability, and its API is very simple. Separate JavaScript and CSS files are compiled at the time of release. Now, the web pack CSS loader has built-in CSS modules.

1. What are the problems of CSS modularization

CSS modularization is important to solve the following two problems: CSS style import and export. Flexible on-demand import to reuse code, export to hide the internal scope, so as to avoid global pollution. Sass, less and postcss try to solve the problem of weak programming ability of CSS, but they don’t solve the problem of modularity. The CSS related problems needed in the actual development of react are as follows:

  1. Global pollution: CSS uses the global selector mechanism to set styles. The advantage is that it is convenient to rewrite styles. The disadvantage is that all styles take effect globally, and styles may be covered by errors. As a result, there are very ugly! Important, even inline! Important and complex selector weight count table, which can improve the error probability and use cost. Shadow DOM in web component standard can completely solve this problem, but it completely localizes styles, which makes it impossible for external users to rewrite styles and loses flexibility.
  2. Naming confusion: due to the problem of global pollution, in order to avoid style conflicts, selectors become more and more complex, and it is easy to form different naming styles. With more styles, naming will be more chaotic.
  3. Incomplete dependency management: components should be independent of each other. When a component is introduced, only the CSS styles it needs should be introduced. The current approach is to introduce JavaScript as well as its CSS. Moreover, it is difficult for sass / less to compile a separate CSS for each component, and the introduction of CSS for all modules will cause waste. JavaScript modularization is very mature. It is a good solution to let JavaScript manage CSS dependencies, and the CSS loader of webpack provides this ability.
  4. Can’t share variables: if complex components want to use JavaScript and CSS to handle styles together, some variables will be redundant in JavaScript and CSS, while precompiled languages can’t provide the ability to share variables across JavaScript and CSS.
  5. Incomplete code compression: nothing to do with very long class names.

2. CSS modules modularization scheme

In CSS modules, ICSS is used to solve the two problems of style import and export, which correspond to two new pseudo classes: import and export.

:import("path/to/dep.css") {
  localAlias: keyFromDep;
}

:export {
  exportedKey: exportedValue;
}

But it’s too cumbersome to use these two keywords directly in programming. We rarely use them directly in projects. What we need is the ability to manage CSS with JavaScript. Combined with the CSS loader of webpack, you can define styles in CSS and export them in JavaScript files.

Enable CSS modules

css?modules&localIdentName=[name]__[local]-[hash:base64:5]

Add modules to enable it, where localidentname is the naming rule for setting generation style

Let’s see how JS introduces CSS

/*All button related styles*/
.normal {}
import styles from './Button.css'

buttonElm.outerHTML = `<button class=${styles.normal}>Submit</button>`

The resulting HTML looks like this

<button class="button--normal-abc5436">Processing...</button>

In this way, the name of class is basically unique.
CSS modules deal with the class names in CSS, and use objects to save the corresponding relationship between the original class and the confused class. Through these simple processing, CSS modules achieve the following points:

  1. All styles are localized, which solves the problem of naming conflict and global pollution
  2. Class name generation rules can be configured flexibly to compress class names
  3. You only need to refer to the component’s JavaScript to complete all the JavaScript and CSS of the component
  4. It’s still CSS, and the cost of learning is almost zero

Style default local

Using CSS modules is equivalent to adding: local to each class name to realize style localization. If we want to switch to global mode, we can use: global package

.normal {
  color: green;
}
/*Equivalent to above*/
:local(.normal) {
  color: green;
}
/*Define global styles*/
:global(.btn) {
  color: red;
}
/*Define multiple global styles*/
:global {
  .link {
    color: green;
  }
  .box {
    color: yellow;
  }
}

Use compositions to combine styles

For style reuse, CSS modules only provides the only way to deal with composition.

/* components/Button.css */
. base {/ * all common styles * /}

.normal {
  composes: base;
  /*Normal other styles*/
}

In addition, you can combine styles in external files using compositions

/* settings.css */
.primary-color {
  color: #f40;
}

/* component/Button.css */
. base {/ * all common styles * /}

.primary {
  composes: base;
  composes: $primary-color from './settings.css'
}

For most projects, with compositions, the precompile processor is no longer needed. However, if you want to use it, because composes is not a standard CSS syntax, compilation will report an error. At this time, you can only use the preprocessing syntax for style reuse.

Class naming skills

The naming convention of CSS modules is extended from BEM. BEM divides style names into three levels

  1. Block: corresponding module name, such as dialog
  2. Element: the node name in the corresponding module confirm button
  3. Modifier: the status of the corresponding node, such as disabled and highlight

Such as dialog__ confirm-button–highlight。

The realization of CSS and JavaScript variable sharing

: export keyword can output variables from CSS to JavaScript

$primary-color: #f40;

:export {
  primaryColor: $primary-color;
}
// app.js
import style from 'config.scss'

console.log(style.primaryColor);

Tips for CSS modules

The following principles are recommended

  1. Instead of using selectors, use only the class name to define the style
  2. Instead of cascading multiple classes, use only one class to define all styles
  3. All styles are reused by composes
  4. No nesting

common problem
1. If you use the same name class in a style file?
Although it may be a random code after compilation, it still has the same name.
2. What if Id selector, pseudo class and tag selector are used in the style file?
These selectors are not converted and appear intact in the compiled CSS. In other words, CSS moodles will only convert the styles related to class names

CSS modules combined with the practice of historical legacy projects

1. How to cover the local style on the outside

Because the final class name cannot be predicted, the style cannot be overridden by a general selector. We can add the data role attribute to the key node of the component, and then override the style through the attribute selector.

// dialog.js
return (
  <div className={styles.root} data-role="dialog-root"></div>
);
// dialog.css
[data-role="dialog-root"] {
  // override style
}

2. How to coexist with global styles

Modifying the webpack configuration

module: {
  loaders: [{
    test: /\.scss$/,
    exclude: path.resolve(__dirname, 'src/views'),
    loader: 'style!css?modules&localIdentName=[name]__[local]!sass?sourceMap=true',
  }, {
    test: /\.scss$/,
    include: path.resolve(__dirname, 'src/styles'),
    loader: 'style!css!sass?sourceMap=true'
  }]
}
/* src/app.js */
import './styles/app.scss';
import Component from './view/Component'

/* src/views/Component.js */
import './Component.scss'

CSS modules combined with react

import styles from './dialog.css';

class Dialog extends Component {
  render() {
    return (
      <div className={styles.root}></div>
    )
  }
}

If you don’t want to type styles (* *) frequently, you can use react CSS modules

Communication between components

The parent component communicates with the child component

The parent component can pass the required information to the child component through props

The child component communicates with the parent component

There are two ways: 1. Using callback function. 2. Using user-defined event mechanism: this method is more general and can simplify the component API by adding event mechanism when designing components.

In react, you can use any method. It’s too complex to use custom events in simple scenarios. Generally, callback functions are used.

Cross level component communication

When sub components need to access information across levels, if props are passed to higher-level components as before, the code is not so elegant or even redundant. In react, we can also use context to realize the communication between cross level parent-child components.

class ListItem extends Component {
  static contextTypes = {
    color: PropTypes.string,
  }

  render () {
    return (
      <li style={{ background: this.context.color }}></li>
    )
  }
}
class List extends Component {
  static childContextTypes = {
    color: PropTypes.string,
  }

  getChildContext() {
    return {
      color: 'red'
    }
  }
  render() {

  }
}

React officials do not recommend using context extensively, because when the component structure is complex, it is difficult for us to know where context comes from. A better scenario using context is the real global information and will not change, such as interface theme, user information, etc. The general principle is that if we really need it, we suggest writing it as a high-level component.

Component communication without nested relationship

If there is no nesting relationship, it can only be considered through some mechanisms that can affect the overall situation. The custom event mechanism mentioned above is a good method.

In the process of handling events, we need to pay attention to that in the componentdidmount event, if the component is mounted, we will subscribe to the event; when the component is unloaded, we will unsubscribe the event in the componentwillunmount event.

For the scenario of react, EventEmitter only needs a single instance

import { EventEmitter } from 'events';

export default new EventEmitter();
import emitter from './events';

emitter.emit('ItenChange', entry)
class App extends Component {
  componentDidMount() {
    this.itemChange = emitter.on('ItemChange', (data) => {
      console.log(data)
    })
  }
  componentWillUnmount() {
    emitter.removeListener(this.itemChange)
  }
}

Generally speaking, if there is multi-level transfer or cross level transfer in the program, it is necessary to reexamine whether there is a more reasonable way. The pub / sub model may also lead to confusion of logical relations.

Cross level communication is often anti pattern, so we should try to avoid the design idea of only implementing through pubgsub. It is better to add strong dependencies and conventions to further comb the process. (e.g. Redux)

Abstraction between components

There are often scenarios where a class of functions needs to be shared by different components, and the topic of abstraction is involved. We focus on two types: mixin and higher-order components

Encapsulating mixin methods

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]
    }
  }
}

const BigMixin = {
  fly: () => {
    console.log('fly');
  }
}

const Big = function() {
  console.log('new big');
}

consg FlyBig = mixin(Big, BigMixin)

const flyBig = new FlyBig();
flyBig.fly(); // => 'fly'

For the generalized mixin method, it is to mount all the methods in the mixin object to the original object in the way of assignment, so as to realize the mixing of objects.

Seeing the above implementation, you may associate with the extend method in the underscore library, the assign method in the lodash library, or the assign method in ES6 Object.assign () method. The explanation on MDN is to copy the enumerable properties of any number of source objects to the target object, and then return the target object.

Using mixin in react

React provides mixin attributes when building components with createclass, such as purerendermixin, which is officially encapsulated

import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';

React.createClass({
  mixins: [PureRenderMixin],
  render() {}
})

Multiple mixins can also be added to the mixins array. If there is overlap between mixin methods, a reactclassinterface error will be reported in the console for common methods. For the life cycle method, the life cycle methods of each module are superimposed and executed in sequence.

Mixin does two things for components:

  1. Tools and methods. If you want to share some tool class methods, you can define them and use them directly in various components.
  2. Life cycle inheritance, props and state merging, mixin can also act on the result of getinitialstate to merge States, and props is also merged in this way.

ES6 classes and decorator

ES6 classes, which does not support mixin. Decorator syntax can implement mixin on class.

The core decorators library provides some practical decorators for developers, which implements the @ mixin we just want

import { getOwnPropertyDescriptors } from './private/utils';

const { defineProperty } = Object;

function handleClass(target, mixins) {
  if (!mixins.length) {
    // throw error;
  }

  for(let i = 0, l = mixins.length; i < l; i ++) {
    const descs = getOwnPropertyDescriptors(mixins[i])

    for (const key in descs) {
      if (!(key in target.prototype)) {
        defineProperty(target.prototype, key, descs[key])
      }
    }
  }
}

export default function mixin(...mixins) {
  if (typeof mixins[0] === 'function') {
    return handleClass(mixins[0], [])
  } else {
    return target => {
      return handleClass(target, mixins)
    }
  }
}

The principle is also very simple. It superimposes the method of each mixin object on the prototype of the target object to achieve the purpose of mixin. In this way, @ mixin can be used to stack multiple reuse modules.

const PureRender = {
  shouldComponentUpdate() {}
}

const Theme = {
  setTheme() {}
}

@mixin(PureRender, Theme)
class MyComponent extends Component {
  render() {}
}

The logic of mixin is very similar to the simple logic that was first implemented. Before, it directly assigned values to the prototype property of the object, but here we used getownpropertydescriptor and defineproperty. What’s the difference?

The good way to achieve this is to define property, which is the difference between definition and assignment. Definition is to define the existing definition, and assignment is to cover the existing definition. The former does not cover existing methods, but the latter does. In essence, it is very different from the official mixin method. In addition to defining the method level that cannot be overridden, it also needs to inherit the life cycle method and merge the state.

The decorator also works on methods. It can control the properties of methods, and it can also be used as the factory method of the decorator.

The problem of mixin

There are many problems with mixin, which has been officially abandoned and replaced by high-level components.

1. Package of damaged original components

We know that mixin methods will be mixed with methods, bringing new features to the original components. For example, there is a renderlist method in mixin, which brings us the ability to render lists, but it may also bring new states and props, which means that components have some “invisible” states that we need to maintain, but we are not clear when using them. In addition, methods in renderlist will call methods in components, but they are likely to be intercepted by other mixins, resulting in a lot of unknowns.

2. Naming conflicts of different mixins

3. Increase complexity

We design a component and introduce the mixin of popupmixin. In this way, we introduce the popupmixin life cycle method to the component. When we introduce hovermixin, more methods will be introduced. Of course, we can further abstract the tooltipmixin and integrate the two, but we find that they both have the componentdidupdate method. After a while, you will find that its logic is too complicated to understand.

When we write react components, the first consideration is often a single function, simple design and logic. When the function is added, you can continue to control the input and output of the component. If we constantly add new states because of complexity, components will become very difficult to maintain.

High order components

Higher order function is a basic concept in functional programming, which takes a function as input or outputs a function.
High order component is similar to high-order function. It takes react component as input and outputs a new react component.

High level component makes our code more reusable, logical and abstract. It can hijack render method and control props and state.

There are two ways to implement high-order components

  1. Property proxy: operate props through the wrapped react component
  2. Reverse inheritance: inherits from the wrapped react component

1. Attribute proxy

const MyContainer = (WrappedComponent) => {
  class extends Component {
    render() {
      return <WrappedComponent {...this.props} />
    }
  }
}

In this way, we can pass props through high-level components, which is called attribute proxy.
In this way, the component can be called as a parameter layer by layer, and the original component has the modification of higher-order component. The encapsulation of a single component is maintained while the ease of use is maintained. Of course, you can also use decorator to convert

@MyContainer
class MyComponent extends Component {
  render() {}
}

export default MyComponent

The above execution life cycle process is similar to stack calls:
didmount -> HOC didmount -> (HOCs didmount) -> (HOCs will unmount) -> HOC will unmount -> unmount
Functionally, high-level components can control components like mixin, including controlling props, using references through refs, abstracting state and wrapping wrapped component with other elements

1. Control props

We can read, add, edit or remove props passed in from wrappedcomponent, but we need to be careful to delete and edit important props. We should name props of high-level components as new as possible to avoid confusion.

For example, we need to add a new prop:

const MyContainer = (WrappedComponent) => {
  class extends Component {
    render() {
      const newProps = {
        text: newText,
      };
      return <WrappedComponent {...this.props} {...newProps} />;
    }
  }
}

2. Use reference through refs

const MyContainer = (WrappedComponent) => {
  class extends Component {
    proc(wrappedComponentInstance) {
      wrappedComponentInstance.method();
    }

    render() {
      const props = Object.assign({}, this.props, {
        ref: this.proc.bind(this),
      })
      return <WrappedComponent {...props} />
    }
  }
}

In this way, it can be easily used to read or increase the props of the instance, and call the method of the instance.
3. Abstract state
High level component can abstract the original component into display component and separate the internal state

const MyContainer = (WrappedComponent) => {
  class extends Component {
    constructor(props) {
      super(props)
      this.state = {
        name: ''
      }

      this.onNameChange = this.onNameChange.bind(this)
    }

    onNameChange(event) {
      this.setState({
        name: event.target.value
      })
    }

    render() {
      const newProps = {
        name: {
          value: this.state.name,
          onChange: this.onNameChange,
        }
      }
      return <WrappedComponent {...this.props} {...newProps}>
    }
  }
}

This effectively abstracts the same state operation.
4. Wrapping wrappedcomponent with other elements
This can be for style or layout

const MyContainer = (WrappedComponent) => {
  class extends Component {
    render() {
      return (
        <div style={{ display: 'block' }}>
          <WrappedComponent {...this.props} />
        </div>
      )
    }
  }
}

Reverse inheritance

const MyContainer = (WrappedComponent) => {
  class extends WrappedComponent{
    render() {
      return super.render()
    }
  }
}

The components returned by higher-order components inherit from wrappedcomponent. Because wrappedcomponent is inherited passively, all calls will be reversed, which is also the origin of this method.
Because it depends on the inheritance mechanism, the call order of hoc is the same as that of queue
didmount->HOC didmount->(HOCs didmount)->will unmount->HOC will unmount->(HOCs will unmount)

In the reverse inheritance method, a higher-order component can use wrappedcomponent reference, which means that it can use wrappedcomponent’s state, props, lifecycle, and render. But it can’t guarantee that the complete sub component tree will be parsed.

It has two characteristics

1. Render hijacking

High level components can control the rendering process of wrappedcomponent. In this process, it can be read from the output of any react element; it can be added or modified. Delete props, or read or modify react element tree, or conditionally display element tree, or wrap element tree with style control.

If the element tree includes the react component of function type, the child component of the component cannot be operated.

Conditional rendering example

const MyContainer = (WrappedComponent) => {
  class extends WrappedComponent {
    render() {
      if (this.props.loggedIn) {
        return super.render();
      } else {
        return null;
      }
    }
  }
}

Modify the output of render

const MyContainer = (WrappedComponent) => {
  class extends WrappedComponent {
    render() {
      const elementsTree = super.render();
      let newProps;
      if (elementsTree && elementsTree.type === 'input') {
        newProps = { value: 'may the force be with you' }
      }
      const props = Object.assign({}, elementsTree.props, newProps);
      const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.children);
      return newElementsTree;
    }
  }
}

2. Control state

High level components can read, modify or delete the state in the wrappedcomponent instance, or add the state if necessary. But doing so may make the internal state of the wrappedcomponent component a mess. Most high-level components should limit reading or adding States, especially the latter. You can rename states to prevent confusion

const MyContainer = (WrappedComponent) => {
  class extends WrappedComponent {
    render() {
      return (
        <div>
          <h2>HOC Debugger Component</h2>
          <p>Props</p><pre>{JSON.stringify(this.props, null, 2)}</pre>
          <p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre>
          {super.render()}
        </div>
      )
    }
  }
}

In this example, props and state of wrappedcomponent are shown to facilitate debugging.

Component naming

High level components lose their original diplayname, so we should name them

HOC.displayName = `HOC(${getDisplayName(WrappedComponent)})`

or

class HOC extends ... {
  static displayName = `HOC(${getDisplayName(WrappedComponent)})`
}
function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName ||
         WrappedComponent.name || 
         'Component'
}

Component parameters

function HOCFactoryFactory(...params) {
  return function HOCFactory(WrappedComponent) {
    return class HOC extends Component {
      render() {
        return <WrappedComponent {...this.props} />
      }
    }
  }
}

Development practice of modular components

We have mentioned many times that props is used to pass parameters when using react to develop components. In other words, the most commonly used encapsulation method is to configure components with parameters. As the scene changes, the form of components also changes. We must continue to increase props to cope with the change, which will lead to the flooding of props. In the expansion, we must ensure the downward compatibility of components, only increase but not decrease, so as to reduce the maintainability of components.

We can make use of the idea of high-level component, put forward the development mode of component combination, and effectively solve some problems of configuration.

1. Re separation of components

Selectinput, searchInput and list, three finer grained components, can be combined into rich components, and each component can be pure and puppet components.

2. Logic abstraction

The same interaction logic and business logic in components should also be abstracted

//Complete the interaction between searchInput and list
const searchDecorator = WrappedComponent => {
  class SearchDecorator extends Component {
    constructor(props) {
      super(props)

      this.handleSearch = this.handleSearch.bind(this)
    }

    handleSearch(keyword) {
      this.setState({
        data: this.props.data,
        keyword,
      })

      this.props.onSearch(keyword)
    }

    render() {
      const { data, keyword } = this.state;
      return (
        <WrappedComponent
          {...this.props}
          data={data}
          keyword={keyword}
          onSearch={this.handleSearch}
        />
      )
    }
  }

  return SearchDecorator;
}

//Complete list data request
const asyncSelectDecorator = WrappedComponent => {
  class AsyncSelectDecorator extends Component {
    componentDidMount() {
      const { url, params } = this.props;

      fetch(url, { params }).then(data => {
        this.setState({
          data
        })
      })
    }

    render() {
      return (
        <WrappedComponent
          {...this.props}
          data={this.state.data}
        />
      )
    }
  }

  return AsyncSelectDecorator;
}

Finally, we use composition to package high-level components layer by layer, which perfectly combines the page and logic

const FinalSelector = compose(asyncSelectDecorator, searchDecorator, selectedItemDecorator)(Selector)

Component performance optimization

From the rendering process of react, how to prevent unnecessary rendering is the most important problem to be solved.
To solve this problem, react official provides a convenient way to solve, that is purerender

Pure function

Pure function consists of three principles

1. Given the same input, it always returns the same output

2. The process has no side effects (we can’t change the external state)

3. No additional state dependencies

Pure function is also very convenient for method level testing and refactoring, which can make the program have good scalability and adaptability.

PureRender

Pure in purerender means that the component satisfies the condition of pure function, that is, the component is rendered by the same props and state to get the same result.

1. Purerender essence

In the early days, the government provided developers with a react addons pure render mixin plug-in. The principle is to re implement the shouldcomponentupdate life cycle method, so that the current incoming props and state are compared with the previous ones. If false is returned, the component will not execute the render method. (if depth comparison is made, it will also consume performance.)

In the purerender source code, only a shallow comparison is made between the old and new props. The following is the example code of shallowequal

function shallowEqual(obj, newObj) {
  if (obj === newObj) {
    return true;
  }

  const objKeys = Object.keys(obj);
  const newObjKeys = Object.keys(newObj);
  if (objKeys.length !== newObjKeysl.length) {
    return false;
  }

  return objKeys.every(key => {
    return newObj[key] === obj[key];
  })
}

3. Optimize purerender

If there are the following types of situations in props or state, it will trigger purerender to true in any case.

3.1 setting objects or arrays directly for props

The address of the reference will change

<Account style={{ color: 'black' }} />

Avoid this problem

const defaultStyle = {};
<Account style={{ this.props.style || defaultStyle }} />

3.2 set props method and bind to element through event

class MyInput extends Component {
  constructor(props) {
    super(props)

    this.handleChange = this.handleChange.bind(this)
  }

  handleChange(e) {
    this.props.update(e.target.value)
  }

  render() {
    return <input onChange={this.handleChange} />
  }

}

3.3 setting subcomponents

For react components with sub components set, when calling shouldcomponentupdate, it returns true.

class NameItem extends Component {
  render() {
    return (
      <Item>
        <span>Arcthur</span>
      </Item>
    )
  }
}
<Item 
  children={React.createElement('span', {}, 'Arcthur')}
/>

How to avoid duplicate rendering overlay? We set purerender in nameitem, that is, we refer to the parent to judge.

Immutable

When transferring data, immutable data can be used directly to further improve the rendering performance of components.

Objects in JavaScript are generally variable, because reference assignment is used. The new object simply refers to the original object. Changing the new object will affect the original object.

Using shallow copy or deep copy can avoid modification, but it also causes the waste of memory and CPU.

1. Once the immutable data is created, you can’t change the data any more. If you modify, add or delete the immutable object, a new immutable object will be returned. Immutable uses persistent data structure, that is, it uses old data to create new data. To keep the old data available and unchanged at the same time, immutable uses structure sharing in order to avoid the performance loss caused by deep copy of all nodes. That is, if a node in the object tree changes, only this node and its affected parent node are modified, Other nodes share.

Advantages of immutable

1. Reduce the complexity caused by variable.

Variable data couple the concepts of time and value, which makes it difficult for data to be traced back

function touchAndLog (touchFn) {
  let data = { key: '1' }
  touchFn(data);
  console.log(data.key)
}

If data is immutable, what results can be printed.

2. Save memory

Immutable uses shared structure to reuse memory as much as possible. Objects that are not referenced are garbage collected.

import { map } from 'immutable';

let a = Map({
  select: '1',
  filter: Map({ name: '2' }),
});

let b = a.set('select', 'people');

a === b
a.get('filter') === b.get('filter')// true

3. Undo / redo, copy / paste, and even time travel are easy to implement.

Because every time the data is different, as long as the data is stored in an array, it can be freely rolled back.

4. Concurrent security

Data is inherently immutable, and the commonly used concurrency lock on the back end is not needed. However, it is useless now, because JavaScript generally runs in a single thread.

5. Embrace functional programming

Immutable itself is a concept in functional programming. As long as the input is consistent, the output must be consistent.

Disadvantages of using immutable

Being easily confused with native objects is the biggest problem encountered in using immutale.
Here are some solutions

1. Use flowtype or typescript static type checking tools

2. Convention variable naming rules, such as immutable type object starts with $$

3. Use Immutable.fromJS instead of Immutable.Map or Immutable.List To create objects, which can avoid mixing immutable objects with native objects

Immutable.js

Two immutable objects can be compared by = =, which is the best way to compare memory addresses directly. But even if the values of the two objects are the same, it will return false.

Immutable provides Immutable.is For “value comparison”, immutable compares the hascode or valueof two objects. Because immutable uses trie data structure to store, as long as the hascode of two objects is equal, the value is the same. This algorithm avoids the depth traversal comparison, so the performance is very good.

Immutable and cursor

Cursors here are totally different from cursors in databases. Because immutable data is usually nested very deeply, in order to facilitate access to deep data, cursor provides a reference that can directly access the deep data

let data = Immutable.fromJS({ a: { b: { c: 1 } } });
let cursor = Cursor.from(data, ['a', 'b'], newData => {
  //Called when the cursor or its child cursor performs an update
  console.log(newData)
})

// 1
cursor.get('c');
cursor = cursor.update('c', x => x + 1)
// 2
cursor.get('c');

Immutable and purerender

Should component update is the most commonly used method to optimize the performance of react. Deep copy and deep comparison are very expensive choices. And make use of Immutable.js , = = = and is are efficient methods to judge whether the data has changed.

import { is } from 'immutable'

shouldComponentUpdate(nextProps, nextState) {
  const thisProps = this.props || {};
  const thisState = this.state || {};

  if (Object.keys(thisProps).length !== Object.keys(nextProps).length || Object.keys(thisState).length !== Object.keys(nextState).length)  {
    return true;
  }

  for (const key in nextProps) {
    if (nextProps.hasOwnProperty(key) && !is(thisProps[key], nextProps[key])) {
      return true;
    }
  }

  for (const key in nextState) {
    if (nextState.hasOwnProperty(key) && !is(thisState[key], nextState[key])) {
      return true;
    }
  }
}

Immutable and setstate

React suggests that this.state As immutable, so you need to make a deep copy before modifying

import _ from 'lodash';

class App extends Component {
  this.state = {
    data: { time: 0 }
  }

  handleAdd() {
    let data = _.cloneDeep(this.state.data);
    data.time = data.time + 1;
    this.setState({ data });
  }
}

After using immutable, the operation becomes very simple

import { Map } from 'immutable';

class App extends Component {
  this.state = {
    data: Map({ time: 0 })
  }

  handleAdd() {
    this.setState(({ data }) => {
      data: data.update('times', v => v + 1)
    })
  }
}

Immutable can greatly improve the performance of applications.

key

If each component is an array or iterator, there must be a unique key prop. It is used to identify the uniqueness of the current item. If you use index, it is equivalent to a random key, and the update will render whether there are the same items or not. If the keys are the same, react throws a warning and only the first key is rendered.

If there are two subcomponents that need rendering, you can use the plug-in createfragment package to solve the problem.

react-addons-perf

React addons perf Perf.start and Perf.stop The two APIs set the start and end states for analysis. It will count the time of each stage of each component rendering, and then print out a table.

reference resources

Deep into react technology stack

Recommended Today

Third party calls wechat payment interface

Step one: preparation 1. Wechat payment interface can only be called if the developer qualification has been authenticated on wechat open platform, so the first thing is to authenticate. It’s very simple, but wechat will charge 300 yuan for audit 2. Set payment directory Login wechat payment merchant platform( pay.weixin.qq . com) — > Product […]