添加操作列

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

修改/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。

项目目录:

react 项目实战(七)用户编辑与删除的更多相关文章

  1. React项目实战:react-redux-router基本原理

    React相关 React 是一个采用声明式,高效而且灵活的用来构建用户界面的框架. JSX 本质上来讲,JSX 只是为React.createElement(component, props, .. ...

  2. 《Node+MongoDB+React 项目实战开发》已出版

    前言 从深圳回长沙已经快4个月了,除了把车开熟练了外,并没有什么值得一提的,长沙这边要么就是连续下一个月雨,要么就是连续一个月高温暴晒,上班更是没啥子意思,长沙这边的公司和深圳落差挺大的,薪资也是断崖 ...

  3. Hadoop项目实战-用户行为分析之应用概述(三)

    1.概述 本课程的视频教程地址:<项目工程准备> 本节给大家分享的主题如下图所示: 下面我开始为大家分享今天的第三节的内容——<项目工程准备>,接下来开始分享今天的内容. 2. ...

  4. Hadoop项目实战-用户行为分析之应用概述(一)

    1.概述 本课程的视频教程地址:<Hadoop 回顾> 好的,下面就开始本篇教程的内容分享,本篇教程我为大家介绍我们要做一个什么样的Hadoop项目,并且对Hadoop项目的基本特点和其中 ...

  5. Hadoop项目实战-用户行为分析之应用概述(二)

    1.概述 本课程的视频教程地址:<项目整体概述> 本节给大家分享的主题如下图所示: 下面我开始为大家分享第二节的内容——<项目整体概述>,下面开始今天的分享内容. 2.内容 从 ...

  6. 【SSH网上商城项目实战08】查询和删除商品类别功能的实现

    转自:https://blog.csdn.net/eson_15/article/details/51338991 上一节我们完成了使用DataGrid显示所有商品信息,这节我们开始添加几个功能:添加 ...

  7. asp.net core react 项目实战(一)

    asp.net-core-react asp.net core react 简介 开发依赖环境 .NET Core SDK (reflecting any global.json): Version: ...

  8. react 项目实战(五)渲染用户列表

    现在我们需要一个页面来展现数据库中记录的用户. 在/src/pages下新建UserList.js文件. 创建并导出UserList组件: import React from 'react'; cla ...

  9. react 项目实战(二)创建 用户添加 页面 及 fetch请求 json-server db.json -w -p 8000

    1.安装 路由 npm install -S react-router@3.x 2.新增页面 我们现在的应用只有一个Hello React的页面,现在需要添加一个用于添加用户的页面. 首先在/src目 ...

随机推荐

  1. classpath 路径和classpath*的区别

    classpath和classpath*区别:  classpath:只会到你的class路径中查找找文件. classpath*:不仅包含class路径,还包括jar文件中(class路径)进行查找 ...

  2. fedora27安装ssh

    Fedora安装sshd 先确认是否已安装ssh服务: [root@localhost ~]# rpm -qa | grep openssh-server openssh-server-5.3p1-1 ...

  3. 时钟周期 VS 机器周期

    时钟周期vs机器周期 Clock cycle The speed of a computer processor, or CPU, is determined by the clock cycle, ...

  4. eclipse如何设置多个字符的智能提示

    clipse代码里面的代码提示功能默认是关闭的,只有输入“.”的时候才会提示功能,用vs的用户可能不太习惯这种,vs是输入任何字母都会提示,下面说一下如何修改eclipse配置,开启代码自动提示功能打 ...

  5. C++链表STL

    #include <iostream> #include <list> #include <algorithm> #include <stdlib.h> ...

  6. 装饰器(python)

    一,装饰器定义:本质就是函数,功能是为其他函数添加新功能原则:1.不修改被装饰函数的源代码(开放封闭原则)2.为被装饰函数添加新功能后,不修改被修饰函数的调用方式3.装饰器=高阶函数+函数嵌套+闭包高 ...

  7. dbeaver能执行存储过程,db2命令编辑器里面不行

  8. PHP 数组使用之道

    本文首发于 PHP 数组使用之道,转载请注明出处. 这个教程我将通过一些实用的实例和最佳实践的方式列举出 PHP 中常用的数组函数.每个 PHP 工程师都应该掌握它们的使用方法,以及如何通过组合使用来 ...

  9. ubuntu14.04 Google Chrome can not be run as root

    问题如下图:

  10. 4_蒙特卡罗算法求圆周率PI

    题目 蒙特卡罗算法的典型应用之一为求圆周率PI问题. 思想: 一个半径r=1的圆,其面积为:S=PI∗r2=PI/4 一个边长r=1的正方形,其面积为:S=r2=1 那么建立一个坐标系,如果均匀的向正 ...