系列文章

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 bubblesboolean cancelableDOMEventTarget currentTargetboolean defaultPreventednumber eventPhaseboolean isTrustedDOMEvent nativeEventvoid preventDefault()boolean isDefaultPrevented()void stopPropagation()boolean isPropagationStopped()void persist()DOMEventTarget targetnumber timeStampstring 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(不晓得怎么翻译)

    // Correctthis.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>  );}