## 第十七步:Top 100 组件
这个组件使用Bootstrap的[Media控件](http://getbootstrap.com/components/#media)作为主要的界面,下面是它看起来的样子:
![](https://box.kancloud.cn/2015-09-14_55f6447f33276.jpg)
### Component
在app/components目录新建文件*CharacterList.js*:
~~~
import React from 'react';
import {Link} from 'react-router';
import {isEqual} from 'underscore';
import CharacterListStore from '../stores/CharacterListStore';
import CharacterListActions from '../actions/CharacterListActions';
class CharacterList extends React.Component {
constructor(props) {
super(props);
this.state = CharacterListStore.getState();
this.onChange = this.onChange.bind(this);
}
componentDidMount() {
CharacterListStore.listen(this.onChange);
CharacterListActions.getCharacters(this.props.params);
}
componentWillUnmount() {
CharacterListStore.unlisten(this.onChange);
}
componentDidUpdate(prevProps) {
if (!isEqual(prevProps.params, this.props.params)) {
CharacterListActions.getCharacters(this.props.params);
}
}
onChange(state) {
this.setState(state);
}
render() {
let charactersList = this.state.characters.map((character, index) => {
return (
<div key={character.characterId} className='list-group-item animated fadeIn'>
<div className='media'>
<span className='position pull-left'>{index + 1}</span>
<div className='pull-left thumb-lg'>
<Link to={'/characters/' + character.characterId}>
<img className='media-object' src={'http://image.eveonline.com/Character/' + character.characterId + '_128.jpg'} />
</Link>
</div>
<div className='media-body'>
<h4 className='media-heading'>
<Link to={'/characters/' + character.characterId}>{character.name}</Link>
</h4>
<small>Race: <strong>{character.race}</strong></small>
<br />
<small>Bloodline: <strong>{character.bloodline}</strong></small>
<br />
<small>Wins: <strong>{character.wins}</strong> Losses: <strong>{character.losses}</strong></small>
</div>
</div>
</div>
);
});
return (
<div className='container'>
<div className='list-group'>
{charactersList}
</div>
</div>
);
}
}
CharacterList.contextTypes = {
router: React.PropTypes.func.isRequired
};
export default CharacterList;
~~~
鉴于角色数组已经按照胜率进行了排序,我们可以使用`index + 1`(从1到100)来作为数组下标直接输出角色。
### Actions
在app/actions目录新建*CharacterListActions.js*:
~~~
import alt from '../alt';
class CharacterListActions {
constructor() {
this.generateActions(
'getCharactersSuccess',
'getCharactersFail'
);
}
getCharacters(payload) {
let url = '/api/characters/top';
let params = {
race: payload.race,
bloodline: payload.bloodline
};
if (payload.category === 'female') {
params.gender = 'female';
} else if (payload.category === 'male') {
params.gender = 'male';
}
if (payload.category === 'shame') {
url = '/api/characters/shame';
}
$.ajax({ url: url, data: params })
.done((data) => {
this.actions.getCharactersSuccess(data);
})
.fail((jqXhr) => {
this.actions.getCharactersFail(jqXhr);
});
}
}
export default alt.createActions(CharacterListActions);
~~~
这里的`payload`包含React Router参数,这些参数我们将在routes.js中指定。
~~~
<Route path=':category' handler={CharacterList}>
<Route path=':race' handler={CharacterList}>
<Route path=':bloodline' handler={CharacterList} />
</Route>
</Route>
~~~
比如,如果我们访问[http://localhost:3000/female/gallente/intaki](http://localhost:3000/female/gallente/intaki) ,则`payload`对象将包括下列数据:
~~~
{
category: 'female',
race: 'gallente',
bloodline: 'intaki'
}
~~~
### Store
在app/store目录下新建文件*CharacterListStore.js*:
~~~
import alt from '../alt';
import CharacterListActions from '../actions/CharacterListActions';
class CharacterListStore {
constructor() {
this.bindActions(CharacterListActions);
this.characters = [];
}
onGetCharactersSuccess(data) {
this.characters = data;
}
onGetCharactersFail(jqXhr) {
toastr.error(jqXhr.responseJSON.message);
}
}
export default alt.createStore(CharacterListStore);/pre>
打开route.js并添加下列路由。所有内嵌路由都使用动态区段,所以不用重复输入。确保它们在路由的最后面,否则:category将会覆盖其它路由如/stats、/add和/shame。不要忘了导入CharacterList组件:
~~~
~~~
import React from 'react';
import {Route} from 'react-router';
import App from './components/App';
import Home from './components/Home';
import AddCharacter from './components/AddCharacter';
import Character from './components/Character';
import CharacterList from './components/CharacterList';
export default (
<Route handler={App}>
<Route path='/' handler={Home} />
<Route path='/add' handler={AddCharacter} />
<Route path='/characters/:id' handler={Character} />
<Route path='/shame' handler={CharacterList} />
<Route path=':category' handler={CharacterList}>
<Route path=':race' handler={CharacterList}>
<Route path=':bloodline' handler={CharacterList} />
</Route>
</Route>
</Route>
);
~~~
下面是所有动态区段可以取的值:
* `:category` — male, female, top.
* `:race` — caldari, gallente, minmatar, amarr.
* `:bloodline` — civire, deteis, achura, intaki, gallente, jin-mei, amarr, ni-kunni, khanid, brutor, sebiestor, vherokior.
可以看到,如果我们使用硬编码的话,将如此多的路由包含进去将使route.js变得很长很长。
- 前言
- 概述
- 第一步:新建Express项目
- 第二步:构建系统
- 第三步:项目结构
- 第四步: ES6速成教程
- 第五步: React速成教程
- 第六步:Flux架构速成教程
- 第七步:React路由(客户端)
- 第八步:React路由(服务端)
- 第九步:Footer和Navbar组件
- 第十步:Socke.IO – 实时用户数
- 第十一步:添加Character的组件
- 第十二步:数据库模式
- 第十三步:Express API 路由(1/2)
- 第十五步:Home组件
- 第十四步:Express API 路由(2/2)
- 第十六步:角色(资料)组件
- 第十七步:Top 100 组件
- 第十八步:Stats组件
- 第十九步:部署
- 第二十步: 附加资源
- 总结