zoukankan      html  css  js  c++  java
  • 没有用到React,为什么我需要import引入React?

    没有用到React,为什么我需要import引入React?

    本质上来说JSX是React.createElement(component, props, ...children)方法的语法糖。

    所以我们如果使用了JSX,我们其实就是在使用React,所以我们就需要引入React

    前言

    React是前端最受欢迎的框架之一,解读其源码的文章非常多,但是我想从另一个角度去解读React:从零开始实现一个React,从API层面实现React的大部分功能,在这个过程中去探索为什么有虚拟DOM、diff、为什么setState这样设计等问题。

    提起React,总是免不了和Vue做一番对比

    Vue的API设计非常简洁,但是其实现方式却让人感觉是“魔法”,开发者虽然能马上上手,但其原理却很难说清楚。

    相比之下React的设计哲学非常简单,虽然有很多需要自己处理的细节问题,但它没有引入任何新的概念,相对更加的干净和简单。

    关于jsx

    在开始之前,我们有必要搞清楚一些概念。

    我们来看一下这样一段代码:

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

      

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

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

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

      

    React.createElement和虚拟DOM

    前文提到,jsx片段会被转译成用React.createElement方法包裹的代码。所以第一步,我们来实现这个React.createElement方法

    从jsx转译结果来看,createElement方法的参数是这样:

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

      

    第一个参数是DOM节点的标签名,它的值可能是divh1span等等
    第二个参数是一个对象,里面包含了所有的属性,可能包含了classNameid等等
    从第三个参数开始,就是它的子节点

    我们对createElement的实现非常简单,只需要返回一个对象来保存它的信息就行了。

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

      

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

    现在我们来试试调用它

    // 将上文定义的createElement方法放到对象React中
    const React = {
        createElement
    }
    
    const element = (
        <div>
            hello<span>world!</span>
        </div>
    );
    console.log( element );
    

      打开调试工具,我们可以看到输出的对象和我们预想的一致

    我们的createElement方法返回的对象记录了这个DOM节点所有的信息,换言之,通过它我们就可以生成真正的DOM,这个记录信息的对象我们称之为虚拟DOM。

    ReactDOM.render

    接下来是ReactDOM.render方法,我们再来看这段代码

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

      经过转换,这段代码变成了这样

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

      

    所以render的第一个参数实际上接受的是createElement返回的对象,也就是虚拟DOM
    而第二个参数则是挂载的目标DOM

    总而言之,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 ( /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 );
            }
        }
    }
    

      这里其实还有个小问题:当多次调用render函数时,不会清除原来的内容。所以我们将其附加到ReactDOM对象上时,先清除一下挂载目标DOM的内容:

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

      

    渲染和更新

    到这里我们已经实现了React最为基础的功能,可以用它来做一些事了。

    我们先在index.html中添加一个根节点

    <div id="root"></div>
    

      我们先来试试官方文档中的Hello,World

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

      可以看到结果:

     

  • 相关阅读:
    QT中使用CoInitializeEx
    Linux 声音采集的时候内容全都是0
    linux类似系统中编译依赖库出现error trying to exec cc1plus
    C语言练习题2
    进程和任务计划管理
    解决火车头7.6版本无法采集部分https网站处理方法
    PHP输出13位时间戳函数
    destoon取消公司名称怎重复注册的限制
    destoon取消公司名称怎重复注册的限制
    destoon伪静态地址空值优化
  • 原文地址:https://www.cnblogs.com/zhaohongcheng/p/11234166.html
Copyright © 2011-2022 走看看