React
是一个用于构建用户界面的 JavaScript 库Web
应用程序let root = document.getElementById("root");
//声明式
ReactDOM.render(<h1 onClick={() => console.log("hello")}>hello</h1>, root);
//命令式
let h1 = document.createElement("h1");
h1.innerHTML = "hello";
h1.addEventListener("click", () => console.log("hello"));
root.appendChild(h1);
JavaScript
的语法扩展,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式React.createElement
的语法糖<button v-on:click="counter += 1">增加 1</button>
npm install @babel/core @babel/plugin-syntax-jsx @babel/plugin-transform-react-jsx @babel/types --save
const babel = require("@babel/core");
const sourceCode = `<h1 id="title">hello</h1>`;
const result = babel.transform(sourceCode, {
plugins: [["@babel/plugin-transform-react-jsx", { runtime: "classic" }]],
});
console.log(result.code);
/**
React.createElement("h1", {
id: "title",
}, "hello");
*/
const babel = require("@babel/core");
const sourceCode = `<h1 id="title" key="title">hello</h1>`;
const result = babel.transform(sourceCode, {
plugins: [["@babel/plugin-transform-react-jsx", { runtime: "automatic" }]],
});
console.log(result.code);
/**
import { jsx as _jsx } from "react/jsx-runtime";
_jsx("h1", {
id: "title",
children: "hello"
}, "title");
*/
npm install @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/preset-env @babel/preset-react babel-loader html-webpack-plugin webpack webpack-cli webpack-dev-server --save-dev
npm install react@experimental react-dom@experimental --save
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development",
devtool: "source-map",
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].js",
publicPath: "/",
},
module: {
rules: [
{
test: /\.js?$/,
use: {
loader: "babel-loader",
options: {
presets: [["@babel/preset-env"], "@babel/preset-react"],
plugins: [
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
],
},
},
exclude: /node_modules/,
},
],
},
plugins: [new HtmlWebpackPlugin({ template: "./public/index.html" })],
};
{
"scripts": {
"start": "webpack serve"
}
}
React.createElement
函数所返回的就是一个虚拟 DOMsrc\index.js
import React from "./react";
let virtualDOM = (
<div id="A1" key="A1">
<div id="B1" key="B1">
B1
</div>
<div id="B2" key="B2">
B2
</div>
</div>
);
console.log(virtualDOM);
import { createElement } from "./ReactElement";
const React = {
createElement,
};
export default React;
const symbolFor = Symbol.for;
export let REACT_ELEMENT_TYPE = symbolFor("react.element");
src\ReactElement.js
//https://gitee.com/mirrors/react/blob/v17.0.1/packages/react/src/ReactElement.js#L348
import { REACT_ELEMENT_TYPE } from "./ReactSymbols";
const RESERVED_PROPS = {
key: true,
ref: true,
_store: true,
__self: true,
__source: true,
};
export function createElement(type, config, children) {
const props = {};
let key = null;
if (config != null) {
key = config.key;
}
for (let propName in config) {
if (!RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
const element = {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
props,
};
return element;
}
import React from './react';
let virtualDOM = (
<div id="A1" key="A1" style={style}>
<div id="B1" key="B1" style={style}>B1</div>
<div id="B2" key="B2" style={style}>B2</div>
</div>
)
+function FunctionComponent(){
+ return virtualDOM;
+}
+let functionVirtualDOM = <FunctionComponent/>;
console.log(functionVirtualDOM);
import React from './react';
let virtualDOM = (
<div id="A1" key="A1" style={style}>
<div id="B1" key="B1" style={style}>B1</div>
<div id="B2" key="B2" style={style}>B2</div>
</div>
)
+class ClassComponent extends React.Component{
+ render(){
+ return virtualDOM;
+ }
+}
+let functionVirtualDOM = <ClassComponent/>;
console.log(functionVirtualDOM);
export function Component(props) {
this.props = props;
}
Component.prototype.isReactComponent = {};
src\react.js
import {createElement} from './ReactElement';
+import {Component} from './ReactBaseClasses';
const React = {
createElement,
+ Component
};
export default React;
shouldComponentUpdate
和PureComponent
来跳过更新,而函数式组件可以使用React.memo
来跳过更新import React from "react";
import ReactDOM from "react-dom";
class ClassComponent extends React.Component {
state = { number: 0 };
handleClick = () => {
setTimeout(() => console.log(this.state.number), 3000); //1
this.setState({ number: this.state.number + 1 });
};
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={this.handleClick}>+</button>
</div>
);
}
}
function FunctionComponent() {
let [number, setNumber] = React.useState(0);
let handleClick = () => {
setTimeout(() => console.log(number), 3000);
setNumber(number + 1);
};
return (
<div>
<p>{number}</p>
<button onClick={handleClick}>+</button>
</div>
);
}
let virtualDOM = <FunctionComponent />;
ReactDOM.render(virtualDOM, document.getElementById("root"));
class PureComponent extends Component {
shouldComponentUpdate(newProps, nextState) {
return (
!shallowEqual(this.props, newProps) ||
!shallowEqual(this.state, nextState)
);
}
}
function shallowEqual(obj1, obj2) {
if (obj1 === obj2) {
return true;
}
if (
typeof obj1 != "object" ||
obj1 === null ||
typeof obj2 != "object" ||
obj2 === null
) {
return false;
}
let keys1 = Object.keys(obj1);
let keys2 = Object.keys(obj2);
if (keys1.length != keys2.length) {
return false;
}
for (let key of keys1) {
if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) {
return false;
}
}
return true;
}
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
state = { list: new Array(10000).fill(0) };
add = () => {
this.setState({ list: [...this.state.list, 1] });
};
render() {
return (
<ul>
<input />
<button onClick={this.add}>add</button>
{this.state.list.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
}
}
let root = document.getElementById("root");
//ReactDOM.render(<App/>,root);
ReactDOM.unstable_createRoot(root).render(<App />);
requestIdleCallback
里注册的任务<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<script>
function sleep(d) {
for (var t = Date.now(); Date.now() - t <= d; );
}
const works = [
() => {
console.log("第1个任务开始");
sleep(20); //sleep(20);
console.log("第1个任务结束");
},
() => {
console.log("第2个任务开始");
sleep(20); //sleep(20);
console.log("第2个任务结束");
},
() => {
console.log("第3个任务开始");
sleep(20); //sleep(20);
console.log("第3个任务结束");
},
];
requestIdleCallback(workLoop);
function workLoop(deadline) {
console.log("本帧剩余时间", parseInt(deadline.timeRemaining()));
while (deadline.timeRemaining() > 1 && works.length > 0) {
performUnitOfWork();
}
if (works.length > 0) {
console.log(
`只剩下${parseInt(
deadline.timeRemaining()
)}ms,时间片到了等待下次空闲时间的调度`
);
requestIdleCallback(workLoop);
}
}
function performUnitOfWork() {
works.shift()();
}
</script>
</body>
</html>
import React from "./react";
import ReactDOM from "./react-dom";
let style = { border: "3px solid red", margin: "5px" };
let virtualDOM = (
<div id="A1" key="A1" style={style}>
A1
</div>
);
let root = document.getElementById("root");
ReactDOM.render(virtualDOM, root);
let style = { border: "1px solid red", color: "red", margin: "5px" };
let A = {
type: "div",
key: "A",
props: {
style,
children: [
"A文本",
{ type: "div", key: "B1", props: { style, children: "B1文本" } },
{ type: "div", key: "B2", props: { style, children: "B2文本" } },
],
},
};
let style = {border:'1px solid red',color:'red',margin:'5px'};
let A = {
type: 'div',
key: 'A',
props: {
style,
children: [
'A文本',
{ type: 'div', key: 'B1', props: { style,children: 'B1文本' } },
{ type: 'div', key: 'B2', props: { style,children: 'B2文本' } }
]
}
}
+let workInProgress;
+const TAG_ROOT = 'TAG_ROOT';
+function workLoop() {
+ while (workInProgress) {
+ workInProgress = performUnitOfWork(workInProgress);
+ }
+}
+let rootFiber = {
+ tag: TAG_ROOT,
+ key: 'ROOT',
+ stateNode: document.getElementById('root'),
+ props: { children: [A] }
+}
+function performUnitOfWork(fiber) {
+ console.log(fiber.key);
+}
+workInProgress=rootFiber;
+workLoop();
let style = {border:'1px solid red',color:'red',margin:'5px'};
let A = {
type: 'div',
key: 'A',
props: {
style,
children: [
'A文本',
{ type: 'div', key: 'B1', props: { style,children: 'B1文本' } },
{ type: 'div', key: 'B2', props: { style,children: 'B2文本' } }
]
}
}
let workInProgress;
const TAG_ROOT = 'TAG_ROOT';
const TAG_TEXT = 'TAG_TEXT';
const TAG_HOST = 'TAG_HOST';
function workLoop() {
while (workInProgress) {
workInProgress = performUnitOfWork(workInProgress);
}
}
let rootFiber = {
tag: TAG_ROOT,
key: 'ROOT',
stateNode: document.getElementById('root'),
props: { children: [A] }
}
workInProgress=rootFiber;
workLoop();
function performUnitOfWork(fiber) {
+ beginWork(fiber);
+ if (fiber.child) {//如果子节点就返回第一个子节点
+ return fiber.child;
+ }
+ while (fiber) {//如果没有子节点说明当前节点已经完成了渲染工作
+ if (fiber.sibling) {//如果它有弟弟就返回弟弟
+ return fiber.sibling;
+ }
+ fiber = fiber.return;//如果没有弟弟让爸爸完成,然后找叔叔
+ }
}
+/**
+ * 根据当前的fiber和子JSX构建子fiber树
+ * @param {*} fiber
+ * @returns
+ */
+function beginWork(fiber) {
+ console.log('beginWork', fiber.key);
+ let nextChildren = fiber.props.children;
+ if(typeof nextChildren === 'string'){
+ nextChildren=null;
+ }
+ return reconcileChildren(fiber,nextChildren);
+}
+
+function reconcileChildren(returnFiber, nextChildren) {
+ let firstChild = null;
+ let previousNewFiber = null;
+ let newChildren=[];
+ if(Array.isArray(nextChildren)){
+ newChildren = nextChildren;
+ }else if(!!nextChildren){
+ newChildren=[nextChildren];
+ }
+ for (let newIdx = 0; newIdx < newChildren.length; newIdx++) {
+ let newFiber = createFiber(newChildren[newIdx]);
+ newFiber.return = returnFiber;
+ if (!previousNewFiber) {
+ firstChild = newFiber;
+ } else {
+ previousNewFiber.sibling = newFiber;
+ }
+ previousNewFiber = newFiber;
+ }
+ returnFiber.child = firstChild;
+ return firstChild;
+}
+function createFiber(element) {
+ if (typeof element === 'string') {
+ return { tag: TAG_TEXT, type: element.type, key: element, props: element };
+ } else {
+ return { tag: TAG_HOST, type: element.type, key: element.key, props: element.props };
+ }
+}
+import {setInitialProperties} from './utils';
let style = {border:'1px solid red',color:'red',margin:'5px'};
let A = {
type: 'div',
key: 'A',
props: {
style,
children: [
'A文本',
{ type: 'div', key: 'B1', props: { style,children: 'B1文本' } },
{ type: 'div', key: 'B2', props: { style,children: 'B2文本' } }
]
}
}
let workInProgress;
const TAG_ROOT = 'TAG_ROOT';
const TAG_TEXT = 'TAG_TEXT';
const TAG_HOST = 'TAG_HOST';
+const Placement = 'Placement';
function workLoop() {
while (workInProgress) {
workInProgress = performUnitOfWork(workInProgress);
}
}
let rootFiber = {
tag: TAG_ROOT,
key: 'ROOT',
stateNode: document.getElementById('root'),
props: { children: [A] }
}
workInProgress=rootFiber;
workLoop();
function performUnitOfWork(fiber) {
beginWork(fiber);
if (fiber.child) {//如果子节点就返回第一个子节点
return fiber.child;
}
while (fiber) {//如果没有子节点说明当前节点已经完成了渲染工作
+ completeUnitOfWork(fiber);//可以结束此fiber的渲染了
if (fiber.sibling) {//如果它有弟弟就返回弟弟
return fiber.sibling;
}
fiber = fiber.return;//如果没有弟弟让爸爸完成,然后找叔叔
}
}
+function completeUnitOfWork(workInProgress) {
+ console.log('completeUnitOfWork', workInProgress.key);
+ let stateNode;
+ switch (workInProgress.tag) {
+ case TAG_HOST:
+ stateNode=createStateNode(workInProgress);
+ setInitialProperties(stateNode, workInProgress.props);
+ break;
+ case TAG_TEXT:
+ createStateNode(workInProgress);
+ break;
+ }
+ makeEffectList(workInProgress);
+}
+function createStateNode(fiber){
+ if (fiber.tag === TAG_TEXT) {
+ let stateNode = document.createTextNode(fiber.props);
+ fiber.stateNode = stateNode;
+ } else if (fiber.tag === TAG_HOST) {
+ let stateNode = document.createElement(fiber.type);
+ if (typeof fiber.props.children === 'string') {
+ stateNode.appendChild(document.createTextNode(fiber.props.children));
+ }
+ fiber.stateNode = stateNode;
+ }
+ return fiber.stateNode;
+}
+function makeEffectList(completedWork){
+ const returnFiber = completedWork.return;
+ if (returnFiber) {
+ if (!returnFiber.firstEffect) {//父亲为空就指向儿子的子链表
+ returnFiber.firstEffect = completedWork.firstEffect;
+ }
+ if (completedWork.lastEffect) {//父亲非空就父亲老尾下一个指向儿子子链表头,父亲尾指出儿子子链表头
+ if (returnFiber.lastEffect) {
+ returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
+ }
+ returnFiber.lastEffect = completedWork.lastEffect;//父亲的尾指向自己的尾
+ }
+ if (completedWork.flags) {
+ if (returnFiber.lastEffect) {//如果父亲有尾,尾巴下一个指向自己
+ returnFiber.lastEffect.nextEffect = completedWork;
+ } else {//如果父亲没有尾,父亲的头毛都指向自己
+ returnFiber.firstEffect = completedWork;
+ }
+ returnFiber.lastEffect = completedWork;
+ }
+ }
}
/**
* 根据当前的fiber和子JSX构建子fiber树
* @param {*} fiber
* @returns
*/
function beginWork(fiber) {
console.log('beginWork', fiber.key);
let nextChildren = fiber.props.children;
if(typeof nextChildren === 'string'){
nextChildren=null;
}
return reconcileChildren(fiber,nextChildren);
}
function reconcileChildren(returnFiber, nextChildren) {
let firstChild = null;
let previousNewFiber = null;
let newChildren=[];
if(Array.isArray(nextChildren)){
newChildren = nextChildren;
}else if(!!nextChildren){
newChildren=[nextChildren];
}
for (let newIdx = 0; newIdx < newChildren.length; newIdx++) {
let newFiber = createFiber(newChildren[newIdx]);
newFiber.return = returnFiber;
if (!previousNewFiber) {
firstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
returnFiber.child = firstChild;
return firstChild;
}
function createFiber(element) {
if (typeof element === 'string') {
return { tag: TAG_TEXT, type: element.type, key: element, props: element };
} else {
return { tag: TAG_HOST, type: element.type, key: element.key, props: element.props };
}
}
import {setInitialProperties} from './utils';
let style = {border:'1px solid red',color:'red',margin:'5px'};
let A = {
type: 'div',
key: 'A',
props: {
style,
children: [
'A文本',
{ type: 'div', key: 'B1', props: { style,children: 'B1文本' } },
{ type: 'div', key: 'B2', props: { style,children: 'B2文本' } }
]
}
}
let workInProgress;
const TAG_ROOT = 'TAG_ROOT';
const TAG_TEXT = 'TAG_TEXT';
const TAG_HOST = 'TAG_HOST';
const Placement = 'Placement';
function workLoop() {
while (workInProgress) {
workInProgress = performUnitOfWork(workInProgress);
}
+ commitRoot(rootFiber);
}
+function commitRoot(rootFiber){
+ let currentEffect = rootFiber.firstEffect;
+ while(currentEffect){
+ let flags = currentEffect.flags;
+ switch (flags) {
+ case Placement:
+ commitPlacement(currentEffect);
+ break;
+ }
+ currentEffect=currentEffect.nextEffect;
+ }
+}
function commitPlacement(currentFiber) {
let parent = currentFiber.return.stateNode;
parent.appendChild(currentFiber.stateNode);
}
let rootFiber = {
tag: TAG_ROOT,
key: 'ROOT',
stateNode: document.getElementById('root'),
props: { children: [A] }
}
workInProgress=rootFiber;
workLoop();
function performUnitOfWork(fiber) {
beginWork(fiber);
if (fiber.child) {//如果子节点就返回第一个子节点
return fiber.child;
}
while (fiber) {//如果没有子节点说明当前节点已经完成了渲染工作
completeUnitOfWork(fiber);//可以结束此fiber的渲染了
if (fiber.sibling) {//如果它有弟弟就返回弟弟
return fiber.sibling;
}
fiber = fiber.return;//如果没有弟弟让爸爸完成,然后找叔叔
}
}
function completeUnitOfWork(workInProgress) {
console.log('completeUnitOfWork', workInProgress.key);
let stateNode;
switch (workInProgress.tag) {
case TAG_HOST:
stateNode=createStateNode(workInProgress);
setInitialProperties(stateNode, workInProgress.props);
break;
case TAG_TEXT:
createStateNode(workInProgress);
break;
}
makeEffectList(workInProgress);
}
function createStateNode(fiber){
if (fiber.tag === TAG_TEXT) {
let stateNode = document.createTextNode(fiber.props);
fiber.stateNode = stateNode;
} else if (fiber.tag === TAG_HOST) {
let stateNode = document.createElement(fiber.type);
if (typeof fiber.props.children === 'string') {
stateNode.appendChild(document.createTextNode(fiber.props.children));
}
fiber.stateNode = stateNode;
}
return fiber.stateNode;
}
function makeEffectList(completedWork){
const returnFiber = completedWork.return;
if (returnFiber) {
if (!returnFiber.firstEffect) {//父亲为空就指向儿子的子链表
returnFiber.firstEffect = completedWork.firstEffect;
}
if (completedWork.lastEffect) {//父亲非空就父亲老尾下一个指向儿子子链表头,父亲尾指出儿子子链表头
if (returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
}
returnFiber.lastEffect = completedWork.lastEffect;//父亲的尾指向自己的尾
}
if (completedWork.flags) {
if (returnFiber.lastEffect) {//如果父亲有尾,尾巴下一个指向自己
returnFiber.lastEffect.nextEffect = completedWork;
} else {//如果父亲没有尾,父亲的头毛都指向自己
returnFiber.firstEffect = completedWork;
}
returnFiber.lastEffect = completedWork;
}
}
}
/**
* 根据当前的fiber和子JSX构建子fiber树
* @param {*} fiber
* @returns
*/
function beginWork(fiber) {
console.log('beginWork', fiber.key);
let nextChildren = fiber.props.children;
if(typeof nextChildren === 'string'){
nextChildren=null;
}
return reconcileChildren(fiber,nextChildren);
}
function reconcileChildren(returnFiber, nextChildren) {
let firstChild = null;
let previousNewFiber = null;
let newChildren=[];
if(Array.isArray(nextChildren)){
newChildren = nextChildren;
}else if(!!nextChildren){
newChildren=[nextChildren];
}
for (let newIdx = 0; newIdx < newChildren.length; newIdx++) {
let newFiber = createFiber(newChildren[newIdx]);
newFiber.return = returnFiber;
newFiber.flags = Placement;
if (!previousNewFiber) {
firstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
returnFiber.child = firstChild;
return firstChild;
}
function createFiber(element) {
if (typeof element === 'string') {
return { tag: TAG_TEXT, type: element.type, key: element, props: element };
} else {
return { tag: TAG_HOST, type: element.type, key: element.key, props: element.props };
}
}
<div>
<h1>h1</h1>
</div>
/*************/
<div>
<h2>h2</h2>
</div>
<div>
<h1 key="h1">h1</h1>
</div>
/*************/
<div>
<h2 key="h2">h2</h2>
</div>
<div>
<h1 key="h1">h1</h1>
</div>
/*************/
<div>
<h1 key="h1">h1-new</h1>
</div>
<div>
<h1 key="h1">h1</h1>
<h2 key="h2">h2</h2>
</div>
/*************/
<div>
<p key="h1">p</p>
</div>
<div>
<h1 key="h1">h1</h1>
<h2 key="h2">h2</h2>
</div>
/*************/
<div>
<h2 key="h2">h2</h2>
</div>
移动时的原则是尽量少量的移动,如果必须有一个要动,新地位高的不动,新地位低的动
一一对比,都可复用,只需更新
<ul>
<li key="A">A</li>
<li key="B">B</li>
<li key="C">C</li>
<li key="D">D</li>
</ul>
/*************/
<ul>
<li key="A">A-new</li>
<li key="B">B-new</li>
<li key="C">C-new</li>
<li key="D">D-new</li>
</ul>
<ul>
<li key="A">A</li>
<li key="B">B</li>
<li key="C">C</li>
<li key="D">D</li>
</ul>
/*************/
<ul>
<div key="A">A-new</div>
<li key="B">B-new</li>
<li key="C">C-new</li>
<li key="D">D-new</li>
</ul>
<ul>
<li key="A">A</li>
<li key="B">B</li>
<li key="C">C</li>
<li key="D">D</li>
</ul>
/*************/
<ul>
<li key="A">A-new</li>
<li key="C">C-new</li>
<li key="D">D-new</li>
<li key="B">B-new</li>
</ul>
移动
import * as React from "react";
import * as ReactDOM from "react-dom";
let oldStyle = { border: "3px solid red", margin: "5px" };
let newStyle = { border: "3px solid green", margin: "5px" };
let root = document.getElementById("root");
let oldVDOM = (
<ul>
<li key="A" style={oldStyle}>
A
</li>
<li key="B" style={oldStyle}>
B
</li>
<li key="C" style={oldStyle}>
C
</li>
<li key="D" style={oldStyle}>
D
</li>
<li key="E" style={oldStyle}>
E
</li>
<li key="F" style={oldStyle}>
F
</li>
</ul>
);
ReactDOM.render(oldVDOM, root);
setTimeout(() => {
let newVDOM = (
<ul>
<li key="A" style={newStyle}>
A-new
</li>
<li key="C" style={newStyle}>
C-new
</li>
<li key="E" style={newStyle}>
E-new
</li>
<li key="B" style={newStyle}>
B-new
</li>
<li key="G" style={newStyle}>
G
</li>
</ul>
);
ReactDOM.render(newVDOM, root);
}, 1000);
类型 | 原生事件 | 合成事件 |
---|---|---|
命名方式 | 全小写 | 小驼峰命名 |
事件处理函数 | 字符串 | 函数对象 |
阻止默认行为 | 返回 false | event.preventDefault() |
const handleClick = (event)=>{event.preventDefault();}
// 原生事件
<a href="#" onclick="handleClick()">Button</a>
//合成事件
<a href="#" onClick={handleClick}>Button</a>
import * as React from "react";
import * as ReactDOM from "react-dom";
class App extends React.Component {
parentRef = React.createRef();
childRef = React.createRef();
componentDidMount() {
this.parentRef.current.addEventListener(
"click",
() => {
console.log("父元素原生捕获");
},
true
);
this.parentRef.current.addEventListener("click", () => {
console.log("父元素原生冒泡");
});
this.childRef.current.addEventListener(
"click",
() => {
console.log("子元素原生捕获");
},
true
);
this.childRef.current.addEventListener("click", () => {
console.log("子元素原生冒泡");
});
document.addEventListener(
"click",
() => {
console.log("document捕获");
},
true
);
document.addEventListener("click", () => {
console.log("document冒泡");
});
}
parentBubble = () => {
console.log("父元素React事件冒泡");
};
childBubble = () => {
console.log("子元素React事件冒泡");
};
parentCapture = () => {
console.log("父元素React事件捕获");
};
childCapture = () => {
console.log("子元素React事件捕获");
};
render() {
return (
<div
ref={this.parentRef}
onClick={this.parentBubble}
onClickCapture={this.parentCapture}
>
<p
ref={this.childRef}
onClick={this.childBubble}
onClickCapture={this.childCapture}
>
事件执行顺序
</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
/**
document捕获
父元素原生捕获
子元素原生捕获
子元素原生冒泡
父元素原生冒泡
父元素React事件捕获
子元素React事件捕获
子元素React事件冒泡
父元素React事件冒泡
document冒泡
*/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>event</title>
</head>
<body>
<div id="parent">
<p id="child">事件执行顺序</p>
</div>
<script>
document.addEventListener("click", dispatchEvent);
function dispatchEvent(event, isCapture) {
let paths = [];
let current = event.target;
while (current) {
paths.push(current);
current = current.parentNode;
}
for (let i = paths.length - 1; i >= 0; i--) {
let eventHandler = paths[i].onClickCapture;
eventHandler && eventHandler();
}
for (let i = 0; i < paths.length; i++) {
let eventHandler = paths[i].onClick;
eventHandler && eventHandler();
}
}
let parent = document.getElementById("parent");
let child = document.getElementById("child");
parent.addEventListener(
"click",
() => {
console.log("父元素原生捕获");
},
true
);
parent.addEventListener("click", () => {
console.log("父元素原生冒泡");
});
child.addEventListener(
"click",
() => {
console.log("子元素原生捕获");
},
true
);
child.addEventListener("click", () => {
console.log("子元素原生冒泡");
});
document.addEventListener(
"click",
() => {
console.log("document捕获");
},
true
);
document.addEventListener("click", () => {
console.log("document冒泡");
});
parent.onClick = () => {
console.log("父元素React事件冒泡");
};
parent.onClickCapture = () => {
console.log("父元素React事件捕获");
};
child.onClick = () => {
console.log("子元素React事件冒泡");
};
child.onClickCapture = () => {
console.log("子元素React事件捕获");
};
/*
父元素React事件捕获
子元素React事件捕获
父元素原生捕获
子元素原生捕获
子元素原生冒泡
父元素原生冒泡
子元素React事件冒泡
父元素React事件冒泡
*/
</script>
</body>
</html>
import * as React from "react";
import * as ReactDOM from "react-dom";
class App extends React.Component {
parentRef = React.createRef();
childRef = React.createRef();
componentDidMount() {
this.parentRef.current.addEventListener(
"click",
() => {
console.log("父元素原生捕获");
},
true
);
this.parentRef.current.addEventListener("click", () => {
console.log("父元素原生冒泡");
});
this.childRef.current.addEventListener(
"click",
() => {
console.log("子元素原生捕获");
},
true
);
this.childRef.current.addEventListener("click", () => {
console.log("子元素原生冒泡");
});
document.addEventListener(
"click",
() => {
console.log("document原生捕获");
},
true
);
document.addEventListener("click", () => {
console.log("document原生冒泡");
});
}
parentBubble = () => {
console.log("父元素React事件冒泡");
};
childBubble = () => {
console.log("子元素React事件冒泡");
};
parentCapture = () => {
console.log("父元素React事件捕获");
};
childCapture = () => {
console.log("子元素React事件捕获");
};
render() {
return (
<div
ref={this.parentRef}
onClick={this.parentBubble}
onClickCapture={this.parentCapture}
>
<p
ref={this.childRef}
onClick={this.childBubble}
onClickCapture={this.childCapture}
>
事件执行顺序
</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
/**
document原生捕获
父元素React事件捕获
子元素React事件捕获
父元素原生捕获
子元素原生捕获
子元素原生冒泡
父元素原生冒泡
子元素React事件冒泡
父元素React事件冒泡
document原生冒泡
*/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>event</title>
</head>
<body>
<div id="root">
<div id="parent">
<p id="child">事件执行顺序</p>
</div>
</div>
<script>
let root = document.getElementById("root");
let parent = document.getElementById("parent");
let child = document.getElementById("child");
root.addEventListener(
"click",
(event) => dispatchEvent(event, true),
true
);
root.addEventListener("click", (event) => dispatchEvent(event, false));
function dispatchEvent(event, isCapture) {
let paths = [];
let current = event.target;
while (current) {
paths.push(current);
current = current.parentNode;
}
if (isCapture) {
for (let i = paths.length - 1; i >= 0; i--) {
let eventHandler = paths[i].onClickCapture;
eventHandler && eventHandler();
}
} else {
for (let i = 0; i < paths.length; i++) {
let eventHandler = paths[i].onClick;
eventHandler && eventHandler();
}
}
}
parent.addEventListener(
"click",
() => {
console.log("父元素原生捕获");
},
true
);
parent.addEventListener("click", () => {
console.log("父元素原生冒泡");
});
child.addEventListener(
"click",
() => {
console.log("子元素原生捕获");
},
true
);
child.addEventListener("click", () => {
console.log("子元素原生冒泡");
});
document.addEventListener(
"click",
() => {
console.log("document原生捕获");
},
true
);
document.addEventListener("click", () => {
console.log("document原生冒泡");
});
parent.onClick = () => {
console.log("父元素React事件冒泡");
};
parent.onClickCapture = () => {
console.log("父元素React事件捕获");
};
child.onClick = () => {
console.log("子元素React事件冒泡");
};
child.onClickCapture = () => {
console.log("子元素React事件捕获");
};
</script>
</body>
</html>
event.stopPropagation()
,但还是会导致另外一个 React 版本上绑定的事件没有被阻止触发,所以在 17 版本中会把事件绑定到 render 函数的节点上event.persist()
才能正确的使用该事件,或者正确读取需要的属性React16
import * as React from "react";
import * as ReactDOM from "react-dom";
class Dialog extends React.Component {
state = { show: false };
componentDidMount() {
document.addEventListener("click", () => {
this.setState({ show: false });
});
}
handleButtonClick = (event) => {
//event.stopPropagation();
event.nativeEvent.stopImmediatePropagation();
this.setState({ show: true });
};
render() {
return (
<div>
<button onClick={this.handleButtonClick}>显示</button>
{this.state.show && (
<div
onClick={(event) => event.nativeEvent.stopImmediatePropagation()}
>
Modal
</div>
)}
</div>
);
}
}
ReactDOM.render(<Dialog />, document.getElementById("root"));
React17
import * as React from 'react';
import * as ReactDOM from 'react-dom';
class Dialog extends React.Component{
state = {show: false};
componentDidMount() {
document.addEventListener("click", () => {
this.setState({show: false});
});
}
handleButtonClick = (event) => {
+ event.stopPropagation();
- //event.nativeEvent.stopImmediatePropagation();
this.setState({show: true});
};
render() {
return (
<div>
<button onClick={this.handleButtonClick}>显示</button>
{this.state.show && (
+ <div onClick={(event) => event.stopPropagation()}>
Modal
</div>
)}
</div>
);
}
}
ReactDOM.render(<Dialog />, document.getElementById('root'));