添加操作列

编辑与删除功能都是针对已存在的某一个用户执行的操作,所以在用户列表中需要再加一个“操作”列来展现【编辑】与【删除】这两个按钮。

修改/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 = window.confirm(`确定要删除用户 ${user.name} 吗?`); // confirm 无法识别,需要加 window. if (confirmed) {
fetch('http://localhost:8000/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的方法,用于在组件中主动设置表单的值。

完整代码(高阶组件):

src / utils / formProvider.js

/**
* 高阶组件 formProvider
* 返回组件的组件(函数)
* 使用高阶组件可以在不修改原组件代码的情况下,修改原组件的行为或增强功能
*/
import React from 'react'; function formProvider (fields) { // fields 对象
return function(Comp) { // Comp
/**
* 定义常量
* 初始表单状态
*/
const initialFormState = {};
// 循环
for(const key in fields){
initialFormState[key] = {
value: fields[key].defaultValue,
error: ''
};
} // 创建组件
class FormComponent extends React.Component {
// 构造器
constructor(props) {
super(props);
// 定义初始状态
this.state = {
form: initialFormState,
formValid: false // 加了一个formValid用来保存整个表单的校验状态
};
// 输入框改变事件 绑定this
this.handleValueChange = this.handleValueChange.bind(this);
// 设置表单的值
this.setFormValues = this.setFormValues.bind(this);
}
// 输入框改变事件
handleValueChange(fieldName, value){
// 定义常量
const { form } = this.state; const newFieldState = {value, valid: true, error: ''}; const fieldRules = fields[fieldName].rules;
// 循环
for(let i=0; i<fieldRules.length; i++){
const {pattern, error} = fieldRules[i];
let valid = false;
if(typeof pattern === 'function'){
valid = pattern(value);
}else{
valid = pattern.test(value);
} if(!valid){
newFieldState.valid = false;
newFieldState.error = error;
break;
}
}
/**
* ... 扩展运算符
* 将一个数组转为用逗号分隔的参数序列
*/
const newForm = {...form, [fieldName]: newFieldState};
/**
* every
* 对数组中的每个元素都执行一次指定的函数,直到此函数返回 false
* 如果发现这个元素,every 将返回 false
* 如果回调函数对每个元素执行后都返回 true,every 将返回 true
*/
const formValid = Object.values(newForm).every(f => f.valid);
// 设置状态
this.setState({
form: newForm,
formValid
});
} /**
* 设置表单的值
*/
setFormValues(values){
if(!values){
return;
} const { form } = this.state;
/**
* form 表单对象
* ...扩展运算符
*/
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});
} render(){
const { form, formValid } = this.state;
return (
<Comp
{...this.props}
form={form}
formValid={formValid}
onFormChange={this.handleValueChange}
setFormValues={this.setFormValues} />
);
}
}
// 返回组件
return FormComponent;
}
} export default formProvider;

抽离UserEditor

接下来新建/src/components/UserEditor.js文件,将表单处理代码从UserAdd.js里搬过去(省略号部分与原来的代码相同):

import React from 'react';
import FormItem from '../components/FormItem'; // 或者写成 ./FormItem
import formProvider from '../utils/formProvider';
// 引入 prop-types
import PropTypes from 'prop-types'; 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: 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:8000/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}/>
...

完成代码(编辑器组件):

src / components / UserEditor.js

