React系列
React系列 --- 简单模拟语法(一)
React系列 --- Jsx, 合成事件与Refs(二)
React系列 --- virtualdom diff算法实现分析(三)
React系列 --- 从Mixin到HOC再到HOOKS(四)
React系列 --- createElement, ReactElement与Component部分源码解析(五)
React系列 --- 从使用React了解Css的各种使用方案(六)
React系列 --- 从零构建状态管理及Redux源码解析(七)
React系列 --- 扩展状态管理功能及Redux源码解析(八)
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
方法.
解决方案
-
event.persist()
事件回调中调用
event.persist()
方法,这样会在池中删除合成事件,并且允许用户代码保留对事件的引用。 -
缓存属性
我们可以将事件属性存储在事件函数并且传递给异步回调函数而不是直接在异步回调里访问它们.
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
-
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>
);
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。