第十五步:Home组件
最后更新于:2022-04-01 03:19:32
## 第十五步:Home组件
这是一个稍微简单些的组件,它唯一的职责就是显示两张图片并且处理点击事件,用于告知哪个角色胜出。
### 组件
在components目录下新建文件*Home.js*:
~~~
import React from 'react';
import {Link} from 'react-router';
import HomeStore from '../stores/HomeStore'
import HomeActions from '../actions/HomeActions';
import {first, without, findWhere} from 'underscore';
class Home extends React.Component {
constructor(props) {
super(props);
this.state = HomeStore.getState();
this.onChange = this.onChange.bind(this);
}
componentDidMount() {
HomeStore.listen(this.onChange);
HomeActions.getTwoCharacters();
}
componentWillUnmount() {
HomeStore.unlisten(this.onChange);
}
onChange(state) {
this.setState(state);
}
handleClick(character) {
var winner = character.characterId;
var loser = first(without(this.state.characters, findWhere(this.state.characters, { characterId: winner }))).characterId;
HomeActions.vote(winner, loser);
}
render() {
var characterNodes = this.state.characters.map((character, index) => {
return (
<div key={character.characterId} className={index === 0 ? 'col-xs-6 col-sm-6 col-md-5 col-md-offset-1' : 'col-xs-6 col-sm-6 col-md-5'}>
<div className='thumbnail fadeInUp animated'>
<img onClick={this.handleClick.bind(this, character)} src={'http://image.eveonline.com/Character/' + character.characterId + '_512.jpg'}/>
<div className='caption text-center'>
<ul className='list-inline'>
<li><strong>Race:</strong> {character.race}</li>
<li><strong>Bloodline:</strong> {character.bloodline}</li>
</ul>
<h4>
<Link to={'/characters/' + character.characterId}><strong>{character.name}</strong></Link>
</h4>
</div>
</div>
</div>
);
});
return (
<div className='container'>
<h3 className='text-center'>Click on the portrait. Select your favorite.</h3>
<div className='row'>
{characterNodes}
</div>
</div>
);
}
}
export default Home;
~~~
2015年7月27日更新:修复“Cannot read property ‘characterId’ of undefined”错误,我更新了在`handleClick()`方法里获取“失败”的Character ID。它使用[`_.findWhere`](http://underscorejs.org/#findWhere)在数组里查找“获胜”的角色对象,然后使用[`_.without`](http://underscorejs.org/#without)获取不包含“获胜”角色的数组,因为数组只包含两个角色,所以这就是我们需要的,然后使用[`_.first`](http://underscorejs.org/#first)获取数组第一个元素,也就是我们需要的对象。
鉴于角色数组只有两个元素,其实没有必要非要使用map方法不可,虽然这也能达到我们的目的。另一种做法是为`characters[0]`和`characters[1]`各自创建标记。
~~~
render() {
return (
<div className='container'>
<h3 className='text-center'>Click on the portrait. Select your favorite.</h3>
<div className='row'>
<div className='col-xs-6 col-sm-6 col-md-5 col-md-offset-1'>
<div className='thumbnail fadeInUp animated'>
<img onClick={this.handleClick.bind(this, characters[0])} src={'http://image.eveonline.com/Character/' + characters[0].characterId + '_512.jpg'}/>
<div className='caption text-center'>
<ul className='list-inline'>
<li><strong>Race:</strong> {characters[0].race}</li>
<li><strong>Bloodline:</strong> {characters[0].bloodline}</li>
</ul>
<h4>
<Link to={'/characters/' + characters[0].characterId}><strong>{characters[0].name}</strong></Link>
</h4>
</div>
</div>
</div>
<div className='col-xs-6 col-sm-6 col-md-5'>
<div className='thumbnail fadeInUp animated'>
<img onClick={this.handleClick.bind(this, characters[1])} src={'http://image.eveonline.com/Character/' + characters[1].characterId + '_512.jpg'}/>
<div className='caption text-center'>
<ul className='list-inline'>
<li><strong>Race:</strong> {characters[1].race}</li>
<li><strong>Bloodline:</strong> {characters[1].bloodline}</li>
</ul>
<h4>
<Link to={'/characters/' + characters[1].characterId}><strong>{characters[1].name}</strong></Link>
</h4>
</div>
</div>
</div>
</div>
</div>
);
}
~~~
第一张图片使用Bootstrap中的`col-md-offset-1`位移,所以两张图片是完美居中的。
注意我们在点击事件上绑定的不是`this.handleClick`,而是`this.handleClick.bind(this, character)`。简单的传递一个事件对象是不够的,它不会给我们任何有用的信息,不像文本字段、单选、复选框元素等。
[MSDN文档](https://msdn.microsoft.com/en-us/library/ff841995%28v=vs.94%29.ASPx?f=255&MSPPError=-2147217396)中的解释:
~~~
function.bind(thisArg[, arg1[, arg2[, ...]]])
~~~
* thisARG(必须) – 使用this的一个对象,能在新函数内部指向当前对象的上下文
* arg1, arg2, … (可选) – 传递给新函数的一系列参数
简单的来说,因为我们需要在`handleClick`方法里引用`this.state`,所以需要将`this`上下文传递进去。另外我们还传递了被点击的角色对象,而不是当前的event对象。
`handleClick`方法里的`character`参数代表的是获胜的角色,因为它是被点击的那一个。因为我们仅有两个角色需要判断,所以不难分辨谁是输的那个。接下来将获胜和失败的角色Character ID传递给`Character ID` action。
### Actions
在actions目录下新建*HomeActions.js*:
~~~
import alt from '../alt';
class HomeActions {
constructor() {
this.generateActions(
'getTwoCharactersSuccess',
'getTwoCharactersFail',
'voteFail'
);
}
getTwoCharacters() {
$.ajax({ url: '/api/characters' })
.done(data => {
this.actions.getTwoCharactersSuccess(data);
})
.fail(jqXhr => {
this.actions.getTwoCharactersFail(jqXhr.responseJSON.message);
});
}
vote(winner, loser) {
$.ajax({
type: 'PUT',
url: '/api/characters' ,
data: { winner: winner, loser: loser }
})
.done(() => {
this.actions.getTwoCharacters();
})
.fail((jqXhr) => {
this.actions.voteFail(jqXhr.responseJSON.message);
});
}
}
export default alt.createActions(HomeActions);
~~~
这里我们不需要`voteSuccess` action,因为`getTwoCharacters`已经满足了我们的需求。换句话说,在一次成功的投票之后,我们需要从数据库获取两个新的随机角色显示出来。
### Store
在stores目录下新建文件*HomeStore.js*:
~~~
import alt from '../alt';
import HomeActions from '../actions/HomeActions';
class HomeStore {
constructor() {
this.bindActions(HomeActions);
this.characters = [];
}
onGetTwoCharactersSuccess(data) {
this.characters = data;
}
onGetTwoCharactersFail(errorMessage) {
toastr.error(errorMessage);
}
onVoteFail(errorMessage) {
toastr.error(errorMessage);
}
}
export default alt.createStore(HomeStore);
~~~
下一步,让我们实现剩下的Express路由,来获取并更新Home组件中的两个角色、获得总角色数量等等。