Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React虚拟DOM和DIFF算法 #13

Open
Wscats opened this issue Mar 29, 2018 · 0 comments
Open

React虚拟DOM和DIFF算法 #13

Wscats opened this issue Mar 29, 2018 · 0 comments

Comments

@Wscats
Copy link
Owner

Wscats commented Mar 29, 2018

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 [email protected] @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
    }]
  ]
}

源码

参考文章

@Wscats Wscats changed the title React虚拟DOM和diff算法 React虚拟DOM和DIFF算法 Dec 20, 2018
@Wscats Wscats self-assigned this Jan 13, 2019
@Wscats Wscats removed their assignment Jan 22, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant