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的过程。