关于javascript:React系列三-Jsx-合成事件与Refs

43次阅读

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

系列文章

React 系列(一)– 2013 起源 OSCON – React Architecture by vjeux
React 系列(二)– React 根本语法实现思路
React 系列(三)– Jsx, 合成事件与 Refs

JSX 的诞生

React 应用 JSX 来代替惯例的 JavaScript。

JSX 是一个看起来很像 XML 的 JavaScript 语法扩大。

咱们不须要肯定应用 JSX,但它有以下长处:

  • 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 对象来示意的话:

// 省略掉局部属性
{
  type: 'div'
  props: {
    className: 'num',
    index: 1,
    children: {
      type: 'span',
      props: {children: '123456'}
    },
  }
}

能够通过在线编译页面查看 codesandbox

所以整个过程大略如下
\105748fhcfjgijkjryctcf.png)

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

  • 除了一般页面还可能渲染到 canvas 或者原生 App(React Native 理解一下)
  • 前面的 diff 比拟须要用到

原生 DOM API 渲染流程

// 首先创立父标签
const parent = document.createElement('div')
parent.className = 'num'
parent.index = 1

// 创立子元素
const child = document.createElement('span')

// 创立文本节点
const text = document.createTextNode("")
text.nodeValue = '123456'

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

创立 JSX 对象

咱们依据下面的构造能够大略模拟出实现函数

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

然而有一个须要留神的点是像文本元素是不同构造的,所以须要特地辨别,思考到原生流程,咱们也要给一个对应构造

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

尝试调用

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

最终运行得出后果

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

整个 JSX 语法解析成 JSX 对象的实现是由 babel 实现的,有趣味自行理解

渲染实现

咱们曾经晓得原生渲染形式和 JSX 对象后果,剩下的就是枚举形式生成元素

function render(component, wrapper) {
  // 辨别标签
  const dom = component.type === 'TEXT' ? document.createTextNode("") : document.createElement(component.type)

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

  // 是否须要渲染子元素
  component.props.children.length && component.props.children.forEach(child => render(child, dom))

  // 最终挂载办法
  wrapper.appendChild(dom)
}

事件处理

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()
void persist()
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() 办法,这样会在池中删除合成事件,并且容许用户代码保留对事件的援用。

  1. 缓存属性

    咱们能够将事件属性存储在事件函数并且传递给异步回调函数而不是间接在异步回调里拜访它们.

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

合成事件注册

源码正文

/**
 * 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 创立一个事件对象, 下面提到的池形式复用就是为了解决高额内存调配的问题.

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