React源码学习:Virtual DOM 与 diff 算法

p8-p14: virtual dom/diff
更新于: 2022-05-19 16:03:25

JSX 到底是什么

  • 长得像HTML,实际上是 js 代码
  • 是一种语法糖
  • 所有节点会被编译成 createElment 的方法的调用

DOM 操作有什么问题

  • 用 js 直接操作 dom 性能,远低于其它 js 对象
  • 所以有 vdom 的出现,先操作 vdom,等改变确定之后,再用 vdom 告诉真实的 dom 哪些需要变化,从而提升性能

什么是 VirtualDOM

  • 他的出现是为了提升 js 操作 dom 的效率
  • 用 js 对象表示真实 dom 的信息
-Virtual DOM

利用 babel 将 jsx的解析器换成 TinyReact

  • 在代码中添加注释:/* @jsx TinyReact.createElement */
用注释的方式,将React换成TinyReact
  • .babelrc 配置中添加 progma: “TinyReact.createElement” (具体自行搜索) -核心是这一句 pragma: 'TinyReact.createElement'
{
  test: /\.js$/,
  exclude: /node_modules/,
  use: {
    loader: 'babel-loader',
    options: {
      presets: [
        "@babel/preset-env",
        [
          '@babel/preset-react',
          {
            pragma: 'TinyReact.createElement',
          },
        ],
      ],
    },
  },
}
/* @jsx TinyReact.createElement */
<h1 model={{ role:'header' }}>
  it works
  <img title="logo" src="https://js.work/logo.png" />
</h1>
"use strict";

/* @jsx TinyReact.createElement */
TinyReact.createElement("h1", {
  model: {
    role: 'header'
  }
}, "it works", TinyReact.createElement("img", {
  title: "logo",
  src: "https://js.work/logo.png"
}));

实现一个 createElement 方法<另外要做的3件事情>

createElement 原理演示
  • 需要改进的点:文本节点
    • 文本本节点(目前jsx是直接以字符串输出),需要处理成 props:{ textContent: ‘text’ } 这种形式就行
  • 看 text 当中为: true/false 值,处理一下
    • true: 显示
    • false/null: 直接从节点中T出
  • children 添加到 props中
/**
 * 由JSX,创建 vdom 对象
 * @param type
 * @param props
 * @param children
 * @returns {{children: *[], type, props}}
 */

function createElement(type, props, ...children) {
  return {
    type,
    props,
    children
  };
}

export default createElement;
最终版本

实操代码如下

  • 原理代码
  • 处理上述3个问题后的代码(实测试只有2个问题)
    • 处理文本节点的问题 // 将 ['HelloReact'] 这种 转化为{type:"text", props:{textContent:child}}
    • 处理不中出现的 true/false值情况,实测,只要filter false 就可以了
  • 最终处理的 vdom 结果
// 这一行是必须的
import TinyReact from './tiny-react';

const vdom = (
  <div className="container">
    <h1>你好 Tiny React- </h1>
    <h2 data-test="mtest">(编码必杀技)</h2>
    <div>
      嵌套1 <div>嵌套 1.1</div>
    </div>
    <h3>(观察:这个将会被改变)</h3>
    {2 == 1 && <div>如果2和1相等渲染当前内容</div>}
    {2 == 2 && <div>2</div>}
    <span>这是一段内容</span>
    <button onClick={() => alert('你好')}>点去我</button>
    <h3>这个将会被删除</h3>
    2, 3
    <input type="text" value="13" />
  </div>
);

console.log('vdom: ', vdom);
export default function createElement(type, props, ...children) {
  //问题1: 将 ['HelloReact'] 这种 转化为{type:"text", props:{textContent:child}}
  let newChildren = children.map((child) => {
    if (typeof child === 'string') {
      return createElement('text', { textContent: child });
    }
    return child;
  });

  // 问题2: 去掉 false 值
  newChildren = newChildren.filter(Boolean);

  // 问题3: 让 props 上具有 children 属性
  const newProps = Object.assign({ children: newChildren }, props);

  return {
    type,
    props: newProps,
    children: newChildren,
  };
}

将 vdom 转化为 真实 dom: render

  • 先处理 type,添加到页面
  • 处理 props,添加属性
    • 普通属性 setAttribute
    • disabled/value 等 props,动态属性
    • hooks:className/for 等属性
    • 整件属性: onClick 这种: propName.slice(0,2) === ‘on’
    • children 这种情况:因为他是子元素,不是属性
  • 处理 children

参考