1.请说一下你对React的理解? #

1.1 React是什么? #

1.2 React能干什么? #

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 一次学习,随处编写 #

1.4 React干的怎么样? #

1.4.1 优点 #

1.4.2 缺点 #

1.5 其它扩展 #

2.为什么React会引入JSX? #

2.1 JSX是什么 #

2.2 React想实现什么目的? #

2.3 为什么JSX最好 #

2.3.1 模板 #

<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抽象语法树 #

ast.jpg

2.4.3 babel工作流 #

ast-compiler-flow

2.4.4 旧转换 #

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

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. 请说一下你对Virtual DOM的理解? #

3.1 创建项目 #

3.1.1 安装 #

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

3.1.2 webpack.config.js #

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

3.1.3 package.json #

{
  "scripts": {
   "start": "webpack serve"
  },
}

3.2 实现虚拟DOM #

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

3.3 优缺点 #

3.3.1 优点 #

3.3.2 缺点 #

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

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.2 ReactBaseClasses.js #

export function Component(props) {
    this.props = props;
}

Component.prototype.isReactComponent = {};

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 相同点 #

4.3.2 不同点 #

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

5. 请说一下React中的渲染流程 #

5.1 设计理念 #

5.2 性能瓶颈 #

5.3 案例 #

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

5.4 屏幕刷新率 #

5.5 帧 #

lifeofframe

5.6 requestIdleCallback #

cooperativescheduling2

<!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>

5.7 React16+的渲染流程 #

5.7.1 index.js #

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

5.7.5 fiber #

5.7.5.1 Fiber是一个执行单元 #

fiberflow

5.7.5.2 Fiber是一种数据结构 #

fiberconstructor.jpg

5.8 实现渲染 #

8bed037dfa35a83bad852c6f3afa18fe

9b71de681e0eab51939d09f625b8ffe6

5.8.1 定义JSX #

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文本' } }
      ]
  }
}

5.8.2.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';
+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();

5.8.3.beginWork #

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

5.8.4. completeUnitOfWork #

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

5.8.5 commitRoot #

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

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

6.1 React优化原则 #

6.2 单节点 #

018b18574d27f40bc86cae775cc8d79a

6.3 多节点 #

移动

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 事件差异 #

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

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

7.3 合成事件 #

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 事件系统变更 #

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