zoukankan      html  css  js  c++  java
  • react系列一,react虚拟dom如何转成真实的dom

    react,想必作为前端开发一定不陌生,组件化以及虚拟dom使得react成为最受欢迎额前端框架之一。我们知道react是基于虚拟dom的,但是什么是虚拟dom呢,其实就是一组js对象,那么我们今天就来认识什么是虚拟dom,以及如何转成真实的dom结构,完整的 简易版react  在个人github,实现了diff算法,组件渲染,组件更新,钩子函数。

    一.认识虚拟dom

    首先我们看如下代码

    const title = <h1 className="title">Hello, world!</h1>;
    

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

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

    我们下载插件 babel-plugin-transform-react-jsx,并且配置.babelrc文件

    {
        "presets": ["env"],
        "plugins": [
            ["transform-react-jsx", {
                "pragma": "React.createElement"//大部分框架喜欢改成h
            }]
        ]
    }

    于是页面中的jsx就会被babel转成如下的结构

    const title = React.createElement(
        'h1',
        { className: 'title' },
        'Hello, world!'
    );
    

    可以看出babel已经把一个dom元素分解成标签名称h1,属性集合对象,以及内部子节点(这里是hello world文本节点),我们首先修改这个方法,为了转成我们需要的结构

    function createElement( tag, attrs, ...children ) {
        return {
            tag,
            attrs,
            children
        }
    }
    // 将上文定义的createElement方法放到对象React中
    const React = {
        createElement,
    }
    

    函数的参数...children使用了ES6的rest参数,它的作用是将后面child1,child2等参数合并成一个数组children。

    现在我们来试试调用它,一下结构都是babel自动调用React.createElement给我们转成的,当然你也可以自己写方法将真实的dom转为js对象

    const element = (
        <div>
            hello<span>world!</span>
        </div>
    );
    console.log( element );
    

      

    二.将上列的虚拟dom结构转成真实的dom

    1.如果遇到文本节点则直接返回新建的文本节点

    //处理文本节点
        if( typeof vnode === 'string'){
            const textNode = document.createTextNode( vnode )
            return textNode;
        }
    

    2.处理普通的元素

    //普通的dom
        const dom = document.createElement( vnode.tag );
        if( vnode.attrs ){
            Object.keys( vnode.attrs ).forEach( key => {
                const value = vnode.attrs[ key ];
                setAttribute( dom, key, value );    // 设置属性
            } );
        }
        vnode.children.forEach( child => render( child, dom ) );    // 递归渲染子节点
        return dom ;    // 返回虚拟dom为真正的DOM
    

    3.遇到普通元素的属性,需要这是属性节点,但是分为两种,一种是普通的属性,比如className,另一种是方法绑定,比如是onClick

    function setAttribute( dom, name, value ) {
        // 如果属性名是className,则改回class
        if ( name === 'className' ) name = 'class';
    
        // 如果属性名是onXXX,则是一个事件监听方法
        if ( /onw+/.test( name ) ) {
            name = name.toLowerCase();
            dom[ name ] = value || '';
        // 如果属性名是style,则更新style对象
        } else if ( name === 'style' ) {
            if ( !value || typeof value === 'string' ) {
                dom.style.cssText = value || '';
            } else if ( value && typeof value === 'object' ) {
                for ( let name in value ) {
                    // 可以通过style={  20 }这种形式来设置样式,可以省略掉单位px
                    dom.style[ name ] = typeof value[ name ] === 'number' ? value[ name ] + 'px' : value[ name ];
                }
            }
        // 普通属性则直接更新属性
        } else {
            if ( name in dom ) {
                dom[ name ] = value || '';
            }
            if ( value ) {
                dom.setAttribute( name, value );
            } else {
                dom.removeAttribute( name, value );
            }
        }
    }

    三.查看完整的代码

    function render ( vnode, container ){
        return container.appendChild( _render( vnode ) );
    }
    function _render( vnode ){
        if ( typeof vnode === 'number' ) {
            vnode = String( vnode );
        }
        //处理文本节点
        if( typeof vnode === 'string'){
            const textNode = document.createTextNode( vnode )
            return textNode;
        }
        //处理组件
        if ( typeof vnode.tag === 'function' ) {
            const component = createComponent( vnode.tag, vnode.attrs );
            setComponentProps( component, vnode.attrs );
            return component.base;
        }
        //普通的dom
        const dom = document.createElement( vnode.tag );
        if( vnode.attrs ){
            Object.keys( vnode.attrs ).forEach( key => {
                const value = vnode.attrs[ key ];
                setAttribute( dom, key, value );    // 设置属性
            } );
        }
        vnode.children.forEach( child => render( child, dom ) );    // 递归渲染子节点
        return dom ;    // 返回虚拟dom为真正的DOM
    }
    //实现dom挂载到页面某个元素
    const ReactDOM = {
        render: ( vnode, container ) => {
            container.innerHTML = '';
            return render( vnode, container );
        }
    }

    现在我们已经实现将虚拟dom转为真实的dom,已经绑定属性,我们现在来像react一样调用这个方法

    const element = (
        <div>
            hello<span>world!</span>
        </div>
    );
    
    ReactDOM.render(
        element,
        document.getElementById( 'main' )
    );

    现在就实现往页面中元素id为main的元素上挂载了该element。

  • 相关阅读:
    2.12 使用@DataProvider
    2.11 webdriver中使用 FileUtils ()
    Xcode8 添加PCH文件
    The app icon set "AppIcon" has an unassigned child告警
    Launch Image
    iOS App图标和启动画面尺寸
    iPhone屏幕尺寸、分辨率及适配
    Xcode下载失败 使用已购项目页面再试一次
    could not find developer disk image
    NSDate与 NSString 、long long类型的相互转化
  • 原文地址:https://www.cnblogs.com/zhenfei-jiang/p/9682430.html
Copyright © 2011-2022 走看看