react小分享

101 阅读10分钟

1. react基础

1.1 React是什么

  • React是一个用于构建用户界面的 JavaScript 库

1.2 React能干什么?

  • 可以通过组件化的方式构建 构建快速响应的大型Web应用程序

1.3 React如何干的?

1.3.1 声明式

  • 声明式 使用声明式的编写用户界面,代码可行方便调试

  • 声明式渲染和命令式渲染

    • 命令式渲染 命令我们的程序去做什么,程序就会跟着你的命令去一步一步执行
    • 声明式渲染 我们只需要告诉程序我们想要什么效果,其他的交给程序来做
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);

1.3.2 组件化

  • 组件化 把页面拆分为一个个组件,方便视图的拆分和复用,还可以做到高内聚和低耦合

1.3.3 一次学习,随处编写

  • 可以使用React开发Web、Android、IOS、VR和命令行程序
  • ReactNative 使用 React 来创建 Android 和 iOS 的原生应用
  • React 360是一个创建3D和VR用户交互的框架

1.4 React干的怎么样?

1.4.1 优点

  • 开发团队和社区强大
  • 一次学习,随处编写
  • API比较简洁

1.4.2 缺点

  • 没有官方系统解决方案,选型成本高
  • 过于灵活,不容易写出高质量的应用

1.5 其它扩展

  • JSX实现声明式
  • 虚拟DOM可以实现跨平台
  • React使用的设计模式
  • 自己React大型架构经验

2.为什么React会引入JSX?

  • 题目分析 方案选型,考察知识广度
  • 解题思路
    • 解释概念?
    • 想实现什么目的?
    • 有哪些可选方案,为什么这个方案最好
    • JSX的工作原理?

2.1 JSX是什么

  • jsx
  • JSX是一个JavaScript的语法扩展,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式
  • JSX其实是React.createElement的语法糖

2.2 React想实现什么目的?

  • 需要实现声明式
  • 代码结构需要非常清晰和简洁,可读性强
  • 结构、样式和事件等能够实现高内聚低耦合,方便重用和组合
  • 不想引入新的的概念和语法,只写JavaScript

2.3 为什么JSX最好

2.3.1 模板

  • Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据
  • 引入太多概念,比如Angular就引入了控制器、作用域、服务等概念
<button v-on:click="counter += 1">增加 1</button>

2.4 JSX工作原理

2.4.1 安装

npm install @babel/core @babel/plugin-syntax-jsx @babel/plugin-transform-react-jsx @babel/types --save

2.4.2 AST抽象语法树

  • 抽象语法树(Abstract Syntax Tree,AST)是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构

ast.jpg

2.4.3 babel工作流

ast-compiler-flow

2.4.5 新转换

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");
*/

3 实现虚拟DOM

  • React.createElement 函数所返回的就是一个虚拟DOM
  • 虚拟DOM就是一个描述真实DOM的纯JS对象

3.2.1 src\index.js

src\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);

3.2.2 src\react.js

import {createElement} from './ReactElement';
const React = {
    createElement,
};
export default React;

3.2.3 src\ReactSymbols.js

const symbolFor = Symbol.for;
export let  REACT_ELEMENT_TYPE = symbolFor('react.element');

3.2.4 ReactElement.js

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;
}

4. 函数组件和类组件的相同点和不同点?

  • 组件 & Props

  • 组合 vs 继承

  • 组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思

  • 题目分析 本题属于差异题

  • 解题思路

    • 相同点
    • 不同点

4.1 实现组件

4.1 实现函数组件

4.1.1 src\index.js

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);

4.2 实现类组件

4.2.1 src\index.js

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);

4.2.3 src\react.js

src\react.js

import {createElement} from './ReactElement';
+import {Component} from './ReactBaseClasses';
const React = {
    createElement,
+   Component
};
export default React;

4.3 相同点和不同点

4.3.1 相同点

  • 它们都可以接收属性并且返回React元素

4.3.2 不同点

  • 编程思想不同: 类组件需要创建实例,是基于面向对象的方式编程,而函数式组件不需要创建实例,接收输入,返回输出,是基于函数式编程的思路来编写的
  • 内存占用:类组件需要创建并保存实例,会占用一定内存,函数组件不需要创建实例,可以节约内存占用
  • 捕获特性:函数组件具有值捕获特性
  • 可测试性: 函数式组件更方便编写单元测试
  • 状态: 类组件有自己的实例,可以定义状态,而且可以修改状态更新组件,函数式组件以前没有状态,现在可以使用useState使用状态
  • 生命周期: 类组件有自己完整的生命周期,可以在生命周期内编写逻辑,函数组件以前没有生命周期,现在可以使用useEffect实现类似生命周期的功能
  • 逻辑复用: 类组件可以通过继承实现逻辑的复用,但官方推荐组件优于继承,函数组件可以通过自定义Hooks实现逻辑的复用
  • 跳过更新: 类组件可以通过shouldComponentUpdatePureComponent来跳过更新,而函数式组件可以使用React.memo来跳过更新
  • 发展前景: 未来函数式组件将会成为主流,因为它可以更好的屏蔽this问题、规范和复用逻辑、更好的适合时间分片和并发渲染
4.3.2.1 捕获特性
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')
);

