React系列
React簡單模擬語法(一)
Jsx, 合成事件與Refs(二)
virtualdom diff算法實現分析(三)
從Mixin到HOC再到HOOKS(四)
createElement, ReactElement與Component部分源碼解析(五)
JSX的誕生
他是 JavaScrip 的一種擴展語法。愛掏網 - it200.com React 官方推薦使用這種語法來描述 UI 信息。愛掏網 - it200.comJSX 可能會讓你想起某種模板語言,但是它具有 JavaScrip 的全部能力
- JSX 執行更快,因為它在編譯為 JavaScript 代碼后進行了優化。愛掏網 - it200.com
- 它是類型安全的,在編譯過程中就能發現錯誤。愛掏網 - it200.com
- 使用 JSX 編寫模板更加簡單快速。愛掏網 - it200.com
編譯
本質上來講,JSX 只是為 React.createElement(component, props, ...children)
方法提供的語法糖
123456
"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();
- 停止回調函數執行并立即返回。愛掏網 - it200.com
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
方法.
解決方案
-
event.persist()
事件回調中調用
event.persist()
方法,這樣會在池中刪除合成事件,并且允許用戶代碼保留對事件的引用。愛掏網 - it200.com - 緩存屬性
我們可以將事件屬性存儲在事件函數并且傳遞給異步回調函數而不是直接在異步回調里訪問它們.
- 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 | . | || | +------------+ * | | | . +--------------+ * +-----|------+ . ^ +-----------+ * | . | |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 } }
訪問 Refs
const node = this.myRef.current;
- 如果用于一個普通HTMl元素時,
React.createRef()
將接收底層 DOM 元素作為它的current
屬性以創建ref
。愛掏網 - it200.com - 當
ref
屬性被用于一個自定義類組件時,ref
對象將接收該組件已掛載的實例作為它的current
。愛掏網 - it200.com - 你不能在函數式組件上使用 ref 屬性,因為它們沒有實例。愛掏網 - it200.com
回調Refs
不同于傳遞 createRef()
創建的 ref
屬性,你會傳遞一個函數。愛掏網 - it200.com這個函數接受 React 組件的實例或 HTML DOM 元素作為參數,以存儲它們并使它們能被其他地方訪問。愛掏網 - it200.com
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 ( ); } }
如果是組件間傳遞回調形式的 refs如下:
function CustomTextInput(props) { return ( ); } class Parent extends React.Component { render() { return (this.inputElement = el} /> ); } }
無狀態組件中使用
因為無狀態組件是不會被實例化的,但是我們可以用過一個變量訪問其中的組件或者dom元素組件的實例引用
function CustomTextInput(props) { let inputRef; return ( inputRef = node} /> ); }