React原理
在Web开发中,我们总需要将变化的数据实时反应到UI上,这时就需要对DOM进行操作。而复杂或频繁的DOM操作通常是性能瓶颈产生的原因(如何进行高性能的复杂DOM操作通常是衡量一个前端开发人员技能的重要指标)。React为此引入了虚拟DOM(Virtual DOM)的机制:在浏览器端用Javascript实现了一套DOM API。基于React进行开发时所有的DOM构造都是通过虚拟DOM进行,每当数据变化时,React都会重新构建整个DOM树,然后React将当前整个DOM树和上一次的DOM树进行对比,得到DOM结构的区别,然后仅仅将需要变化的部分进行实际的浏览器DOM更新。而且React能够批处理虚拟DOM的刷新,在一个事件循环(Event Loop)内的两次数据变化会被合并,例如你连续的先将节点内容从A变成B,然后又从B变成A,React会认为UI不发生任何变化,而如果通过手动控制,这种逻辑通常是极其复杂的。尽管每一次都需要构造完整的虚拟DOM树,但是因为虚拟DOM是内存数据,性能是极高的,而对实际DOM进行操作的仅仅是Diff部分,因而能达到提高性能的目的。这样,在保证性能的同时,开发者将不再需要关注某个数据的变化如何更新到一个或多个具体的DOM元素,而只需要关心在任意一个数据状态下,整个界面是如何Render的。
JSX
JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。React 可以用来做简单的 JSX 句法转换。
为什么要用 JSX?
- 你不需要为了 React 使用 JSX,可以直接使用原生 JS。但是,我们建议使用 JSX 是因为它能精确,也是常用的定义包含属性的树状结构的语法。
- 它对于非专职开发者比如设计师也比较熟悉。
- XML 有固定的标签开启和闭合的优点。这能让复杂的树更易于阅读,优于方法调用和对象字面量的形式。
- 它没有修改 JavaScript 语义。
HTML 标签对比 React 组件
React 可以渲染 HTML 标签 (strings) 或 React 组件 (classes)。
要渲染 HTML 标签,只需在 JSX 里使用小写字母开头的标签名。
var myDivElement =
; React.render(myDivElement, document.getElementById('example'));
要渲染 React 组件,只需创建一个大写字母开头的本地变量。
var MyComponent = React.createClass({/*...*/});
var myElement = ;
React.render(myElement, document.getElementById('example'));
React 的 JSX 里约定分别使用首字母大、小写来区分本地组件的类和 HTML 标签。
特别注意
由于 JSX 就是 JavaScript,一些标识符像 class
和 for
不建议作为 XML 属性名。作为替代,React DOM 使用className
和 htmlFor
来做对应的属性。
转换(Transform)
JSX 把类 XML 的语法转成原生 JavaScript,XML 元素、属性和子节点被转换成 React.createElement
的参数。
var Nav; // 输入 (JSX): var app =
; // 输出 (JS): var app = React.createElement(Nav, {color:"blue"});
注意,要想使用
,Nav变量一定要在作用区间内。JSX 也支持使用 XML 语法定义子结点:
var Nav, Profile;
// 输入 (JSX):
var app = click;
// 输出 (JS):
var app = React.createElement( Nav, {color:"blue"}, React.createElement(Profile, null, "click"));
可以使用 facebook compile 工具查看比较两者转换 babeljs.io/repl/
属性表达式
要使用 JavaScript 表达式作为属性值,只需把这个表达式用一对大括号 ({}) 包起来,不要用引号 ("")。
// 输入 (JSX):
var person = ;
// 输出 (JS):
var person = React.createElement(
Person,
{name: window.isLoggedIn ? window.name : ''}
);
子节点表达式
同样地,JavaScript 表达式可用于描述子结点:
// 输入 (JSX): var content = {window.isLoggedIn ?
: }; // 输出 (JS): var content = React.createElement( Container, null, window.isLoggedIn ? React.createElement(Nav) : React.createElement(Login) );
展开属性
如果你事先知道组件需要的全部 Props(属性),JSX 很容易地这样写:
var component = ;
修改 Props 是不好的,明白吗
如果你不知道要设置哪些 Props,那么现在最好不要设置它:
var component = ;
component.props.foo = x; // 不好
component.props.bar = y; // 同样不好
这样是反模式,因为 React 不能帮你检查属性类型(propTypes)。这样即使你的 属性类型有错误也不能得到清晰的错误提示。
Props 应该被当作禁止修改的。修改 props 对象可能会导致预料之外的结果,所以最好不要去修改 props 对象。
展开属性(Spread Attributes)
现在你可以使用 JSX 的新特性 - 展开属性:
var props = {}; props.foo = x; props.bar = y; var component = ;
传入对象的属性会被复制到组件内。
它能被多次使用,也可以和其它属性一起用。注意顺序很重要,后面的会覆盖掉前面的。
var props = { foo: 'default' }; var component = ;
console.log(component.props.foo); // 'override'
这个奇怪的 ...
标记是什么?
这个 ...
操作符(增强的操作符)已经被 ES6 数组 支持。相关的还有 ES7 规范草案中的 Object 剩余和展开属性(Rest and Spread Properties)。我们利用了这些还在制定中标准中已经被支持的特性来使 JSX 拥有更优雅的语法。
JSX 陷阱
HTML 实体可以插入到 JSX 的文本中。
First · Second
如果想在 JSX 表达式中显示 HTML 实体,可以会遇到二次转义的问题,因为 React 默认会转义所有字符串,为了防止各种 XSS 攻击。
// 错误: 会显示 “First · Second”
{'First · Second'}
有多种绕过的方法。最简单的是直接用 Unicode 字符。这时要确保文件是 UTF-8 编码且网页也指定为 UTF-8 编码。
{'First · Second'}
安全的做法是先找到 实体的 Unicode 编号 ,然后在 JavaScript 字符串里使用。
{'First \u00b7 Second'} {'First ' + String.fromCharCode(183) + ' Second'}
可以在数组里混合使用字符串和 JSX 元素。
{['First ', ·, ' Second']}
万不得已,可以直接使用原始 HTML。
如果往原生 HTML 元素里传入 HTML 规范里不存在的属性,React 不会显示它们。如果需要使用自定义属性,要加data-
前缀。
以 aria-
开头的 [网络无障碍] 属性可以正常使用。
注释
JSX 里添加注释很容易;它们只是 JS 表达式而已。你只需要在一个标签的子节点内(非最外层)小心地用 {}
包围要注释的部分。
var content = ( {
/* 一般注释, 用 {} 包围 */
} );
组件属性
属性初始化在组件中进行初始化
属性调用使用 this.props.xx
var title = this.props.title
这里有几点需要注意:
-
1)获取属性的值用的是this.props.属性名
-
2)创建的组件名称首字母必须大写。
-
3)为元素添加css的class时,要用className。
-
4)组件的style属性的设置方式也值得注意,要写成style={{width: this.state.witdh}}。
组件状态
一般情况下,在getInitialState中进行状态的初始化,该方法只会初始化一次
getInitialState: function(){
return {state: false};
}
使用setState()进行状态的改变
this.setState({state: true});
原理分析:
当用户点击组件,导致状态变化,this.setState 方法就修改状态值,每次修改以后,自动调用 this.render 方法,再次渲染组件。
这里值得注意的几点如下:
1)getInitialState函数必须有返回值,可以是NULL或者一个对象。
2)访问state的方法是this.state.属性名。
3)变量用{}包裹,不需要再加双引号。
组件生命周期
组件的生命周期分成三个状态:
- Mounting:已插入真实 DOM
- Updating:正在被重新渲染
- Unmounting:已移出真实 DOM
React 为每个状态都提供了两种处理函数,will 函数在进入状态之前调用,did 函数在进入状态之后调用,三种状态共计五种处理函数。
- componentWillMount()
- componentDidMount()
- componentWillUpdate(object nextProps, object nextState)
- componentDidUpdate(object prevProps, object prevState)
- componentWillUnmount()
此外,React 还提供两种特殊状态的处理函数。
- componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
- shouldComponentUpdate(object nextProps, object nextState):组件判断是否重新渲染时调用
组件话开发
React认为一个组件应该具有如下特征:
-
(1)可组合(Composeable):一个组件易于和其它组件一起使用,或者嵌套在另一个组件内部。如果一个组件内部创建了另一个组件,那么说父组件拥有(own)它创建的子组件,通过这个特性,一个复杂的UI可以拆分成多个简单的UI组件;
-
(2)可重用(Reusable):每个组件都是具有独立功能的,它可以被使用在多个UI场景;
-
(3)可维护(Maintainable):每个小的组件仅仅包含自身的逻辑,更容易被理解和维护;
-
(4)可测试(Testable):因为每个组件都是独立的,那么对于各个组件分别测试显然要比对于整个UI进行测试容易的多。
ReactJS小结
-
1、ReactJs是基于组件化的开发,所以最终你的页面应该是由若干个小组件组成的大组件。
-
2、可以通过属性,将值传递到组件内部,同理也可以通过属性将内部的结果传递到父级组件(留给大家研究);要对某些值的变化做DOM操作的,要把这些值放到state中。
-
3、为组件添加外部css样式时,类名应该写成className而不是class;添加内部样式时,应该是style={{opacity: this.state.opacity}}而不是style="opacity:{this.state.opacity};"。
-
4、组件名称首字母必须大写。
-
5、变量名用{}包裹,且不能加双引号。
注意点
- 只有一个限制: React 组件只能渲染单个根节点。如果你想要返回多个节点,它们必须被包含在同一个节点里。