结论: 所以看到这里,你似乎明白了为什么及时我们改变了num界面的num也改变了,然后打印的结果却没有改变。我们可以确保函数执行的瞬间的值是我们最初捕获到的,而不是我们后面改变的,当一些值的改变比如props的改变导致函数的重新执行也不会影响到我们上一次执行的结果。这就是 Dan 所说的函数式组件捕获了渲染所使用的值,并且我们还能进一步意识到:函数组件真正将数据和渲染紧紧的绑定到一起了。Hooks也给出了获取最新的props和state的方法,就是 useRef

解决上面例子的问题

如果我们确实想在setTimeout中输出最新改变过后的值怎么办呢?正如上面所言:我们可以用useRef

function FunctionComponent(){
  let [number,setNumber] = React.useState(0);
  const latestNum = useRef(0);
  useEffect(() => {
		latestNum.current = number;
	});
  let handleClick = ()=>{
      setTimeout(()=> {
        console.log(latestNum.current);
      }, 3000);
      setNumber(number+1);
      latestNum.current = number;
  }
  return (
      <div>
          <p>{number}</p>
          <button onClick={handleClick}>+</button>
      </div>
  );
}
4.3.2.2 跳过更新
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;
}

6. 请说一下React中有DOM-DIFF算法?

  • 在React17+中DOM-DIFF就是根据老的fiber树和最新的JSX对比生成新的fiber树的过程

6.1 React优化原则

  • 只对同级节点进行对比,如果DOM节点跨层级移动,则React不会复用
  • 不同类型的元素会产出不同的结构 ,会销毁老结构,创建新结构
  • 可以通过key标识移动的元素

6.2 单节点

  • 如果新的节点只有一个的话

018b18574d27f40bc86cae775cc8d79a

  • type不同

    <div>
    <h1>h1</h1>
    </div>
    /*************/
    <div>
    <h2>h2</h2>
    </div>
    
  • key不同

    <div>
    <h1 key="h1">h1</h1>
    </div>
    /*************/
    <div>
    <h2 key="h2">h2</h2>
    </div>
    
  • type和key都相同

    <div>
    <h1 key="h1">h1</h1>
    </div>
    /*************/
    <div>
    <h1 key="h1">h1-new</h1>
    </div>
    
  • key相同但是type不同,直接删除所有老节点

    <div>
    <h1 key="h1">h1</h1>
    <h2 key="h2">h2</h2>
    </div>
    /*************/
    <div>
    <p key="h1">p</p>
    </div>
    
  • key不同,删除当前老节点,接着对比下一个节点

    <div>
    <h1 key="h1">h1</h1>
    <h2 key="h2">h2</h2>
    </div>
    /*************/
    <div>
    <h2 key="h2">h2</h2>
    </div>
    

    6.3 多节点

  • 如果新的节点有多个节点的话

  • 节点有可能更新、删除、新增

  • 多节点的时候会经历二轮遍历

  • 第一轮遍历主要是处理节点的更新,更新包括属性和类型的更新

  • 第二轮遍历主要处理节点的新增、删除和移动

  • 移动时的原则是尽量少量的移动,如果必须有一个要动,新地位高的不动,新地位低的动

  • 一一对比,都可复用,只需更新

    <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>
    
  • 一一对比,key相同,type不同,删除老的,添新的

    <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>
    
  • key不同退出第一轮循环

    <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);

81b92f5eeab21a3f37fe7c3728ec13d4

7. 请说一下你对React合成事件的理解?

7.1 事件工作流

  • 事件捕获
  • 事件目标
  • 事件冒泡
  • 事件委托
  • 先绑定先执行

6f1dae04a2159507c0875343ca202169

7.2 事件差异

类型原生事件合成事件
命名方式全小写小驼峰命名
事件处理函数字符串函数对象
阻止默认行为返回falseevent.preventDefault()
const handleClick = (event)=>{event.preventDefault();}
// 原生事件
<a href="#" onclick="handleClick()">Button</a>

//合成事件
<a href="#" onClick={handleClick}>Button</a>

7.3 合成事件

  • React把事件委托到document对象上

  • 当真实DOM元素触发事件,先处理原生事件,然后会冒泡到 document 对象后,再处理 React 事件

  • React事件绑定的时刻是在reconciliation阶段,会在原生事件的绑定前执行

  • 目的和优势

    • 进行浏览器兼容,React 采用的是顶层事件代理机制,能够保证冒泡一致性
    • 事件对象可能会被频繁创建和回收,因此 React 引入事件池,在事件池中获取或释放事件对象(React17中被废弃)

7.3.1 React17以前

7.3.1.1 使用
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冒泡
 */
7.3.1.2 实现
<!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>

7.3.2 React17以后

7.3.2.1 使用
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原生冒泡
 */
7.3.2.2 实现
<!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>

7.4 事件系统变更

  • 更改事件委托

    • 首先第一个修改点就是更改了事件委托绑定节点,在16版本中,React都会把事件绑定到页面的document元素上,这在多个React版本共存的情况下就会虽然某个节点上的函数调用了event.stopPropagation(),但还是会导致另外一个React版本上绑定的事件没有被阻止触发,所以在17版本中会把事件绑定到render函数的节点上
  • 去除事件池

    • 17版本中移除了事件对象池,这是因为 React 在旧浏览器中重用了不同事件的事件对象,以提高性能,并将所有事件字段在它们之前设置为 null。在 React 16 及更早版本中,使用者必须调用event.persist() 才能正确的使用该事件,或者正确读取需要的属性

7.5 案例

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'));