React系列 --- Jsx, 合成事件與Refs(二)

React系列

React系列 --- 簡單模擬語法(一)
React系列 --- Jsx, 合成事件與Refs(二)
React系列 --- virtualdom diff算法實現分析(三)
React系列 --- 從Mixin到HOC再到HOOKS(四)
React系列 --- createElement, ReactElement與Component部分源碼解析(五)
React系列 --- 從使用React瞭解Css的各類使用方案(六)
React系列 --- 從零構建狀態管理及Redux源碼解析(七)
React系列 --- 擴展狀態管理功能及Redux源碼解析(八)html

JSX的誕生

他是 JavaScrip 的一種擴展語法。 React 官方推薦使用這種語法來描述 UI 信息。JSX 可能會讓你想起某種模板語言,可是它具備 JavaScrip 的所有能力node

  • JSX 執行更快,由於它在編譯爲 JavaScript 代碼後進行了優化。
  • 它是類型安全的,在編譯過程當中就能發現錯誤。
  • 使用 JSX 編寫模板更加簡單快速。

編譯

本質上來說,JSX 只是爲 React.createElement(component, props, ...children) 方法提供的語法糖react

<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會報錯.canvas

JSX原理

從上面的編譯代碼來看,JSX最終包含的信息其實分別是: 元素標籤, 元素屬性, 子元素.若是用Javascript對象來表示的話:segmentfault

{
  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中,事件的觸發實質上是要通過三個階段:事件捕獲、目標對象自己的事件處理和事件冒泡.babel

  • 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建立一個事件對象,上面提到的池方式複用就是爲了解決高額內存分配的問題.

合成事件

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>
  );
}
相關文章
相關標籤/搜索