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; }