/**
* 编辑器组件
*/
import React from 'react';
import FormItem from '../components/FormItem'; // 或写成 ./FormItem
// 高阶组件 formProvider表单验证
import formProvider from '../utils/formProvider';
// 引入 prop-types
import PropTypes from 'prop-types'; class UserEditor extends React.Component {
// 按钮提交事件
handleSubmit(e){
// 阻止表单submit事件自动跳转页面的动作
e.preventDefault();
// 定义常量
const { form: { name, age, gender }, formValid, editTarget} = this.props; // 组件传值
// 验证
if(!formValid){
alert('请填写正确的信息后重试');
return;
} // 默认值
let editType = '添加';
let apiUrl = 'http://localhost:8000/user';
let method = 'post';
// 判断类型
if(editTarget){
editType = '编辑';
apiUrl += '/' + editTarget.id;
method = 'put';
} // 发送请求
fetch(apiUrl, {
method, // method: method 的简写
// 使用fetch提交的json数据需要使用JSON.stringify转换为字符串
body: JSON.stringify({
name: name.value,
age: age.value,
gender: gender.value
}),
headers: {
'Content-Type': 'application/json'
}
})
// 强制回调的数据格式为json
.then((res) => res.json())
// 成功的回调
.then((res) => {
// 当添加成功时,返回的json对象中应包含一个有效的id字段
// 所以可以使用res.id来判断添加是否成功
if(res.id){
alert(editType + '添加用户成功!');
this.context.router.push('/user/list'); // 跳转到用户列表页面
return;
}else{
alert(editType + '添加用户失败!');
}
})
// 失败的回调
.catch((err) => console.error(err));
} // 生命周期--组件加载中
componentWillMount(){
const {editTarget, setFormValues} = this.props;
if(editTarget){
setFormValues(editTarget);
}
} render() {
// 定义常量
const {form: {name, age, gender}, onFormChange} = this.props;
return (
<form onSubmit={(e) => this.handleSubmit(e)}>
<FormItem label="用户名:" valid={name.valid} error={name.error}>
<input
type="text"
value={name.value}
onChange={(e) => onFormChange('name', e.target.value)}/>
</FormItem> <FormItem label="年龄:" valid={age.valid} error={age.error}>
<input
type="number"
value={age.value || ''}
onChange={(e) => onFormChange('age', e.target.value)}/>
</FormItem> <FormItem label="性别:" valid={gender.valid} error={gender.error}>
<select
value={gender.value}
onChange={(e) => onFormChange('gender', e.target.value)}>
<option value="">请选择</option>
<option value="male">男</option>
<option value="female">女</option>
</select>
</FormItem>
<br />
<input type="submit" value="提交" />
</form>
);
}
} // 必须给UserEditor定义一个包含router属性的contextTypes
// 使得组件中可以通过this.context.router来使用React Router提供的方法
UserEditor.contextTypes = {
router: PropTypes.object.isRequired
}; // 实例化
UserEditor = formProvider({ // field 对象
// 姓名
name: {
defaultValue: '',
rules: [
{
pattern: function (value) {
return value.length > 0;
},
error: '请输入用户名'
},
{
pattern: /^.{1,4}$/,
error: '用户名最多4个字符'
}
]
},
// 年龄
age: {
defaultValue: 0,
rules: [
{
pattern: function(value){
return value >= 1 && value <= 100;
},
error: '请输入1~100的年龄'
}
]
},
// 性别
gender: {
defaultValue: '',
rules: [
{
pattern: function(value) {
return !!value;
},
error: '请选择性别'
}
]
}
})(UserEditor); export default UserEditor;

所以现在就可以将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';
// 引入 prop-types
import PropTypes from 'prop-types';
// 编辑组件
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:8000/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: 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('root'));

完成handleEdit方法

最后,来补上UserList页面组件的handleEdit方法:

import PropTypes from 'prop-types';

class UserList extends React.Component {
constructor (props) { ... } componentWillMount () { ... }
/**
* 编辑
*/
handleEdit (user) {
// 跳转编辑页面
this.context.router.push('/user/edit/' + user.id);
} handleDel (user) { ... } /**
* 任何使用this.context.xxx的地方,必须在组件的contextTypes里定义对应的PropTypes
*/
UserList.contextTypes = {
router: PropTypes.object.isRequired
};

在handleEdit方法中只需要使用router.push方法跳转到该用户的编辑页面,别忘了加上contextTypes。

项目目录:

最新文章

  1. CSS3 GRID LAYOUT
  2. Eclipse将引用了第三方jar包的Java项目打包成jar文件的两种方法
  3. where子句的使用
  4. [Javascript] Decorators in JavaScript
  5. BP神经网络学习笔记_附源代码
  6. mahout算法源码分析之Itembased Collaborative Filtering(四)共生矩阵乘法
  7. c++各种数据类型表示范围
  8. 用grunt搭建自动化的web前端开发环境-完整教程
  9. 在ecshop商品详情页显示供货商
  10. [BZOJ 3209] 花神的数论题 【数位统计】
  11. BZOJ 3040 最短路 (堆优化dijkstra)
  12. ffplay for mfc 代码备忘录
  13. Underscore.js 的模板功能介绍与应用
  14. 并查集-HDU1232-畅通工程
  15. day 9~11 函数
  16. 六、latex中的特殊字符
  17. Redis】Java中使用Jedis操作Redis(Maven导入包)、创建Redis连接池
  18. Selenium基础知识(七)弹出框处理
  19. Fiddler无法抓取某些APP的HTTPS请求,无解!!!
  20. 去除TFS版本控制

热门文章

  1. spring mvc介绍只试图解析(转载)
  2. mysql group_concat函数详解
  3. 【原】简单shell练习(二)
  4. js中间件
  5. Java 调用存储过程 返回结果集
  6. RN code push自定义弹框
  7. 简述站点访问控制、基于用户的访问控制、httpd虚拟主机、持久链接等应用配置实例
  8. Python基础—线程、进程和协程
  9. Qt5笔记之数据库(五)SQL表格模型QSqlTableModel
  10. xtu summer individual-4 D - Martian Strings