虚拟化技术的用户目前有两种免费的开源管理程序可以选择:Xen和KVM。
作者:admin
React 概览
React 的核心思想是:封装组件。
各个组件维护自己的状态和 UI,当状态变更,自动重新渲染整个组件。
基于这种方式的一个直观感受就是我们不再需要不厌其烦地来回查找某个 DOM 元素,然后操作 DOM 去更改 UI。
React 大体包含下面这些概念:
- 组件
- JSX
- Virtual DOM
- Data Flow
这里通过一个简单的组件来快速了解这些概念,以及建立起对 React 的一个总体认识。
import React, { Component } from 'react';
import { render } from 'react-dom';
class HelloMessage extends Component {
render() {
return <div>Hello {this.props.name}</div>;
}
}
// 加载组件到 DOM 元素 mountNode
render(<HelloMessage name="John" />, mountNode);
组件
React 应用都是构建在组件之上。
上面的 HelloMessage
就是一个 React 构建的组件,最后一句 render
会把这个组件显示到页面上的某个元素 mountNode
里面,显示的内容就是 <div>Hello John</div>
。
props
是组件包含的两个核心概念之一,另一个是 state
(这个组件没用到)。可以把 props
看作是组件的配置属性,在组件内部是不变的,只是在调用这个组件的时候传入不同的属性(比如这里的 name
)来定制显示这个组件。
JSX
从上面的代码可以看到将 HTML 直接嵌入了 JS 代码里面,这个就是 React 提出的一种叫 JSX 的语法,这应该是最开始接触 React 最不能接受的设定之一,因为前端被“表现和逻辑层分离”这种思想“洗脑”太久了。但实际上组件的 HTML 是组成一个组件不可分割的一部分,能够将 HTML 封装起来才是组件的完全体,React 发明了 JSX 让 JS 支持嵌入 HTML 不得不说是一种非常聪明的做法,让前端实现真正意义上的组件化成为了可能。
好消息是你可以不一定使用这种语法,后面会进一步介绍 JSX,到时候你可能就会喜欢上了。现在要知道的是,要使用包含 JSX 的组件,是需要“编译”输出 JS 代码才能使用的,之后就会讲到开发环境。
Virtual DOM
当组件状态 state
有更改的时候,React 会自动调用组件的 render
方法重新渲染整个组件的 UI。
当然如果真的这样大面积的操作 DOM,性能会是一个很大的问题,所以 React 实现了一个Virtual DOM,组件 DOM 结构就是映射到这个 Virtual DOM 上,React 在这个 Virtual DOM 上实现了一个 diff 算法,当要重新渲染组件的时候,会通过 diff 寻找到要变更的 DOM 节点,再把这个修改更新到浏览器实际的 DOM 节点上,所以实际上不是真的渲染整个 DOM 树。这个 Virtual DOM 是一个纯粹的 JS 数据结构,所以性能会比原生 DOM 快很多。
Data Flow
“单向数据绑定”是 React 推崇的一种应用架构的方式。当应用足够复杂时才能体会到它的好处,虽然在一般应用场景下你可能不会意识到它的存在,也不会影响你开始使用 React,你只要先知道有这么个概念。
开发环境配置
Webpack 配置 React 开发环境
Webpack 是一个前端资源加载/打包工具,只需要相对简单的配置就可以提供前端工程化需要的各种功能,并且如果有需要它还可以被整合到其他比如 Grunt / Gulp 的工作流。
安装 Webpack:npm install -g webpack
Webpack 使用一个名为 webpack.config.js
的配置文件,要编译 JSX,先安装对应的 loader: npm install babel-loader --save-dev
假设我们在当前工程目录有一个入口文件 entry.js
,React 组件放置在一个 components/
目录下,组件被 entry.js
引用,要使用 entry.js
,我们把这个文件指定输出到 dist/bundle.js
,Webpack 配置如下:
var path = require('path');
module.exports = {
entry: './entry.js',
output: {
path: path.join(__dirname, '/dist'),
filename: 'bundle.js'
},
resolve: {
extensions: ['', '.js', '.jsx']
},
module: {
loaders: [
{ test: /\.js|jsx$/, loaders: ['babel'] }
]
}
}
resolve
指定可以被 import
的文件后缀。比如 Hello.jsx
这样的文件就可以直接用 import Hello from 'Hello'
引用。
loaders
指定 babel-loader 编译后缀名为 .js
或者 .jsx
的文件,这样你就可以在这两种类型的文件中自由使用 JSX 和 ES6 了。
监听编译: webpack -d --watch
更多关于 Webpack 的介绍
JSX
为什么要引入 JSX 这种语法
传统的 MVC 是将模板放在其他地方,比如 <script>
标签或者模板文件,再在 JS 中通过某种手段引用模板。按照这种思路,想想多少次我们面对四处分散的模板片段不知所措?纠结模板引擎,纠结模板存放位置,纠结如何引用模板……下面是一段 React 官方的看法:
We strongly believe that components are the right way to separate concerns rather than “templates” and “display logic.” We think that markup and the code that generates it are intimately tied together. Additionally, display logic is often very complex and using template languages to express it becomes cumbersome.
简单来说,React 认为组件才是王道,而组件是和模板紧密关联的,组件模板和组件逻辑分离让问题复杂化了。显而易见的道理,关键是怎么做?
所以就有了 JSX 这种语法,就是为了把 HTML 模板直接嵌入到 JS 代码里面,这样就做到了模板和组件关联,但是 JS 不支持这种包含 HTML 的语法,所以需要通过工具将 JSX 编译输出成 JS 代码才能使用。
JSX 是可选的
因为 JSX 最终是输出成 JS 代码来表达的,所以我们可以直接用 React 提供的这些 DOM 构建方法来写模板,比如一个 JSX 写的一个链接:
<a href="http://facebook.github.io/react/">Hello!</a>
用 JS 代码来写就成这样了:
React.createElement('a', {href: 'http://facebook.github.io/react/'}, 'Hello!')
你可以通过 React.createElement
来构造组件的 DOM 树。第一个参数是标签名,第二个参数是属性对象,第三个参数是子元素。
一个包含子元素的例子:
var child = React.createElement('li', null, 'Text Content');
var root = React.createElement('ul', { className: 'my-list' }, child);
React.render(root, document.body);
对于常见的 HTML 标签,React 已经内置了工厂方法:
var root = React.DOM.ul({ className: 'my-list' },
React.DOM.li(null, 'Text Content')
);
所以 JSX 和 JS 之间的转换也很简单直观,用 JSX 的好处就是它基本上就是 HTML(后面会讲到有一些小差异),对于构造 DOM 来说我们更熟悉,更具可读性。
关于 JSX 映射成 JS 对象,也就是 Virtual DOM 的内部描述,参见Virtual DOM Terminology,如果你不想使用 JSX,直接使用 JS 就是用这里面提到的接口方法。
使用 JSX
利用 JSX 编写 DOM 结构,可以用原生的 HTML 标签,也可以直接像普通标签一样引用 React 组件。这两者约定通过大小写来区分,小写的字符串是 HTML 标签,大写开头的变量是 React 组件。
使用 HTML 标签:
import React from 'react';
import { render } from 'react-dom';
var myDivElement = <div className="foo" />;
render(myDivElement, document.getElementById('mountNode'));
HTML 里的 class
在 JSX 里要写成 className
,因为 class
在 JS 里是保留关键字。同理某些属性比如 for
要写成 htmlFor
。
使用组件:
import React from 'react';
import { render } from 'react-dom';
import MyComponent from './MyComponet';
var myElement = <MyComponent someProperty={true} />;
render(myElement, document.body);
使用 JavaScript 表达式
属性值使用表达式,只要用 {}
替换 ""
:
// Input (JSX):
var person = <Person name={window.isLoggedIn ? window.name : ''} />;
// Output (JS):
var person = React.createElement(
Person,
{name: window.isLoggedIn ? window.name : ''}
);
子组件也可以作为表达式使用:
// Input (JSX):
var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;
// Output (JS):
var content = React.createElement(
Container,
null,
window.isLoggedIn ? React.createElement(Nav) : React.createElement(Login)
);
注释
在 JSX 里使用注释也很简单,就是沿用 JavaScript,唯一要注意的是在一个组件的子元素位置使用注释要用 {}
包起来。
var content = (
<Nav>
{/* child comment, put {} around */}
<Person
/* multi
line
comment */
name={window.isLoggedIn ? window.name : ''} // end of line comment
/>
</Nav>
);
HTML 转义
React 会将所有要显示到 DOM 的字符串转义,防止 XSS。所以如果 JSX 中含有转义后的实体字符比如 ©
(©) 最后显示到 DOM 中不会正确显示,因为 React 自动把 ©
中的特殊字符转义了。有几种解决办法:
- 直接使用 UTF-8 字符 ©
- 使用对应字符的 Unicode 编码,查询编码
- 使用数组组装
<div>{['cc ', <span>©</span>, ' 2015']}</div>
- 直接插入原始的 HTML
<div dangerouslySetInnerHTML={{__html: 'cc © 2015'}} />
自定义 HTML 属性
如果在 JSX 中使用的属性不存在于 HTML 的规范中,这个属性会被忽略。如果要使用自定义属性,可以用 data-
前缀。
可访问性属性的前缀 aria-
也是支持的。
支持的标签和属性
如果你要使用的某些标签或属性不在这些支持列表里面就可能被 React 忽略,必须要使用的话可以提 issue,或者用前面提到的 dangerouslySetInnerHTML
。
属性扩散
有时候你需要给组件设置多个属性,你不想一个个写下这些属性,或者有时候你甚至不知道这些属性的名称,这时候 spread attributes 的功能就很有用了。
比如:
var props = {};
props.foo = x;
props.bar = y;
var component = <Component {...props} />;
props
对象的属性会被设置成 Component
的属性。
属性也可以被覆盖:
var props = { foo: 'default' };
var component = <Component {...props} foo={'override'} />;
console.log(component.props.foo); // 'override'
写在后面的属性值会覆盖前面的属性。
关于 ...
操作符
The
...
operator (or spread operator) is already supported for arrays in ES6. There is also an ES7 proposal for Object Rest and Spread Properties.
JSX 与 HTML 的差异
除了前面提到的 class
要写成 className
,比较典型的还有:
style
属性接受由 CSS 属性构成的 JS 对象onChange
事件表现更接近我们的直觉(不需要 onBlur 去触发)- 表单的表现差异比较大,要单独再讲
更多异同,可以参见 DOM Differences
React 组件
可以这么说,一个 React 应用就是构建在 React 组件之上的。
组件有两个核心概念:
- props
- state
一个组件就是通过这两个属性的值在 render
方法里面生成这个组件对应的 HTML 结构。
注意:组件生成的 HTML 结构只能有一个单一的根节点。
props
前面也提到很多次了,props
就是组件的属性,由外部通过 JSX 属性传入设置,一旦初始设置完成,就可以认为 this.props
是不可更改的,所以不要轻易更改设置 this.props
里面的值(虽然对于一个 JS 对象你可以做任何事)。
state
state
是组件的当前状态,可以把组件简单看成一个“状态机”,根据状态 state
呈现不同的 UI 展示。
一旦状态(数据)更改,组件就会自动调用 render
重新渲染 UI,这个更改的动作会通过 this.setState
方法来触发。
划分状态数据
一条原则:让组件尽可能地少状态。
这样组件逻辑就越容易维护。
什么样的数据属性可以当作状态?
当更改这个状态(数据)需要更新组件 UI 的就可以认为是 state
,下面这些可以认为不是状态:
- 可计算的数据:比如一个数组的长度
- 和 props 重复的数据:除非这个数据是要做变更的
最后回过头来反复看几遍 Thinking in React,相信会对组件有更深刻的认识。
无状态组件
你也可以用纯粹的函数来定义无状态的组件(stateless function),这种组件没有状态,没有生命周期,只是简单的接受 props 渲染生成 DOM 结构。无状态组件非常简单,开销很低,如果可能的话尽量使用无状态组件。比如使用箭头函数定义:
const HelloMessage = (props) => <div> Hello {props.name}</div>;
render(<HelloMessage name="John" />, mountNode);
因为无状态组件只是函数,所以它没有实例返回,这点在想用 refs 获取无状态组件的时候要注意,参见DOM 操作。
组件生命周期
一般来说,一个组件类由 extends Component
创建,并且提供一个 render
方法以及其他可选的生命周期函数、组件相关的事件或方法来定义。
一个简单的例子:
import React, { Component } from 'react';
import { render } from 'react-dom';
class LikeButton extends Component {
constructor(props) {
super(props);
this.state = { liked: false };
}
handleClick(e) {
this.setState({ liked: !this.state.liked });
}
render() {
const text = this.state.liked ? 'like' : 'haven\'t liked';
return (
<p onClick={this.handleClick.bind(this)}>
You {text} this. Click to toggle.
</p>
);
}
}
render(
<LikeButton />,
document.getElementById('example')
);
getInitialState
初始化 this.state
的值,只在组件装载之前调用一次。
如果是使用 ES6 的语法,你也可以在构造函数中初始化状态,比如:
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: props.initialCount };
}
render() {
// ...
}
}
getDefaultProps
只在组件创建时调用一次并缓存返回的对象(即在 React.createClass
之后就会调用)。
因为这个方法在实例初始化之前调用,所以在这个方法里面不能依赖 this
获取到这个组件的实例。
在组件装载之后,这个方法缓存的结果会用来保证访问 this.props
的属性时,当这个属性没有在父组件中传入(在这个组件的 JSX 属性里设置),也总是有值的。
如果是使用 ES6 语法,可以直接定义 defaultProps
这个类属性来替代,这样能更直观的知道 default props 是预先定义好的对象值:
Counter.defaultProps = { initialCount: 0 };
render
必须
组装生成这个组件的 HTML 结构(使用原生 HTML 标签或者子组件),也可以返回 null
或者 false
,这时候 ReactDOM.findDOMNode(this)
会返回 null
。
生命周期函数
装载组件触发
componentWillMount
只会在装载之前调用一次,在 render
之前调用,你可以在这个方法里面调用 setState
改变状态,并且不会导致额外调用一次 render
componentDidMount
只会在装载完成之后调用一次,在 render
之后调用,从这里开始可以通过 ReactDOM.findDOMNode(this)
获取到组件的 DOM 节点。
更新组件触发
这些方法不会在首次 render
组件的周期调用
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
卸载组件触发
componentWillUnmount
更多关于组件相关的方法说明,参见: