【React全家桶入門之八】使用者編輯與刪除
前文中實現了使用者新增與使用者列表展示的功能,本篇帶大家來完成使用者的編輯與刪除。
新增操作列
編輯與刪除功能都是針對已存在的某一個使用者執行的操作,所以在使用者列表中需要再加一個“操作”列來展現【編輯】與【刪除】這兩個按鈕。
修改/src/pages/UserList.js
檔案,新增方法handleEdit與handleDel,並在table中新增一列:
...
class UserList extends React.Component {
constructor (props) { ... }
componentWillMount () { ... }
handleEdit (user) {
}
handleDel (user) {
}
render () {
const {userList} = this.state;
return (
<HomeLayout title="使用者列表">
<table>
<thead>
<tr>
<th>使用者ID</th>
<th>使用者名稱</th>
<th>性別</th>
<th>年齡</th>
<th >操作</th>
</tr>
</thead>
<tbody>
{
userList.map((user) => {
return (
<tr key={user.id}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.gender}</td>
<td>{user.age}</td>
<td>
<a href="javascript:void(0)" onClick={() => this.handleEdit(user)}>編輯</a>
<a href="javascript:void(0)" onClick={() => this.handleDel(user)}>刪除</a>
</td>
</tr>
);
})
}
</tbody>
</table>
</HomeLayout>
);
}
}
...
點選編輯(刪除)時,會把該行的user物件作為引數傳給handleEdit(handleDel)方法,在handleEdit(handleDel)方法中我們就可以根據傳入的user物件進行相應的操作了。
使用者刪除
使用者刪除比較簡單,先解決它。
在執行刪除資料的操作時,通常需要對操作進行進一步的確認以避免誤刪資料釀成慘劇。
所以在handleDel方法中我們應該先確認使用者是否想要執行刪除操作,在使用者確認後呼叫刪除使用者的介面來刪除使用者:
...
handleDel (user) {
const confirmed = confirm(`確定要刪除使用者 ${user.name} 嗎?`);
if (confirmed) {
fetch('http://localhost:3000/user/' + user.id, {
method: 'delete'
})
.then(res => res.json())
.then(res => {
this.setState({
userList: this.state.userList.filter(item => item.id !== user.id)
});
alert('刪除使用者成功');
})
.catch(err => {
console.error(err);
alert('刪除使用者失敗');
});
}
}
...
使用者編輯
使用者編輯和使用者新增基本上是一樣的,不同的地方有:
- 使用者編輯需要將使用者的資料先填充到表單
- 使用者編輯在提交表單的時候呼叫的介面和方法不同
- 頁面標題不同
- 頁面路由不同
那麼我們可以複製UserAdd.js檔案的程式碼到一個新的UserEdit.js檔案中,再對上述四點進行修改…嗎?
當然不行!在前文中我們費盡心思對重複程式碼進行優化,更不能為了偷懶直接複製程式碼完事啦。
想辦法讓原來的程式碼既能夠支援新增操作又能夠支援編輯操作!
為了達到這一個目標,我們需要:
- 升級formProvider使其返回的表單元件支援傳入表單的值(用於主動填充表單)
- 將UserAdd.js中的大部分程式碼抽離到一個通用元件UserEditor,通過傳入不同的props來控制組件的行為是新增還是編輯
升級formProvider
修改/src/utils/formProvider.js
檔案:
function formProvider (fields) {
return function (Comp) {
...
class FormComponent extends React.Component {
constructor (props) {
...
this.setFormValues = this.setFormValues.bind(this);
}
setFormValues (values) {
if (!values) {
return;
}
const {form} = this.state;
let newForm = {...form};
for (const field in form) {
if (form.hasOwnProperty(field)) {
if (typeof values[field] !== 'undefined') {
newForm[field] = {...newForm[field], value: values[field]};
}
// 正常情況下主動設定的每個欄位一定是有效的
newForm[field].valid = true;
}
}
this.setState({form: newForm});
}
handleValueChange (fieldName, value) { ... }
render () {
const {form, formValid} = this.state;
return (
<Comp
{...this.props}
form={form}
formValid={formValid}
onFormChange={this.handleValueChange}
setFormValues={this.setFormValues}
/>
);
}
}
return FormComponent;
}
}
...
給表單元件傳入了一個setFormValues的方法,用於在元件中主動設定表單的值。
抽離UserEditor
接下來新建/src/components/UserEditor.js
檔案,將表單處理程式碼從UserAdd.js裡搬過去(省略號部分與原來的程式碼相同):
import React from 'react';
import FormItem from '../components/FormItem';
import formProvider from '../utils/formProvider';
class UserEditor extends React.Component {
handleSubmit (e) { ... }
render () {
const {form: {name, age, gender}, onFormChange} = this.props;
return (
<form onSubmit={(e) => this.handleSubmit(e)}>
...
</form>
);
}
}
UserEditor.contextTypes = {
router: React.PropTypes.object.isRequired
};
UserEditor = formProvider({ ... })(UserEditor);
export default UserEditor;
然後再handleSubmit方法中,通過檢查是否收到一個editTarget的props來判斷這次的操作是新增操作還是編輯操作,並根據當前的操作切換呼叫介面的url和method:
...
handleSubmit (e) {
e.preventDefault();
const {form: {name, age, gender}, formValid, editTarget} = this.props;
if (!formValid) {
alert('請填寫正確的資訊後重試');
return;
}
let editType = '新增';
let apiUrl = 'http://localhost:3000/user';
let method = 'post';
if (editTarget) {
editType = '編輯';
apiUrl += '/' + editTarget.id;
method = 'put';
}
fetch(apiUrl, {
method,
body: JSON.stringify({
name: name.value,
age: age.value,
gender: gender.value
}),
headers: {
'Content-Type': 'application/json'
}
})
.then((res) => res.json())
.then((res) => {
if (res.id) {
alert(editType + '使用者成功');
this.context.router.push('/user/list');
return;
} else {
alert(editType + '失敗');
}
})
.catch((err) => console.error(err));
}
...
同時,我們也需要在UserEditor載入的時候檢查是否存在props.editTarget
,如果存在,使用props.setFormValues
方法將editTarget的值設定到表單:
...
componentWillMount () {
const {editTarget, setFormValues} = this.props;
if (editTarget) {
setFormValues(editTarget);
}
}
...
這樣我們的UserEditor就基本完成了,當我們要作為一個使用者新增器使用時,只需要:
...
<UserEditor/>
...
而作為一個使用者編輯器使用時,則需要將編輯的目標使用者物件傳給editTarget這個屬性:
...
<UserEditor editTarget={user}/>
...
所以現在就可以將UserAdd.js檔案改成這樣了:
import React from 'react';
import HomeLayout from '../layouts/HomeLayout';
import UserEditor from '../components/UserEditor';
class UserAdd extends React.Component {
render () {
return (
<HomeLayout title="新增使用者">
<UserEditor/>
</HomeLayout>
);
}
}
export default UserAdd;
新增UserEditPage
現在需要新增一個/src/pages/UserEdit.js
檔案作為編輯使用者的頁面:
import React from 'react';
import HomeLayout from '../layouts/HomeLayout';
import UserEditor from '../components/UserEditor';
class UserEdit extends React.Component {
constructor (props) {
super(props);
this.state = {
user: null
};
}
componentWillMount () {
const userId = this.context.router.params.id;
fetch('http://localhost:3000/user/' + userId)
.then(res => res.json())
.then(res => {
this.setState({
user: res
});
});
}
render () {
const {user} = this.state;
return (
<HomeLayout title="編輯使用者">
{
user ? <UserEditor editTarget={user}/> : '載入中...'
}
</HomeLayout>
);
}
}
UserEdit.contextTypes = {
router: React.PropTypes.object.isRequired
};
export default UserEdit;
在這個頁面元件裡,我們根據路由中名為id的引數(this.context.router.params.id
)來呼叫介面獲取使用者資料(儲存在this.state.user
中)。
當user資料未就緒時,我們不應該展示出編輯器以避免使用者混亂或者誤操作:使用三元運算子,當this.state.user
有值時渲染UserEditor元件,否則顯示文字“載入中…”。
注意:任何使用this.context.xxx的地方,必須在元件的contextTypes裡定義對應的PropTypes。
別忘了在/src/index.js
中給頁面新增路由,路由的path中使用:id來定義路由的引數(引數名與頁面元件中獲取引數時的引數名相對應):
import UserEditPage from './pages/UserEdit';
ReactDOM.render((
<Router history={hashHistory}>
...
<Route path="/user/edit/:id" component={UserEditPage}/>
</Router>
), document.getElementById('app'));
完成handleEdit方法
最後,來補上UserList頁面元件的handleEdit方法:
class UserList extends React.Component {
constructor (props) { ... }
componentWillMount () { ... }
handleEdit (user) {
this.context.router.push('/user/edit/' + user.id);
}
handleDel (user) { ... }
UserList.contextTypes = {
router: React.PropTypes.object.isRequired
};
在handleEdit方法中只需要使用router.push方法跳轉到該使用者的編輯頁面,別忘了加上contextTypes。
看看效果:
大功告成!