zoukankan      html  css  js  c++  java
  • react总结之JSX与虚拟DOM实现

      React是facebook开源的JS库,它可以把界面抽象成一个一个的组件,组件进行组合来得到功能丰富的页面。与Vue不同,React立足于MVC框架,是一个包括view和controller的库。首先我们来看一下什么是MVC。

      MVC(Model View Controller):M指的是业务模型,它有最多的处理任务,被模型返回的数据是中立的,模型与数据格式无关,这样一个模型可以为多个视图提供数据,由于应用于模型的代码只用写一次就可被多个视图进行重用,这样减少了代码的重复性;V指的是用户界面,如网页界面或者是软件的客户端界面;C是控制器,它可以接受用户输入并且调用模型和视图去完成用户的需求,它本身并不输入任何东西和做任务处理,它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。概括来说,用户的请求首先会到达Controller,由Controller从Model上获取数据,进而选择合适的View,把处理的结果呈现在View上。React它是用来创建用户界面的JS库,它和用户界面打交道可以把它看成是MVC中的 V层。

      一、JSX

      JSX它是JavaScript的语法扩展,即我们在js中可以编写html代码。它可以将html结构,数据,甚至是样式都聚合在一起。

      1、使用html标签

      先声明后使用,react中的html标签和vue的不太一样,react是插入而vue是替换。如下所示:把名为myElement的多个标签插入到root根元素中。其中要注意的是:html里的class在jsx中要写成className,for写成htmlFor,不能与js中的关键字重复。当代码中嵌套多个html标签,需要用一个div或者是空标签进行包裹,否则它会报错。

    import React from 'react';
    import ReactDOM from 'react-dom';
    
      let myElement =
      <>
          <div className="myDivElement">my-div-element</div>
          <h3>JSX</h3>
      </>
    
    ReactDOM.render(
      // 将元素渲染到DOM中
      myElement,
      document.getElementById('root')
    );

      2、使用js表达式

      我们可以在JSX中使用js表达式,表达式要写在花括号{}中。需要注的有以下几点:

      JSX中的注释是像js中块注释一样{/*注释内容*/};

      JSX中可以使用任意的变量,当我们需要把数组进行循环一般使用map,reduce,filter等方法;

      结构中的判断不能用else...if而是要使用三元运算符或者是可以使用&&,||操作。

    import React from 'react';
    import ReactDOM from 'react-dom';
    let num = 10;
    let name = 'hello JSX';
    let isLogin = true;
    let a = undefined;
    let nul = null;
    let col = 'lightpink';
    let ary = [14, 32, 42, 31, 85, 79];
    let href = "https://baidu.com";
    let obj = { a: 'React', b: 'Vue', c: 'Javascript' };
    let sty = { color: 'lightpink', background: 'lightblue', fontSize: '20px' };
    ReactDOM.render(
        <>
            {/* 注意特殊的注释方式 */}
    
            {/* 1.运算 */}
            <h2>{10 * 10}</h2>
    
            {/* 2.嵌入数据 */}
            <h3>数据是:{num}</h3>
            <h3>姓名是:{name}</h3>
            <h3>{isLogin ? 'hello React' : ''}</h3>
            {/* undefined和null不显示,如果非要显示则需要转成字符串 */}
            <h3>{a}</h3>
            <h3>{nul}</h3>
            <h3>{nul + ''}</h3>
    
            {/* 3.数组 */}
            {/* 3.1列表展示 */}
            <h3>数字列表</h3>
            <ul>{ary.map(item => <li key={item}>{item}</li>)}</ul>
            {/* 3.2过滤 */}
            <ul>{ary.filter(item => item >= 50).map(item => <li key={item}>{item}</li>)}</ul>
            {/* 3.2截取 */}
            <ul>{ary.slice(0, 3).map(item => <li key={item}>{item}</li>)}</ul>
    
            {/* 4.对象:在结构中不能直接放入对象 */}
            <li>我现在学的是:{obj.a}</li>
    
            {/* 5.判断不能用else...if可以使用三元 */}
            <h4>{1 > 0 ? '1>0' : '1<=0'}</h4>
    
            {/* 6.样式  style={{color:col}}这不是小胡子,这是花括号包了一个对象,style的值有两层花括号*/}
            <h5 style={{ color: col, backgroundColor: 'lightblue' }}>样式</h5>
            <a href={href} target="_blank">点击跳转</a>
            {/* 当我们需要设置多个属性,或者是有时不知道属性名称时,可以使用扩展属性。 */}
            <li style={{ ...sty }}>hello davina</li>
        </>,
        document.querySelector('#root')
    )

      JSX其实是一种语法糖,它并不是直接渲染到页面上,而是内部通过babel先转义成createElement形式,再进行渲染。React引入JSX主要是为了方便view层面上的组件化,它可以构建html结构化页面。React将JSX映射为虚拟元素,并且用虚拟元素来管理整个的虚拟DOM。如果我们用JSX语法声明某个元素它会被转化成React.createElement(type,props,...children)。当然如果元素是组件也是不例外的。如下所示:

    import React from 'react';
    import ReactDOM from 'react-dom';
    let name = 'davina';
    /* 
    ReactDOM.render(
      <h2 className="myh2" style={{ color: 'lightblue' }}>hello
      <span className="myspan">{name}</span>
      </h2>,
      document.querySelector('#root')
    )
     */
    
    // 它可以转化成如下:
    ReactDOM.render(
      React.createElement("h2", {
      className: "myh2",
      style: {
        color: 'lightblue'
      }
    }, "hello", 
      React.createElement("span", {
      className: "myspan"
    }, name)) ,
      document.querySelector('#root')
    )

      React元素它就是一个普通的js对象,它是构建应用的最小单位。当元素已经生成,它是不可变的,如果要进行更新它只会更新必要的部分。如下所示:

    import React from 'react'
    import ReactDom from 'react-dom'
    setInterval(() => {
      let element = <div><span>当前时间:</span>{new Date().toLocaleTimeString()}</div>
      ReactDom.render(element, document.getElementById('root'))
    }, 1000);

      二、实现虚拟DOM

      通过上面的应用我们可以看到React它是使用JSX编写虚拟的DOM对象,通过babel.js编译后生成真正的DOM,然后将真正的DOM插入到页面中。

    //index.js
    import React from './react';
    import ReactDOM from './react-dom';
    
    let element = React.createElement("h4", {
      className: "title",
      style: {
        color: 'lightpink',
        fontSize: '20px'
      }
    }, 'hello', React.createElement("span", null, "davina"));
    
    ReactDOM.render(element, document.getElementById('root'));

      在生成虚拟DOM时,使用createElement方法,用来模拟真实的DOM。根据真实的createElement我们可以看到它里面有三个参数:参数一:type类型,它有可能是DOM的元素标签,也有可能是组件名;参数二:props:属性,如下所示:className: "myh2",style: {color: 'lightblue'};参数三:chidlren子元素。

    //react.js
    //
    创建一个createElement函数,并把type,props传给ReactElement函数 function createElement(type, config = {}, children) { //声明名字和props let propName; let props = {}; //把config上的所有属性都拷贝到props上,这样原来的就不发生改变 for (propName in config) { props[propName] = config[propName]; } //儿子的长度 const childrenLength = arguments.length - 2; //当只有一个儿子时 if (childrenLength == 1) { props.children = children; //当多个儿子时 } else if (childrenLength > 1) { //把类数组转成数组并进行截取,得到所有儿子 props.children = Array.from(arguments).slice(2); } return ReactElement(type, props); } //声明一个ReactElement函数,那createElement返回的就是这个element function ReactElement(type, props) { //声明一个type,props属性的元素并返回 const element = { type, props }; return Element; }
    export default { createElement }

      这样一个createElement函数就创建成功,通过这个函数,我们可以把JSX格式的代码进行转化,创建一个虚拟的DOM。但单单这个方法是不足以达渲染要求,所以还需要render方法。它可以将虚拟的DOM解析成真实的DOM插入到父节点中最后并渲染到页面上

    //react-dom.js
    function render(element, parentNode) {
        // 如果element是字符串或者是数字那可以直接进行添加
        if (typeof element === 'string'||typeof element === 'number') {
            //创建一个文本节点然后添加到parentNode中
            return parentNode.appendChild(document.createTextNode(element));
        }
        //如果是element,element
        let type, props;
        type = element.type;
        props = element.props;
        //如果element是dom元素时,要处理特殊的属性,如className,fontSize......
        let domElement = document.createElement(type);//div,h1....
        for (let propName in props) {
            switch (propName) {
                //className要特殊处理
                case 'className':
                    domElement.className = props[propName];
                    break;
                //style要特殊处理
                case 'style':
                    let styleObj = props[propName];  //styleObj => {color: 'lightpink',fontSize: '20px'}
                    //用字符串拼接
                    //['color','fontSize']=>['color:lightpink,'font-size:20px']=>'color:lightpink,'font-size:20px'
                    let cssText = Object.keys(styleObj).map(attr => {
                        //propName=>${attr}     props[propName]=>${styleObj[attr]}
                        //处理驼峰转-
                        return `${attr.replace(/A-Z/g, function () { return "-" + arguments[1].toLowerCase() })}:${styleObj[attr]}`;
                    }).join(';');
                    domElement.style.cssText = 'color:lightpink;font-size:20px';
                    break;
                //children要特殊处理
                case 'children':
                    //转成数组
                    let children = Array.isArray(props.children) ? props.children : [props.children];
                    //进行循环,并且挂载在自己身上
                    children.forEach(child => render(child, domElement));
                    break;
                default:
                    // element.setAttribute(propName, props[propName])=>div.setAttribute('id','box')
                    //普通属性时直接进行添加,如id
                    domElement.setAttribute(propName, props[propName]);
            }
            parentNode.appendChild(domElement);
        }
    }
    
    export default { render }

      但element不一定是元素或者是字符串,数字,还有可能是组件,所以我们还要考虑到element是组件的情况,组件又分为函数组件和类组件,这二者要分开考虑。

    // index.js
    // import ReactDOM from './react-dom'
    // import React from './react'

    //
    函数组件 // function Welcome(props) { // return React.createElement('h4',{id:'welcome'},props.name,props.age) // } // let element = React.createElement(Welcome, { name: 'davina', age: 20 }); // ReactDOM.render(element, document.getElementById('root')); // 类组件 class Welcome1 extends React.Component{ render(){ return React.createElement('h4',{id:'class_welcome'},this.props.name,this.props.age) } } let element = React.createElement(Welcome1, { name: 'davina', age: 20 }); ReactDOM.render( element, document.getElementById('root'));

      当element为类组件时,因为类组件要继承React的Component所以:

    //react.js
    // Component
    class Component{
        //加一个静态属性isReactComponent,后面的所有子类都是可以继承它的静态属性
        static isReactComponent = true;
        constructor(props){
            this.props = props;
        }
    }
    export default { Component}
    //render函数内部
    //如果是element,element
        let type, props;
        type = element.type;
        props = element.props;
        
        //判断是否是类组件
        if (type.isReactComponent) {
            // returnedElement接收返回值
            let returnedElement = new type(props).render();
            type = returnedElement.type;
            props = returnedElement.props;
            //判断是否是函数
        } else if (typeof type == 'function') {
            let returnedElement = type(props);
            type = returnedElement.type;
            props = returnedElement.props;
        }

       总结来说,jsx它仅仅是一个语法糖,并不是直接渲染到页面上,在react内部它要通过先转义成React.createElement(type,props,...children)形式再进行调用。利用React.createElement函数形成了一个js对象树,即虚拟DOM,形成虚拟DOM后,如果状态或者是数据发生改变,新旧虚拟DOM通过一系列复杂的算法进行比较,再重新渲染,形成真实DOM。即react的渲染是从jsx到虚拟DOM再到真实DOM的过程。

     

      

  • 相关阅读:
    重量传感器
    cotex_m3内核提供的ITM串口打印调试
    unsigned char 转字符串:
    手工双面打印
    Windows系统(服务器)忘记管理员登录密码:
    标准电流信号为什么是4-20MA?(网络摘录)
    LPC1768之ISP
    万用表的位数
    485收发控制器:
    [LeetCode] 534. Design TinyURL 设计短网址
  • 原文地址:https://www.cnblogs.com/davina123/p/13606215.html
Copyright © 2011-2022 走看看