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

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

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

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