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层。

  下面我们先来看一下React中的基本概念。

  一、JSX

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

  1、使用html标签

  先声明后使用,react中的html标签和vue的不太一样,react是插入而vue是替换。如下所示:把名为myElement的多个标签插入到root根元素中。其中要注意的是:html里的class在jsx中要写成className,class在js里为关键字,for写成htmlFor。当代码中嵌套多个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 name = 'hello JSX';
let col='lightpink';
let ary =[1,2,3,4,5];
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>姓名是:{name}</h3>

    {/* 3.判断不能用else...if可以使用三元 */}

    <h4>{1 > 0 ? '1>0' : '1<=0'}</h4>

    {/* 4.样式  style={{color:col}}这不是小胡子,这是花括号包了一个对象,style的值有两层花括号*/}
    <h5 style={{color:col,backgroundColor:'lightblue'}}>样式</h5>

    {/* 5.数组 */}
    <ul>{ary.map(item=>{return <li key={item}>{item}</li>})}</ul>

    <li><a href={href}>点击跳转</a></li>

    {/* 在结构中不能直接放入对象 */}
    <li>我现在学的是:{obj.a}</li>

    {/* 当我们需要设置多个属性,或者是有时不知道属性名称时,可以使用扩展属性。 */}
    <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;
    }

 

 

 

  

 

上一篇:vue对比其他框架


下一篇:JS和JSX的区别