前文中实现了用户添加与用户列表展示的功能,本篇带大家来完成用户的编辑与删除。
添加操作列
编辑与删除功能都是针对已存在的某一个用户执行的操作,所以在用户列表中需要再加一个“操作”列来展现【编辑】与【删除】这两个按钮。
修改/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。
看看效果:
大功告成!