Jsx-合成事件与Refs二

19次阅读

共计 5542 个字符,预计需要花费 14 分钟才能阅读完成。

JSX 的诞生

他是 JavaScrip 的一种扩展语法。React 官方推荐使用这种语法来描述 UI 信息。JSX 可能会让你想起某种模板语言,但是它具有 JavaScrip 的全部能力

  • JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
  • 它是类型安全的,在编译过程中就能发现错误。
  • 使用 JSX 编写模板更加简单快速。

编译

本质上来讲,JSX 只是为 React.createElement(component, props, ...children) 方法提供的语法糖

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

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

具体效果可以在此体验

这就是为什么尽管你看不到里面使用过React, 但是如果你不引入模块的话 JSX 会报错.

JSX 原理

从上面的编译代码来看,JSX 最终包含的信息其实分别是: 元素标签, 元素属性, 子元素. 如果用 Javascript 对象来表示的话:

{
  tag: 'div',
  attrs: {className: 'num', index: 1},
  children: [
    {
      tag: 'span',
      arrts: null,
      children: null
    }
  ]
}

所以整个过程大概如下

至于为什么会有中间编译成 JS 对象那一步而不直接编译成 Dom 元素.

  • 除了普通页面还可能渲染到 canvas 或者原生 App(React Native 了解一下)
  • 后面的 diff 比较需要用到

事件处理

React 的事件是基于 SyntheticEvent 的实例实现模拟跨浏览器原生事件一样的接口, 包括 stopPropagation()preventDefault(),期望事件的行为跨浏览器是相同的. 甚至兼容直达 IE8. 每个 SyntheicEvent 对象都有如下属性:

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

基础科普

在 JavaScript 中,事件的触发实质上是要经过三个阶段: 事件捕获、目标对象本身的事件处理和事件冒泡.

  • stopPropagation(): 停止事件冒泡
  • preventDefault(): 阻止默认行为
  • return false: 实际上使用这个的时候会做三件事

    • event.preventDefault();
    • event.stopPropagation();
    • 停止回调函数执行并立即返回。

React 是怎么管理事件系统的?

出于性能原因.React 会通过池方式复用 SyntheicEvent 对象, 这意味着事件调用完成之后 event.target 上所有的属性都会失效. 意思就是当我们尝试异步方式调用 React 事件, 因为复用的原因, 在事件回调执行完之后 SyntheicEvent 对象将不再存在, 所以我们无法访问其属性.

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});
}

比较常见的例子就是 setState 方法.

解决方案

  1. event.persist()

    事件回调中调用 event.persist() 方法,这样会在池中删除合成事件,并且允许用户代码保留对事件的引用。

  2. 缓存属性

    我们可以将事件属性存储在事件函数并且传递给异步回调函数而不是直接在异步回调里访问它们.

    <button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
    
  3. Debouncing a synthetic event handler(不知道怎么翻译)

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

合成事件注册

源码注释

/**
 * 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 将事件传给 ReactEventListener 注册到 document, 然后分发到具体节点.EventPluginHub 负责事件的存储, 合成事件以及池方式的实现创建和销毁, 后面是各种类型的合成事件模拟, 交互通过 ReactEventEmitter 将原生的 DOM 事件转化成合成的事件, 触发将对应操作推入队列批量执行. 因为浏览器会为每个事件的每个 listener 创建一个事件对象, 上面提到的池方式复用就是为了解决高额内存分配的问题.

合成事件

  • Clipboard Events
  • Composition Events
  • Keyboard Events
  • Focus Events
  • Form Events
  • Mouse Events
  • Pointer Events
  • Selection Events
  • Touch Events
  • UI Events
  • Wheel Events
  • Media Events
  • Image Events
  • Animation Events
  • Transition Events
  • Other Events

event

其中事件都会被自动传入一个 event 对象, 是由 React 将浏览器原生的 event 对象封装一下对外提供统一的 API 和属性.

this

因为 React 里调用传入方法的时候并不是通过对象方法方式, 而是直接通过函数调用, 所以里面指向的 this 是 null 或者 undefined.

一般传入的时候需要手动用 bind 或者 箭头函数 显性绑定 this 指向

Refs & DOM

这是一种用于访问 render 方法中创建的 DOM 节点或 React 元素的方式. 一般用于

  • 处理表单, 媒体控制
  • 触发强制动画
  • 集成第三方 DOM 库

创建 Refs

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

访问 Refs

const node = this.myRef.current;
  • 如果用于一个普通 HTMl 元素时,React.createRef() 将接收底层 DOM 元素作为它的 current 属性以创建 ref
  • ref 属性被用于一个自定义类组件时,ref 对象将接收该组件已挂载的实例作为它的 current
  • 你不能在函数式组件上使用 ref 属性,因为它们没有实例。

回调 Refs

不同于传递 createRef() 创建的 ref 属性,你会传递一个函数。这个函数接受 React 组件的实例或 HTML DOM 元素作为参数,以存储它们并使它们能被其他地方访问。

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

    this.textInput = null;

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

    this.focusTextInput = () => {
      // 直接使用原生 API 使 text 输入框获得焦点
      if (this.textInput) this.textInput.focus();};
  }

  componentDidMount() {
    // 渲染后文本框自动获得焦点
    this.focusTextInput();}

  render() {
    // 使用 `ref` 的回调将 text 输入框的 DOM 节点存储到 React
    // 实例上(比如 this.textInput)return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

如果是组件间传递回调形式的 refs 如下:

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

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

无状态组件中使用

因为无状态组件是不会被实例化的, 但是我们可以用过一个变量访问其中的组件或者 dom 元素组件的实例引用

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

正文完
 0