## 第十一步:添加Character的组件
这个组件包含一个简单的表单。成功或失败的消息会显示在输入框下的`help-block`里。
### 组件
在app/components目录新建文件*AddCharacter.js*:
~~~
import React from 'react';
import AddCharacterStore from '../stores/AddCharacterStore';
import AddCharacterActions from '../actions/AddCharacterActions';
class AddCharacter extends React.Component {
constructor(props) {
super(props);
this.state = AddCharacterStore.getState();
this.onChange = this.onChange.bind(this);
}
componentDidMount() {
AddCharacterStore.listen(this.onChange);
}
componentWillUnmount() {
AddCharacterStore.unlisten(this.onChange);
}
onChange(state) {
this.setState(state);
}
handleSubmit(event) {
event.preventDefault();
var name = this.state.name.trim();
var gender = this.state.gender;
if (!name) {
AddCharacterActions.invalidName();
this.refs.nameTextField.getDOMNode().focus();
}
if (!gender) {
AddCharacterActions.invalidGender();
}
if (name && gender) {
AddCharacterActions.addCharacter(name, gender);
}
}
render() {
return (
<div className='container'>
<div className='row flipInX animated'>
<div className='col-sm-8'>
<div className='panel panel-default'>
<div className='panel-heading'>Add Character</div>
<div className='panel-body'>
<form onSubmit={this.handleSubmit.bind(this)}>
<div className={'form-group ' + this.state.nameValidationState}>
<label className='control-label'>Character Name</label>
<input type='text' className='form-control' ref='nameTextField' value={this.state.name}
onChange={AddCharacterActions.updateName} autoFocus/>
<span className='help-block'>{this.state.helpBlock}</span>
</div>
<div className={'form-group ' + this.state.genderValidationState}>
<div className='radio radio-inline'>
<input type='radio' name='gender' id='female' value='Female' checked={this.state.gender === 'Female'}
onChange={AddCharacterActions.updateGender}/>
<label htmlFor='female'>Female</label>
</div>
<div className='radio radio-inline'>
<input type='radio' name='gender' id='male' value='Male' checked={this.state.gender === 'Male'}
onChange={AddCharacterActions.updateGender}/>
<label htmlFor='male'>Male</label>
</div>
</div>
<button type='submit' className='btn btn-primary'>Submit</button>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default AddCharacter;
~~~
现在你可以看到这些组件的一些共同点:
1. 设置组件的初始状态为store中的值。
2. 在`componentDidMount`中添加store监听者,在`componentWillUnmount`中移除。
3. 添加`onChange`方法,无论何时当store改变后更新组件状态。
`handleSubmit`方法的作用和你想的一样——处理添加新角色的表单提交。当它为真时我们能在`addCharacter` action里完成表单验证,不过这样做的话,需要我们将输入区的DOM节点传到action,因为当`nameTextField`无效时,需要focus在输入框,这样用户可以直接输入而无需点击一下输入框。
![](https://box.kancloud.cn/2015-09-14_55f64389a1e14.jpg)
### Actions
在app/actions目录新建*AddCharacterActions.js*:
~~~
import alt from '../alt';
class AddCharacterActions {
constructor() {
this.generateActions(
'addCharacterSuccess',
'addCharacterFail',
'updateName',
'updateGender',
'invalidName',
'invalidGender'
);
}
addCharacter(name, gender) {
$.ajax({
type: 'POST',
url: '/api/characters',
data: { name: name, gender: gender }
})
.done((data) => {
this.actions.addCharacterSuccess(data.message);
})
.fail((jqXhr) => {
this.actions.addCharacterFail(jqXhr.responseJSON.message);
});
}
}
export default alt.createActions(AddCharacterActions);
~~~
当角色被成功加入数据库后触发`addCharacterSuccess`,当失败时触发`addCharacterFail`,失败的原因可能是无效的名字,或角色已经在数据库中存在了。当角色的Name字段和Gender单选框改变时由`onChange`触发`updateName`和`updateGender`,同样的,当输入的名字无效或没有选择性别时触发`invalidName`和`invalidGender`。
### Store
在app/stores目录新建*AddCharacterStore.js*:
~~~
import alt from '../alt';
import AddCharacterActions from '../actions/AddCharacterActions';
class AddCharacterStore {
constructor() {
this.bindActions(AddCharacterActions);
this.name = '';
this.gender = '';
this.helpBlock = '';
this.nameValidationState = '';
this.genderValidationState = '';
}
onAddCharacterSuccess(successMessage) {
this.nameValidationState = 'has-success';
this.helpBlock = successMessage;
}
onAddCharacterFail(errorMessage) {
this.nameValidationState = 'has-error';
this.helpBlock = errorMessage;
}
onUpdateName(event) {
this.name = event.target.value;
this.nameValidationState = '';
this.helpBlock = '';
}
onUpdateGender(event) {
this.gender = event.target.value;
this.genderValidationState = '';
}
onInvalidName() {
this.nameValidationState = 'has-error';
this.helpBlock = 'Please enter a character name.';
}
onInvalidGender() {
this.genderValidationState = 'has-error';
}
}
export default alt.createStore(AddCharacterStore);
~~~
`nameValidationState`和`genderValidationState`指向Bootstrap提供的代表验证状态的表单控件。
`helpBlock`是在输入框下显示的状态信息,如“Character has been added successfully”。
`onInvalidName`方法当Character Name字段为空时触发。如果name在EVE中不存在,将由`onAddCharacterFail`输出另一个错误信息。
最后,打开routes.js并添加新的路由`/add`,以及`AddCharacter`组件方法:
~~~
import React from 'react';
import {Route} from 'react-router';
import App from './components/App';
import Home from './components/Home';
import AddCharacter from './components/AddCharacter';
export default (
<Route handler={App}>
<Route path='/' handler={Home} />
<Route path='/add' handler={AddCharacter} />
</Route>
);
~~~
这里简单总结了从你输入角色名称开始的整个流程:
1. 触发`updateName` action,传递event对象。
2. 调用`onUpdateName` store处理程序。
3. 使用新的名称更新状态。
![](https://box.kancloud.cn/2015-09-14_55f64389d5e20.jpg)
在下一节,我们将实现添加和保存新character到数据库的后端代码。
- 前言
- 概述
- 第一步:新建Express项目
- 第二步:构建系统
- 第三步:项目结构
- 第四步: ES6速成教程
- 第五步: React速成教程
- 第六步:Flux架构速成教程
- 第七步:React路由(客户端)
- 第八步:React路由(服务端)
- 第九步:Footer和Navbar组件
- 第十步:Socke.IO – 实时用户数
- 第十一步:添加Character的组件
- 第十二步:数据库模式
- 第十三步:Express API 路由(1/2)
- 第十五步:Home组件
- 第十四步:Express API 路由(2/2)
- 第十六步:角色(资料)组件
- 第十七步:Top 100 组件
- 第十八步:Stats组件
- 第十九步:部署
- 第二十步: 附加资源
- 总结