3 React技术
React是Facebook于2013年开源的框架。React解决的是前端MVC框架中的View视图层的问题。
3.1 Virtual DOM*
DOM(文档对象模型Document Object Model)
将网页内所有内容映射到一棵树型结构的层级对象模型上,浏览器提供对DOM的支持,用户可以是用脚本调用DOM API来动态的修改DOM结点,从而达到修改网页的目的,
这种修改是浏览器中完成,浏览器会根据DOM的改变重绘改变的DOM结点部分。
修改DOM重新渲染代价太高,前端框架为了提高效率,尽量减少DOM的重绘,提出了Virtual DOM,所有的修改都是现在VirtualDOM上完成的,通过比较算法,
找出浏览器DOM之间的差异,使用这个差异操作D0M,浏览器只需要渲染这部分变化就行了。
React实现了DOM Diff算法可以高效比对Virtual DOM和DOM的差异。
Virtual DOM实际上就是内存中的一个数据结构、对象,本身不属于浏览器,而是在浏览器中通过js引擎构建出来的一个对象。不管怎么样,都是在浏览器中跑得东西,和服务器端没什么关系。
前后端,一般前后端指的是web的前后端,前端往往指的是浏览器,后端指的是wsgi、server这些东西
前端技术,通常指浏览器中能够跑的东西:CSS,HTML,JS
3.2 支持JSX语法
JSX是一种JavaScript和XML混写的语法,是JavaScript的扩展。
React.render(<div><div><div>content</div></div></div>,document.getElementById('example')
)
3.3 测试程序
替换src/index.js
程序
// 基于React框架,使用JSX语法,替换上述代码
// 1、导入react模块
import React from "react"; // 导入react模块
import ReactDOM from "react-dom"; // 导入react的DOM模块// 2、创建React元素
class Root extends React.Component{ // 组件类定义,从React.Component类上继承,这个类生成JSXElement对象,即React元素state = {p1: "www.baidu.",p2: "com"} // 每一个react组件component都有一个状态变量state,它是一个js对象,可以定义属性来保存值。如果状态发生变化,会触发UI重新渲染。state是组件内部的私有属性render(){ // 渲染函数,返回组件中渲染的内容, 注意,只能返回唯一一个顶级元素回去console.log("render log");setTimeout(() => this.setState({p2: "net"}), 3000); // 延时函数:设置异步修改状态 // state的变化,会触发调用render,所以,render内最好不要对state进行任何改变,虽然可以,但是尽量不要。// this.state.p2 = "net"return <div>{this.state.p1 + this.state.p2}</div>; // div是一个容器,可以包含其他html标记。这里只能包含一个顶级元素}
}// 3、将React元素添加到DOM的Element元素中并渲染
ReactDOM.render(<Root/>, document.getElementById('root')); // 渲染,将组件类挂载到DOM树中要被渲染的元素中去。第一个参数是JSXElement对象;第二个参数是DOM的Element元素。指定DOM树中被渲染的元素id,将React元素对DOM树中的元素进行替换并重新渲染。// 4、保存后文件自动编译
- html中的元素分类:
学习html后, 你会了解一些基本的html元素(Element), 如p, h1~h6, br, div, li, ul, img等.如果将这些元素细分, 又可以分别归为顶级 (top-level)元素,块级 (block-level)元素和内联 (inline)元素.
1、Top-level element 【顶级元素】: { html, body, frameset }。包括html, body, frameset, 表现如Block-level element, 属于高级块级元素.
2、Block-level element 【块级元素】: { p, h1~h6, div, ul }。顾名思义就是以块显示的元素,高度宽度都是可以设置的。比如我们常用的 p, h1~h6, div, ul 默认状态下都是属于块级元素。块级元素比较霸道,默认状态下每次都占据一整个行,后面的内容也必须再新起一行显示。当然非块级元素也可以通过css的 display:block;将其更改成块级元素。此外还有个特殊的,float也具有此功能。块级元素能够独立存在, 一般的块级元素之间以换行(如一个段落结束后另起一行)分隔。块级元素是构成一个html的主要和关键元素, 而任意一个块级元素均可以用Box model来解释说明.
3、Inline element 【内联元素】: { a, br, em, img, li, span }通俗点来说就是文本的显示方式,与块级元素相反,内联元素的高度宽度是不可以设置的,其宽度就是自身文字或者图片的宽度。我们常用到的<a>、<span>、<em>
都属于内联元素。内联元素的显示特点就是像文本一样的显示,不会独自占据一个行。 当然内联元素也能变成块级元素,那就是通过css的display:inline;和float来实现。内联元素依附其他块级元素存在, 紧接于被联元素之间显示, 而不换行.
注意:
1、React组件的render函数return,只能是一个顶级元素。组件是最小封装对象
2、JSX语法是XML,要求所有元素闭合,注意<br />
不能写成<br>
3.4 JSX规范*
首字母小写就是html标记,首字母大写就是组件。
要求严格的HTML标记,要求所有标签都必须闭合。br也应写成<br />
,/前留一个空格。
单行省略小括号,多行请使用小括号
元素有嵌套,建议多行,注意缩进
JSX表达式:使用{}扩起来,如果大括号内使用了引号,会当做字符串处理。例如:<div>{'2>1?true:false'}<div/>
3.5 组件状态state*
每一个react组件component都有一个状态变量state,它是一个js对象,可以定义属性来保存值。
如果状态发生变化,会触发UI重新渲染。
state是组件内部的私有属性。
class Root extends React.Component{ // 组件类定义,从React.Component类上继承,这个类生成JSXElement对象,即React元素state = {p1: "www.baidu.",p2: "com"} // 每一个react组件component都有一个状态变量state,它是一个js对象,可以定义属性来保存值。如果状态发生变化,会触发UI重新渲染。state是组件内部的私有属性render(){ // 渲染函数,返回组件中渲染的内容console.log("render log");// this.setState({p2: "net"}); // 不可以对还在更新中的state使用setStatesetTimeout(() => this.setState({p2: "net"}), 3000); // 延时函数:设置异步修改状态,和上一行是两码事。// this.state.p2 = "net"return <div>{this.state.p1 + this.state.p2}</div>; // div是一个容器,可以包含其他html标记。这里只能包含一个顶级元素}
}
- state的变化,会触发调用render,所以,render内最好不要对state进行任何改变,虽然可以,但是尽量不要。
- 延时函数:设置异步修改状态
3.6 复杂状态
将本目录中’./test.html’使用jsx进行替换。
可以将’./test.html’文件移动到上一层目录下,然后在浏览器中http://localhost:3000/test.html
进行访问查看效果。
class SubElement extends React.Component{render(){return <div>这是子元素SubElement</div>}
}class Trigger extends React.Component{state = {flag: true}handlerClick(event){console.log("触发的事件ID为:" + event.target.id)alert("点击这里触发事件,事件ID为" + event.target.id)this.setState({flag: !this.state.flag})}render(){let x = this.state.flag?'true':'false';return (<div id="trig" onClick={this.handlerClick.bind(this)}>定义一个html标记div,div是分组,给这个标记起名字ID为trig,并注册一个事件onClick,事件捆绑一个函数handlerClick函数.当鼠标点击时,触发事件。事件一被触发立即调用与该事件绑定的函数,同时送一个参数进去, 这个参数不用管,这个参数是浏览器对象(event对象),由浏览器自动传入<SubElement/>{x}</div>)}
}ReactDOM.render(<Trigger/>, document.getElementById("root2"))
3.7 props属性*
props,使得可以从外部给React组件传递数据。传入的这个数据如果是对象,还可以访问、修改对象的属性。
props的属性是只读的,不允许修改。
import React from "react";
import ReactDOM from "react-dom";class SubElement extends React.Component{render(){console.log(this.props.name)console.log(this.props.parent, 'SubElement.props.parent.state.flag=' + this.props.parent.state.flag) // 还可以通过props传递的对象,修改、访问外层对象的元素return <div>这是子元素SubElement {this.props.children} {/*演示通过子元素,给props的children元素增加的属性*/}</div>}
}class Trigger extends React.Component{state = {flag: true}handlerClick(event){alert("点击这里触发事件,事件ID为" + event.target.id)this.setState({flag: !this.state.flag})// this.props.name = "Trigger" // props元素只读,不允许修改}render(){let x = this.state.flag?'true':'false';return (<div id="trig" onClick={this.handlerClick.bind(this)}><SubElement name="我是subElement" parent={this}><hr /><div>我是subElement的children的元素</div> {/*演示通过子元素传入数据到props的children元素中*/}</SubElement> {/*利用props属性,从组件外部给组件内部传递数据。这里的数据会保存到props属性中*/}点击这里触发一个事件,会弹出警告对话框。</div>)}
}ReactDOM.render(<Trigger/>, document.getElementById("root2"))
总结:
- state是私有private属性,组件外无法直接访问,可以修改state,建议使用setState。
- props是公有public属性,组件外也可以访问,只读。
- 代码示例中,
name="我是subElement"
,这个属性作为单一对象传递给组件,加入到组件的props属性中;parent={this}
,这里的this是Trigger组件本身。 - 在Trigger中,使用JSX语法增加子元素,这些子元素,也会被加入到subElement的props.children中。
3.8 构造器constructor
ES6使用构造器,为组件添加属性
import React from "react";
import ReactDOM from "react-dom";class Trigger extends React.Component{// state = {flag: true}constructor(props){ /*ES6使用构造器,为组件添加属性。通过构造器添加属性,使代码可读性更好*/ super(props); // 调用父类的constructor,需传入props属性。this.state = {flag: true}}handlerClick(event){alert("触发事件的ID为" + event.target.id)this.setState({flag: !this.state.flag})}render(){return (<div>点击这里触发一个事件,会弹出警告对话框。</div>)}
}
3.9 组件的生命周期管理*
组件的声明周期可分为三个状态:
- Mounting: 挂载,表示组件已插入到真实的DOM树中
- Updating: 正在本重新渲染
- Unmounting: 已从真实的DOM树中移除
组件的生命周期状态,说明在不同时机访问组件,组件正处在生命周期的不同状态上。在不同的声明周期状态访问,就产生不同的方法。
生命周期的方法如下:
-
装载组件触发
-
componentWillMount
在渲染前调用,在客户端也在服务端。只会在装载之前调用一次。 -
componentDidMount
:在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()
来进行访问。如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout,setInterval
或者发送AJAX请求等操作(防止异部操作阻塞U1)。只在装载完成后调用一次,在render之后。
-
-
更新组件触发。这些方法不会在首次render组件的周期调用
-
componentWillReceiveProps(nextProps)
在组件接收到一个新的prop时被调用。这个方法在初始化render时不会被调用。 -
shouldComponentUpdate(nextProps,nextState)
返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。可以在你确认不需要更新组件时使用。
如果设置为false,就是不允许更新组件,那么componentWillUpdate、componentDidUpdate
不会执行。 -
componentWillUpdate(nextProps, nextState)
在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。 -
componentDidUpdate(prevProps,prevState)
在组件完成更新后立即调用。在初始化时不会被调用。
-
-
卸载组件触发
componentWillUnmount
在组件从DOM中移除的时候立刻被调用。
从上图可以看出:
constructor
构造器是最早被执行的函数- 要触发
更新声明周期函数
,需要更新state
或props
。
3.10 无状态组件*
从React15.0开始,支持无状态组件。定义为:
import React from "react"; // 导入react模块
import ReactDOM from "react-dom"; // 导入react的DOM模块function Root(props){return <div>{props.name}</div>;
}ReactDOM.render(<Root name={"test"}/>, document.getElementById("root"));
无状态组件也叫函数式组件。
开发中很多情况下,组件其实很简单,不需要state状态,也不需要使用生命周期函数。无状态组件很好的满足了需求。
- 无状态组件函数应该提供一个参数
props
,返回一个React元素。 - 无状态组件函数本身就是render函数。
改写定义代码为:
import React from "react"; // 导入react模块
import ReactDOM from "react-dom"; // 导入react的DOM模块let ROOT = props => <div>{props.name}</div>;
ReactDOM.render(<Root name={"test"}/>, document.getElementById("root"));
总结
react中,虚拟dom有可能会被文档。组件的state、props、生命周期一般都会被问,需要理解。