一、React
React是一个JavaScript库,是由FaceBook和Instagram开发的,主要用于用户创建图形化界面。
由于 React 的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它可能是将来 Web 开发的主流工具。
这个项目本身也越滚越大,从最早的UI引擎变成了一整套前后端通吃的 Web App 解决方案。衍生的 React Native 项目,目标更是宏伟,希望用写 Web App 的方式去写 Native App。如果能够实现,整个互联网行业都会被颠覆,因为同一组人只需要写一次 UI ,就能同时运行在服务器、浏览器和手机。
二、React组件
React 的核心思想是:封装组件。各个组件维护自己的状态和 UI,当状态变更,自动重新渲染整个组件。基于这种方式的一个直观感受就是我们不再需要不厌其烦地来回查找某个 DOM 元素,然后操作 DOM 去更改 UI。
React 大体包含下面这些概念:
- 组件
- JSX
- Virtual DOM
- Data Flow
-
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 不得不说是一种非常聪明的做法,让前端实现真正意义上的组件化成为了可能。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,你只要先知道有这么个概念。使用 React 的网页源码,结构大致如下。
-
-
<html>
-
<head>
-
<meta charset="utf-8">
-
<title>Hello world! </title>
-
<script src = "../../build/react.js"> </script>
-
<script src = "../../build/react-dom.js"> </script>
-
<script src = "../../build/browser.min.js"> </script>
-
</head>
-
<body>
-
<div id = "example"> </div>
-
<script type="text/babel">
-
ReactDOM.render(
-
<h1>Hello,World!</h1>,
-
document.getElementById( 'example')
-
);
-
</script>
-
</body>
-
</html>
其次,上面代码一共用了三个库: react.js 、react-dom.js 和 Browser.js ,它们必须首先加载。其中, react.js 是 React 的 核心库 ,react-dom.js 是提供 与 DOM 相关的功能 ,Browser.js 的作用是 将 JSX 语法转为 JavaScript 语法 ,这一步很消耗时间,实际上线的时候,应该将它放到服务器完成。
$ babel src --out-dir build
JSX的基本语法规则:遇到
HTML标签(以<开头)
,就用HTML规则解析;遇到
代码块(以{开头)
,就用JavaScript规则解析。上面命令可以将 src 子目录的 js 文件进行语法转换,转码后的文件全部放在
build 子目录
。
三、React组件
3.1 ReactDOM.render()
ReactDOM.render 是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点。
-
ReactDOM.render(
-
<h1>Hello, world!</h1>,
-
document.getElementById( 'example')
-
);
3.2 props和state
组件有两个核心概念:
- props
- state
props 就是组件的属性,由外部通过 JSX 属性传入设置,一旦初始设置完成,就可以认为 this.props 是不可更改的,所以不要轻易更改设置 this.props 里面的值(虽然对于一个 JS 对象你可以做任何事)。
state 是组件的当前状态,可以把组件简单看成一个“状态机”,根据状态 state 呈现不同的 UI 展示。一旦状态(数据)更改,组件就会自动调用 render 重新渲染 UI,这个更改的动作会通过 this.setState 方法来触发。
this.props.children
this.props对象的属性和组件的属性一一对应,但是有个children除外,它表示的是组件的所有子节点:
-
var NotesList = React.createClass({
-
render: function() {
-
return (
-
<ol>
-
{
-
React.Children.map(this.props.children, function (child) {
-
return <li>{child} </li>;
-
})
-
}
-
</ol>
-
);
-
}
-
});
-
-
ReactDOM.render(
-
<NotesList>
-
<span>hello </span>
-
<span>world </span>
-
</NotesList>,
-
document.body
-
);
-
1. hello
-
2. world
3.3 事件处理
React 的一大创新,就是将组件看成是一个状态机,一开始有一个初始状态,然后用户互动,导致状态变化,从而触发重新渲染 UI,如下的例子显示了React中事件绑定和处理的机制:
-
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')
-
);
由于 this.props 和 this.state 都用于描述组件的特性,可能会产生混淆。一个简单的区分方法是,this.props 表示那些一旦定义,就不再改变的特性,而 this.state 是会随着用户互动而产生变化的特性。
3.4 组件的生命周期
组件的生命周期分成三个状态:
- Mounting:已插入真实 DOM
- Updating:正在被重新渲染
- Unmounting:已移出真实 DOM
- componentWillMount()
- componentDidMount()
- componentWillUpdate(object nextProps, object nextState)
- componentDidUpdate(object prevProps, object prevState)
- componentWillUnmount()
此外,React 还提供两种特殊状态的处理函数:
- componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
- shouldComponentUpdate(object nextProps, object nextState):组件判断是否重新渲染时调用
如下是状态处理函数的使用实例,即一个100ms定时器:
-
var Hello = React.createClass({
-
getInitialState: function () {
-
return {
-
opacity: 1.0
-
};
-
},
-
-
componentDidMount: function () {
-
this.timer = setInterval( function () {
-
var opacity = this.state.opacity;
-
opacity -= .05;
-
if (opacity < 0.1) {
-
opacity = 1.0;
-
}
-
this.setState({
-
opacity: opacity
-
});
-
}.bind( this), 100);
-
},
-
-
render: function () {
-
return (
-
<div style={{opacity: this.state.opacity}}>
-
Hello {this.props.name}
-
</div>
-
);
-
}
-
});
-
-
ReactDOM.render(
-
<Hello name="world"/>,
-
document.body
-
);
3.5 组件的引用
ReactDOM.render 组件返回的是什么?
它会返回对组件的引用也就是组件实例(对于无状态状态组件来说返回 null),注意 JSX 返回的不是组件实例,它只是一个ReactElement对象(还记得我们用纯 JS 来构建 JSX 的方式吗),比如这种:
-
// A ReactElement
-
const myComponent = <MyComponent />
-
-
// render
-
const myComponentInstance = ReactDOM.render(myComponent, mountNode);
-
myComponentInstance.doSomething();
-
findDOMNode()
当组件加载到页面上之后(mounted),你都可以通过 react-dom 提供的 findDOMNode () 方法拿到组件对应的 DOM 元素。
-
import { findDOMNode } from 'react-dom';
-
-
// Inside Component class
-
componentDidMound() {
-
const el = findDOMNode( this);
-
}
另外一种方式就是通过在要引用的 DOM 元素上面设置一个 ref 属性指定一个名称,然后通过 this.refs.name 来访问对应的 DOM 元素。比如有一种情况是必须直接操作 DOM 来实现的,你希望一个 <input/> 元素在你清空它的值时 focus,你没法仅仅靠 state 来实现这个功能。
-
class App extends Component {
-
constructor() {
-
return { userInput: '' };
-
}
-
-
handleChange(e) {
-
this.setState({ userInput: e.target.value });
-
}
-
-
clearAndFocusInput() {
-
this.setState({ userInput: '' }, () => {
-
this.refs.theInput.focus();
-
});
-
}
-
-
render() {
-
return (
-
<div>
-
<div onClick={this.clearAndFocusInput.bind(this)}>
-
Click to Focus and Reset
-
</div>
-
<input
-
ref= "theInput"
-
value= {this.state.userInput}
-
onChange= {this.handleChange.bind(this)}
-
/>
-
</div>
-
);
-
}
-
}
因为无状态组件没有实例,所以 ref 不能设置在无状态组件上,一般来说这没什么问题,因为无状态组件没有实例方法,不需要 ref 去拿实例调用相关的方法,但是如果想要拿无状态组件的 DOM 元素的时候,就需要用一个状态组件封装一层,然后通过 ref 和 findDOMNode 去获取。
3.6 组件间通信
父子组件间通信
这种情况下很简单,就是通过 props 属性传递,在父组件给子组件设置 props,然后子组件就可以通过 props 访问到父组件的数据/方法,这样就搭建起了父子组件间通信的桥梁。
-
import React, { Component } from 'react';
-
import { render } from 'react-dom';
-
-
class GroceryList extends Component {
-
handleClick(i) {
-
console.log( 'You clicked: ' + this.props.items[i]);
-
}
-
-
render() {
-
return (
-
<div>
-
{this.props.items.map((item, i) => {
-
return (
-
<div onClick={this.handleClick.bind(this, i)} key={i}>{item} </div>
-
);
-
})}
-
</div>
-
);
-
}
-
}
-
-
render(
-
<GroceryList items={['Apple', 'Banana', 'Cranberry']} />, mountNode
-
);
非父子组件间的通信
使用全局事件 Pub/Sub 模式,在 componentDidMount 里面订阅事件,在componentWillUnmount 里面取消订阅,当收到事件触发的时候调用setState 更新 UI。
3.7 Ajax请求
组件的数据通常是通过Ajax请求服务器获取的,可以使用componentDidMount方法来设置Ajax请求,等到请求成功,再用this.setState方法重新渲染UI:
-
var UserGist = React.createClass({
-
getInitialState: function() {
-
return {
-
username: '',
-
lastGistUrl: ''
-
}
-
},
-
componentDidMount: function(){
-
$.get( this.props.source, function(result){
-
var lastGist = result[ 0];
-
if ( this.isMounted()) {
-
this.setState({
-
username:lastGist.owner.login,
-
lastGistUrl:lastGist.html_url
-
}
-
);
-
}
-
}.bind( this));
-
},
-
render: function() {
-
return (
-
<div>
-
-
{this.state.username}'s last gist is
-
<a href={this.state.lastGistUrl}>here </a>
-
-
</div>
-
);
-
}
-
});
-
ReactDOM.render(
-
<UserGist source = "https://api.github.com/users/octocat/gists"/>,
-
document.getElementById('example')
-
);