React series - Jsx, composite events and Refs

The birth of JSX

React uses JSX instead of regular JavaScript.

JSX is a JavaScript syntax extension that looks much like XML.

We don't have to use JSX, but it has the following advantages:

  • JSX executes faster because it is optimized after compiling into JavaScript code.
  • It is type safe and errors can be found during compilation.
  • Writing templates using JSX is easier and faster.

compile

In essence, JSX is just for react The syntax provided by the createElement (component, props,... Children) method

<div className="num" index={1}>
  <span>123456</span>
</div>
"use strict";

React.createElement("div", {
  className: "num",
  index: 1
}, React.createElement("span", null, "123456"));

The specific effect is OK Experience here

This is why although you can't see that React has been used in it, JSX will report an error if you don't introduce the module

JSX principle

From the above compiled code, the final information contained in JSX is: element tag, element attribute and child element If represented by Javascript objects:

// Omit some attributes
{
  type: 'div'
  props: {
    className: 'num',
    index: 1,
    children: {
      type: 'span',
      props: { children: '123456' }
    },
  }
}

You can view it through the online compilation page codesandbox

So the whole process is as follows
\105748fhcfjgijkjryctcf.png)

As for why there is an intermediate step of compiling into JS objects instead of directly compiling into Dom elements

  • In addition to ordinary pages, they may also be rendered to canvas or native App(React Native)
  • The following diff comparison needs to be used

Native DOM API rendering process

// First create the parent label
const parent = document.createElement('div')
parent.className = 'num'
parent.index = 1

// Create child element
const child = document.createElement('span')

// Create text node
const text = document.createTextNode("")
text.nodeValue = '123456'

child.appendChild(text)
parent.appendChild(child)
document.body.appendChild(parent);

Create JSX object

We can roughly simulate the implementation function according to the above structure

function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children
    }
  }
}

However, one thing to note is that text elements have different structures, so they need to be distinguished. Considering the native process, we also need to give a corresponding structure

function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child =>
        typeof child === 'object'
          ? child
          : createTextElement(child)
      )
    }
  }
}

function createTextElement(text) {
  return {
    type: "TEXT",
    props: {
      nodeValue: text,
      children: []
    }
  }
}

Attempt to call

createElement(
  "div",
  { className: "num", index: 1 },
  createElement("span", null, "123456")
)

Final operation results

{
  type: 'div',
  props: {
    className: 'num',
    index: 1,
    children: [{
      type: 'span',
      props: [{
        children: [{
          type: 'TEXT',
          props: [{
            nodeValue: '123456'
          }]
        }]
      }]
    }],
  }
}

The implementation of parsing the whole JSX syntax into JSX objects is implemented by babel. You are interested in understanding it yourself

Rendering implementation

We already know the native rendering method and JSX object results, and the rest is to generate elements by enumeration

function render(component, wrapper) {
  // Distinguishing labels
  const dom = component.type === 'TEXT' ? document.createTextNode("") : document.createElement(component.type)

  // Traversal props
  Object.keys(component.props).forEach(key => {
    if (key !== 'children') {
      dom[key] = component.props[key]
    }
  })

  // Do you want to render child elements
  component.props.children.length && component.props.children.forEach(child => render(child, dom))

  // Final mount method
  wrapper.appendChild(dom)
}

event processing

React events are based on SyntheticEvent The instance implements the same interface as simulating cross browser native events, including stopPropagation() and preventDefault(), and expects the behavior of events to be the same across browsers Even compatible with IE8 Each SyntheicEvent object has the following properties:

boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
void persist()
DOMEventTarget target
number timeStamp
string type

Basic science popularization

In JavaScript, event triggering essentially goes through three stages: event capture, event processing of the target object itself and event bubbling

  • stopPropagation(): stop event bubbling
  • preventDefault(): block default behavior
  • return false: in fact, you can do three things when using this

    • event.preventDefault();
    • event.stopPropagation();
    • Stops the callback function execution and returns immediately.

How does React manage the event system?

For performance reasons React reuses the SyntheicEvent object through the pool, which means that after the event call is completed, event All attributes on target will be invalidated This means that when we try to call the react event asynchronously, because of reuse, the SyntheicEvent object will no longer exist after the event callback is executed, so we cannot access its properties

function onClick(event) {
  console.log(event); // => nullified object.
  console.log(event.type); // => "click"
  const eventType = event.type; // => "click"

  setTimeout(function() {
    console.log(event.type); // => null
    console.log(eventType); // => "click"
  }, 0);

  // Won't work. this.state.clickEvent will only contain null values.
  this.setState({clickEvent: event});

  // You can still export event properties.
  this.setState({eventType: event.type});
}

A common example is the setState method

Solution

  1. event.persist()

    Calling event. in event callback Persist () method, which removes the composite event from the pool and allows user code to retain references to the event.

  1. Cache properties

    We can store event properties in event functions and pass them to asynchronous callbacks instead of accessing them directly in asynchronous callbacks

    <button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
  1. Debouncing a synthetic event handler

    // Correct
    this.setState((prevState, props) => ({
      counter: prevState.counter + props.increment
    }));

Composite event registration

Source code comments

/**
 * Summary of `ReactBrowserEventEmitter` event handling:
 *
 *  - Top-level delegation is used to trap most native browser events. This
 *    may only occur in the main thread and is the responsibility of
 *    ReactDOMEventListener, which is injected and can therefore support
 *    pluggable event sources. This is the only work that occurs in the main
 *    thread.
 *
 *  - We normalize and de-duplicate events to account for browser quirks. This
 *    may be done in the worker thread.
 *
 *  - Forward these native events (with the associated top-level type used to
 *    trap it) to `EventPluginHub`, which in turn will ask plugins if they want
 *    to extract any synthetic events.
 *
 *  - The `EventPluginHub` will then process each event by annotating them with
 *    "dispatches", a sequence of listeners and IDs that care about that event.
 *
 *  - The `EventPluginHub` then dispatches the events.
 *
 * Overview of React and the event system:
 *
 * +------------+    .
 * |    DOM     |    .
 * +------------+    .
 *       |           .
 *       v           .
 * +------------+    .
 * | ReactEvent |    .
 * |  Listener  |    .
 * +------------+    .                         +-----------+
 *       |           .               +--------+|SimpleEvent|
 *       |           .               |         |Plugin     |
 * +-----|------+    .               v         +-----------+
 * |     |      |    .    +--------------+                    +------------+
 * |     +-----------.--->|EventPluginHub|                    |    Event   |
 * |            |    .    |              |     +-----------+  | Propagators|
 * | ReactEvent |    .    |              |     |TapEvent   |  |------------|
 * |  Emitter   |    .    |              |<---+|Plugin     |  |other plugin|
 * |            |    .    |              |     +-----------+  |  utilities |
 * |     +-----------.--->|              |                    +------------+
 * |     |      |    .    +--------------+
 * +-----|------+    .                ^        +-----------+
 *       |           .                |        |Enter/Leave|
 *       +           .                +-------+|Plugin     |
 * +-------------+   .                         +-----------+
 * | application |   .
 * |-------------|   .
 * |             |   .
 * |             |   .
 * +-------------+   .
 *                   .
 *    React Core     .  General Purpose Event Plugin System
 */

DOM passes the event to the ReactEventListener and registers it with the document

Then distribute to specific nodes EventPluginHub is responsible for the storage of events, the creation and destruction of synthetic events and the implementation of pool mode

The following is the simulation of various types of synthetic events. The interaction converts the native DOM events into synthetic events through ReactEventEmitter, triggering the corresponding operations to be pushed into the queue for batch execution Because the browser will create an event object for each listener of each event, the above-mentioned pool reuse is to solve the problem of high memory allocation

event

Events will be automatically passed into an event object. React encapsulates the browser's native event object and provides unified API and properties

this

Because the incoming method in React is called not through object methods, but directly through function calls, the this pointed to in React is null or undefined

In general, you need to explicitly bind this point with bind or arrow function manually

Refs & DOM

This is a way to access DOM nodes or React elements created in the render method Generally used

  • Processing forms, media control
  • Trigger forced animation
  • Integrating third-party DOM Libraries

Create Refs

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

Access Refs

const node = this.myRef.current;
  • If it is used for an ordinary HTMl element, react Createref () will receive the underlying DOM element as its current attribute to create a ref.
  • When the ref attribute is used for a custom class component, the ref object will receive the mounted instance of the component as its current.
  • You cannot use the ref attribute on functional components because they have no instances.

Callback Refs

Instead of passing the ref attribute created by createRef(), you pass a function. This function takes instances of React components or HTML DOM elements as parameters to store them and make them accessible elsewhere.

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);

    this.textInput = null;

    this.setTextInputRef = element => {
      this.textInput = element;
    };

    this.focusTextInput = () => {
      // Use the native API directly to focus the text input box
      if (this.textInput) this.textInput.focus();
    };
  }

  componentDidMount() {
    // After rendering, the text box automatically gets focus
    this.focusTextInput();
  }

  render() {
    // Use the callback of 'ref' to store the DOM node of the text input box in React
    // Instance (such as this.textInput)
    return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

If refs in the form of callback is passed between components, it is as follows:

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

Used in stateless components

Because stateless components will not be instantiated, but we can use a variable to access the instance reference of the component or dom element component

function CustomTextInput(props) {
  let inputRef;
  return (
    <div>
      <input ref={(node) => inputRef = node} />
    </div>
  );
}

Keywords: Javascript Front-end React

Added by lihman on Wed, 05 Jan 2022 02:42:59 +0200