美文网首页前端 & js & react
React的简单实现(一)JSX和虚拟Dom

React的简单实现(一)JSX和虚拟Dom

作者: 不懂量化的吃货不是好前端 | 来源:发表于2020-05-03 11:15 被阅读0次
    createElement函数

    在react里面JSX会被转译成用React.createElement方法包裹的代码,比如

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

    会经过一个语法分析的库(babel里面)参考:https://www.jianshu.com/p/7e872afeae42
    ,最终被转化为:

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

    不管JSX,先简单实现一下createElement这个函数,从jsx转译结果来看,createElement方法的参数是这样:

    createElement( tag, attrs, child1, child2, child3 );
    

    第一个参数是DOM的标签名,比如div,h1,span等等
    第二个参数是一个对象,里面包含了所有的属性,可能包含了className,id等等
    第三个参数开始,就是它的子节点

    最简单实现,返回一个对象进行包裹:

    function createElement( tag, attrs, ...children ) {
        return {
            tag,
            attrs,
            children
        }
    }
    

    试着进行封装和调用:

    const React = {
        createElement
    }
    
    const element = (
        <div>
            hello<span>world!</span>
        </div>
    );
    console.log( element );
    
    image.png

    这时候会遇到上面的错误,因为目前我们的语法是JSX,所以需要引入babel的支持,参考:https://babeljs.io/docs/en/,这里我们采用一种更简单的方式,单纯对JSX进行转换

    1,新建一个.babelrc文件,指定transform-react-jsx用的方法就是上述的createElement:

    {
      "presets": ["env"],
      "plugins": [
        ["transform-react-jsx", {
          "pragma": "React.createElement"
        }]
      ]
    }
    

    2, 使用parcel打包工具,对jsx语法进行词法分析,然后用上述的createElement包裹起来,先进行安装:

    npm install -g parcel-bundler
    

    3,调整代码结构,新建index.html和index.js,js内容就是上面的


    image.png

    4,使用 ‘parcel index.html’ 命令进行打包操作
    5,打包完成后得到dist的输出


    image.png

    运行index.html可以得到下述结果(可能需要更改下js的路径)


    image.png
    ReactDOM.render方法

    参考react里面的写法

    ReactDOM.render(
        <h1>Hello, world!</h1>,
        document.getElementById('root')
    );
    

    实际上是JSX进过转换是这样的

    ReactDOM.render(
        React.createElement( 'h1', null, 'Hello, world!' ),
        document.getElementById('root')
    );
    

    render方法的作用就是将对象转换成虚拟DOM,再最终渲染成真实的DOM

    function render( vnode, container ) {
        
        // 当vnode为字符串时,渲染结果是一段文本
        if ( typeof vnode === 'string' ) {
            const textNode = document.createTextNode( vnode );
            return container.appendChild( textNode );
        }
    
        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 container.appendChild( dom );    // 将渲染结果挂载到真正的DOM上
    }
    

    还有setAttribute方法:

    function setAttribute( dom, name, value ) {
        // 如果属性名是className,则改回class
        if ( name === 'className' ) name = 'class';
    
        // 如果属性名是onXXX,则是一个事件监听方法
        if ( /on\w+/.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={ width: 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 );
            }
        }
    }
    

    每次render前,清理一下之前的内容:

    const ReactDOM = {
        render: ( vnode, container ) => {
            container.innerHTML = '';
            return render( vnode, container );
        }
    }
    

    在index.html里面增加容器:<div id="root"></div>
    重新 parcel index.html 运行,得到输出结果:

    image.png

    源码地址:https://github.com/liuxiaocong/dailyMove/tree/master/simpleReact

    相关文章

      网友评论

        本文标题:React的简单实现(一)JSX和虚拟Dom

        本文链接:https://www.haomeiwen.com/subject/hpxkghtx.html