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')
    );
    

      可以看到结果:

     

  • 相关阅读:
    图片上传-下载-删除等图片管理的若干经验总结3-单一业务场景的完整解决方案
    图片上传-下载-删除等图片管理的若干经验总结2
    HDU 1195 Open the Lock
    HDU 1690 Bus System
    HDU 2647 Reward
    HDU 2680 Choose the best route
    HDU 1596 find the safest road
    POJ 1904 King's Quest
    CDOJ 889 Battle for Silver
    CDOJ 888 Absurdistan Roads
  • 原文地址:https://www.cnblogs.com/zhaohongcheng/p/11234166.html
Copyright © 2011-2022 走看看