Skip to content

React虚拟DOM和DIFF算法 #13

@Wscats

Description

@Wscats

VDOM

VDOM,也叫虚拟 DOM,它是仅存于内存中的 DOM,因为还未展示到页面中,所以称为 VDOM

var vdom = document.createElement("div");

上面这一句就是最简单的虚拟 DOM

var vdom = document.createElement("div"); document.body.append(vdom);

上面这两句就是把虚拟 DOM 转化为 真实 DOM,其实就是把节点 append 到页面中

常见DOM操作

常见DOM操作,就三类:增、删、改。对应的DOM操作如下:

DOM操作 DOM方法
增加一个节点 appendChild
删除一个节点 removeChild
更改一个节点 replaceChild

以前我们写代码经常会拼接完模板,简单粗暴的用$(el).html(template)整块节点替换

这样做最大的问题在于性能,如果页面比较小,问题不大,但如果页面庞大,这样会出现卡顿,用户体验会很差,所以解决办法就是差量更新

差量更新

差量更新就是只对局面的 HTML 片段进行更新。比如你加了一个节点,那么我就只更新这个节点,我无需整个模板替换。这样做效率就会提高。但问题在于,不知道哪个节点更新了,哪个节点删除了,哪个节点替换了,所以我们需要对 DOM 建模

DOM 建模,简单点说就是用一个 JS 对象来表示 VDOM。

如果我们可以用一个JS对象来表示 VDOM,那么这个对象上多一个属性(增加节点),少一个属性(删除节点),或者属性值变了(更改节点),就很清醒了

DOM 也叫 DOM 树,是一个树形结构,DOM 树上有很多元素节点,要对 VDOM 进行建模,本质上就是对一个个元素节点进行建模,然后再把节点放回 DOM 树的指定位置

JSX建模

每个节点都是由以下三部分组成

  • type : 元素类型
  • props : 元素属性
  • children : 子元素集合
{type:"div",props: null, children:[ {type:"img",props:{"src":"avatar.png", "className":"profile"},children:[], {type:"h3",props: null, children:[{[user.firstName, user.lastName].join(' ')}], ]}

上面 VDOM 建模是用下面的 HTML 结构转出来的

var profile = <div> <img src="avatar.png" className="profile" /> <h3>{[user.firstName, user.lastName].join(' ')}</h3> </div>;

但这段代码并不是合法的 js 代码,它是一种被称为 jsx 的语法扩展,通过它我们就可以很方便的在 js 代码中书写 html 片段

本质上,jsx 是语法糖,上面这段代码会被 babel 转换成如下代码

pig("div", null, pig("img", { src: "avatar.png", className: "profile" }), pig("h3", null, [user.firstName, user.lastName].join(" ")))

而上面的这段被转化的代码是 将我们的 VDOM 配合pig(一般应该是createElement函数)转化为真实 DOM

注意,如果是自定义组件<App />会转化为pig(App, null),因为组件是class App extends React.Component {}这样定义的,所以App进入createElement函数里面就会变成是一个对象

这里我们可以把这个函数放进createElement()里面生成一个 VDOM 对象,然后用生成的 VDOM 对象,配合render()生成一个 DOM 插入页面,从而转变成真实 DOM 结构

createElement()

补充createElement()方法的源代码

function createElement(type, props, ...childrens) { return { // 父标签类型,比如dev,ul等 type: type, // 属性值 props: { ...props, }, // 子节点,比如li,字符串等 children: childrens.length <= 1 ? childrens[0] : childrens }; }

render()

补充render()方法的源代码

//=>DOM的动态创建 function render(jsxObj, container, callback) { let { type, props, children } = jsxObj; let newElement = document.createElement(type); //=>属性和子元素的处理 for (let attr in props) { if (!props.hasOwnProperty(attr)) break; switch (attr) { case 'className': newElement.setAttribute('class', props[attr]); break; case 'style': let styleOBJ = props['style']; for (let key in styleOBJ) { if (styleOBJ.hasOwnProperty(key)) { newElement['style'][key] = styleOBJ[key]; } } break; // =>CHILDREN case 'children': // 如果children放在props里面的话,这句才会有意义 // renderChildren() default: newElement.setAttribute(attr, props[attr]); } } renderChildren() function renderChildren() { let childrenAry = children; childrenAry = childrenAry instanceof Array ? childrenAry : (childrenAry ? [childrenAry] : []); childrenAry.forEach(item => { // 如果子节点直接是字符串,进入这个分支 if (typeof item === 'string') { // =>字符串:文本节点,直接增加到元素中 newElement.appendChild(document.createTextNode(item)); } else { // 如果是标签节点比如<span><img />这些都进入这个分支 // =>字符串:新的JSX元素,递归调用RENDER,只不过此时的容器是当前新创建的newElement render(item, newElement); } }); } console.log(newElement); container.appendChild(newElement); callback && callback(); }

transform-react-jsx

安装transform-react-jsx来实现 jsx 和 js 之间的转换

npm install babel-loader@8.0.0-beta.0 @babel/core @babel/preset-env webpack // 首先安装好 babel 环境 npm install --save-dev babel-plugin-transform-react-jsx //再安装 transform-react-jsx 插件

配置对应的 webpack 参数,如果这里把注释的那条 plugins 打开,那就不需要写另外配置 .babelrc 文件,这里默认会先使用 plugins 的配置的

const path = require('path'); const config = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }, module: { rules: [{ test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { // "plugins": ["transform-react-jsx"]// 如果需要配置参数注释这条,在 .babelrc上面配置 } } }] } }; module.exports = config;

配置 .babelrc 文件,如果需要对应的自定义的函数名,可以设置 pragma 的参数,不设置默认返回 React.createElement

{ "plugins": [ ["transform-react-jsx", { "pragma": "pig.yao" // default pragma is React.createElement }] ] }

源码

参考文章

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions