示例代码放置在 demo/目录下,每个文件夹为一个独立的示例。
先看下这个 demo 最终的样子吧:
每个示例的入口文件 index.html 结构大体相同:
<!-- React 真实 DOM 将会插入到这里 -->
<div id="demo"></div>
<!-- 引入 React -->
<script src="../../bower_components/react/react.js"></script>
<!-- 引入 JSX 语法格式转换器 -->
<script src="../../bower_components/react/JSXTransformer.js"></script>
<!-- 注意:script 需要注明 type 为 text/jsx 以指定这是一个 JSX 语法格式 -->
<script type="text/jsx" src="demo.js"></script>
</body>
使用 render() 方法生成真实 DOM 并插入到网页中。
// 使用 React.createClass 创建一个组件
var DemoComponent = React.createClass({
// 使用 render 方法自动渲染 DOM
render: function () {
return (
<div className="component-hello">
<h1 className="hello-title">Hello React</h1>
<p className="hello-desc">React 初探</p>
<div className="hello-movies">
<p2>我喜欢的电影</p2>
<ul>
<li className="movie-item">
<span className="movie-name">速度与激情7</span>
-
<span className="movie-date">2015</span>
</li>
</ul>
</div>
</div>
)
}
});
// 将组件插入到网页中指定的位置
React.render(<DemoComponent />, document.getElementById('demo'));
第一次渲染真实 DOM 时将使用 getInitialState() 返回的数据。
// 使用 React.createClass 创建一个组件
var DemoComponent = React.createClass({
// getInitialState 中返回的值将会作为数据的默认值
getInitialState: function () {
return {
title: '我喜欢的电影',
movies: [
{
id: 7,
name: '速度与激情7',
date: 2015
},
{
id: 6,
name: '速度与激情6',
date: 2013
}
]
}
},
// 使用 render 方法自动渲染 DOM
render: function () {
// this.state 用于存储数据
var title = this.state.title;
var movies = this.state.movies.map(function (movie) {
return (
<li className="movie-item" key={movie.id}>
<span className="movie-name">{movie.name}</span>
-
<span className="movie-date">{movie.date}</span>
</li>
)
});
return (
<div className="component-hello">
<h1 className="hello-title">Hello React</h1>
<p className="hello-desc">React 初探</p>
<div className="hello-movies">
<p2>{title}</p2>
<ul>{movies}</ul>
</div>
</div>
)
}
});
// 将组件插入到网页中指定的位置
React.render(<DemoComponent />, document.getElementById('demo'));
第二次更新渲染真实 DOM 时将使用 setState() 设置的数据。
// 使用 componentDidMount 在组件初始化后执行一些操作
componentDidMount: function () {
// 拉取远程数据
// 开启假数据服务器:
// cd fake-server && npm install && node index.js
this.fetchData();
},
// 使用自定义的 fetchData 方法从远程服务器获取数据
fetchData: function () {
var self = this;
// 发起 ajax 获取到数据后调用 setState 方法更新组件的数据
var url = '../../fake-data/movies.json';
$.getJSON(url, function (movies) {
// 本地模拟返回太快了,模拟一下网络延迟
setTimeout(function() {
self.setState({
movies: movies
});
}, 2000);
});
},
绑定事件时,我们可以使用 ref="name" 属性对一个 DOM 节点进行标记,同时可以通过 React.findDOMNode(this.refs.name) 获取到这个节点的原生 DOM。
// 使用 render 方法自动渲染 DOM
render: function () {
var self = this;
// this.state 用于存储数据
var title = this.state.title;
var movies = this.state.movies.map(function (movie) {
return (
<li className="movie-item" key={movie.id}>
<span className="movie-name">{movie.name}</span>
-
<span className="movie-date">{movie.date}</span>
<a href="#" onClick={self.onRemove.bind(null, movie)}>删除</a>
</li>
)
}.bind(this));// 注意这里 bind(this) 修正了上下文
return (
<div className="component-hello">
<h1 className="hello-title">Hello React</h1>
<p className="hello-desc">React 初探</p>
<div className="hello-movies">
<p2>{title}</p2>
<form onSubmit={this.onAdd}>
{/* 注意这里指定 ref 属性,然后我们就可以使用 this.refs.xxx 访问到 */}
<input type="text" ref="name" placehlder="输入你喜欢的电影"/>
<input type="text" ref="date" placeholder="上映时间"/>
<input type="submit" value="提交"/>
</form>
<ul>{movies}</ul>
{this.state.loading ? <div>大家好我是菊花, 我现在在转</div> : null}
</div>
</div>
)
}
onRemove: function (movie) {
var id = movie.id;
console.log(movie)
// 删除这个 item
var movies = this.state.movies;
var len = movies.length;
var index = -1;
for(var i = 0; i < len; i++) {
var _movie = movies[i];
if (_movie.id === id) {
index = i;
break;
}
}
if (index > 0) {
movies.splice(index, 1);
this.setState({
movies: movies
});
}
},
onAdd: function (e) {
e.preventDefault();
var refs = this.refs;
var refName = React.findDOMNode(refs.name);
var refDate = React.findDOMNode(refs.date);
if (refName.value === '') {
alert('请输入电影名');
return;
} else if (refDate === '') {
alert('请输入上映时间');
return;
}
var movie = {
// 使用 findDOMNode 获取到原生的 DOM 对象
name: refName.value,
date: refDate.value,
id: Date.now() // 粗暴地以时间数字作为随机 id
};
var movies = this.state.movies;
movies.push(movie);
this.setState(movies);
refName.value = '';
refDate.value = '';
},
一个组件就包含了 JSX 模板、数据维护、事件绑定的话,代码量已经够多了,这时候可以采用 AMD/CMD 的方式,将组件进行更细粒度的划分,可以以文件即组件的方式来编写,这里就不上 demo 了。
在 React 中,数据流是单向的,且组件之间可以嵌套,我们可以通过对最顶层组件传递属性方式,向下层组件传送数据。
-
嵌套组件间,使用 this.props 属性向下传递数据
-
独立组件之间,自行维护数据则需要自行维护一个全局数据存储,或者使用发布订阅地方式通知数据的更新。
全局数据存储怎么做呢?可以理解为不同的组件获取的数据源一致,在组件的外部维护这个数据集合,或者干脆直接从服务器端获取。
有人会说了,这样很不方便。
但我觉得,既然是一个组件,那就配套有获取组件所需数据的方式,独立组件间有很强的数据依赖时,要么使用上述方式,要么可以简单粗暴,将独立组件用一个顶层组件包裹起来,转化为嵌套组件的关系,即可数据互通。
// 将子组件抽离出来
var LiWrapper = React.createClass({
render: function () {
// 使用 this.props 获得传入组件的数据
var movie = this.props.movie;
return (
<li>{/* ... */}</li>
)
}
});
// 使用 React.createClass 创建一个组件
var DemoComponent = React.createClass({
// 使用 getInitialState 的返回值作为数据的默认值
getInitialState: function () {
// ...
},
// 使用 render 方法自动渲染 DOM
render: function () {
// this.state 用于存储数据
var movies = this.state.movies.map(function (movie) {
return (
<LiWrapper movie={movie}/>
)
}.bind(this));// 注意这里 bind(this) 修正了上下文
return (
<div className="component-hello">
{/* ... */}
<div className="hello-movies">
<ul>{movies}</ul>
</div>
</div>
)
}
});
// 将组件插入到网页中指定的位置
// 在使用组件时传入 movies 数据
var movies = [// ...];
React.render(<DemoComponent movies={movies}/>, document.getElementById('demo'));