## 第十一步:添加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在輸入框,這樣用戶可以直接輸入而無需點擊一下輸入框。

### 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. 使用新的名稱更新狀態。

在下一節,我們將實現添加和保存新character到數據庫的后端代碼。
- 前言
- 概述
- 第一步:新建Express項目
- 第二步:構建系統
- 第三步:項目結構
- 第四步: ES6速成教程
- 第五步: React速成教程
- 第六步:Flux架構速成教程
- 第七步:React路由(客戶端)
- 第八步:React路由(服務端)
- 第九步:Footer和Navbar組件
- 第十步:Socke.IO – 實時用戶數
- 第十一步:添加Character的組件
- 第十二步:數據庫模式
- 第十三步:Express API 路由(1/2)
- 第十五步:Home組件
- 第十四步:Express API 路由(2/2)
- 第十六步:角色(資料)組件
- 第十七步:Top 100 組件
- 第十八步:Stats組件
- 第十九步:部署
- 第二十步: 附加資源
- 總結