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 = 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 项目实战(七)用户编辑与删除的更多相关文章
- React项目实战:react-redux-router基本原理
React相关 React 是一个采用声明式,高效而且灵活的用来构建用户界面的框架. JSX 本质上来讲,JSX 只是为React.createElement(component, props, .. ...
- 《Node+MongoDB+React 项目实战开发》已出版
前言 从深圳回长沙已经快4个月了,除了把车开熟练了外,并没有什么值得一提的,长沙这边要么就是连续下一个月雨,要么就是连续一个月高温暴晒,上班更是没啥子意思,长沙这边的公司和深圳落差挺大的,薪资也是断崖 ...
- Hadoop项目实战-用户行为分析之应用概述(三)
1.概述 本课程的视频教程地址:<项目工程准备> 本节给大家分享的主题如下图所示: 下面我开始为大家分享今天的第三节的内容——<项目工程准备>,接下来开始分享今天的内容. 2. ...
- Hadoop项目实战-用户行为分析之应用概述(一)
1.概述 本课程的视频教程地址:<Hadoop 回顾> 好的,下面就开始本篇教程的内容分享,本篇教程我为大家介绍我们要做一个什么样的Hadoop项目,并且对Hadoop项目的基本特点和其中 ...
- Hadoop项目实战-用户行为分析之应用概述(二)
1.概述 本课程的视频教程地址:<项目整体概述> 本节给大家分享的主题如下图所示: 下面我开始为大家分享第二节的内容——<项目整体概述>,下面开始今天的分享内容. 2.内容 从 ...
- 【SSH网上商城项目实战08】查询和删除商品类别功能的实现
转自:https://blog.csdn.net/eson_15/article/details/51338991 上一节我们完成了使用DataGrid显示所有商品信息,这节我们开始添加几个功能:添加 ...
- asp.net core react 项目实战(一)
asp.net-core-react asp.net core react 简介 开发依赖环境 .NET Core SDK (reflecting any global.json): Version: ...
- react 项目实战(五)渲染用户列表
现在我们需要一个页面来展现数据库中记录的用户. 在/src/pages下新建UserList.js文件. 创建并导出UserList组件: import React from 'react'; cla ...
- react 项目实战(二)创建 用户添加 页面 及 fetch请求 json-server db.json -w -p 8000
1.安装 路由 npm install -S react-router@3.x 2.新增页面 我们现在的应用只有一个Hello React的页面,现在需要添加一个用于添加用户的页面. 首先在/src目 ...
随机推荐
- codeforces_B. Forgery
http://codeforces.com/contest/1059/problem/B 题意: For simplicity, the signature is represented as an ...
- 浅谈JS之text/javascript和application/javascript
问题描述: JS在IE8以下浏览器运行异常 代码: <script>标签是这样子写的: <script type="application/javascript" ...
- 使用webpack+vue.js构建前端工程化
参考文章:https://blog.csdn.net/qq_40208605/article/details/80661572 使用webpack+vue.js构建前端工程化本篇主要介绍三块知识点: ...
- 16.04 下 ufw 防火墙的的开启、禁用、开放端口、关闭端口
16.04 下的 ufw 防火墙相关操作使用ufw命令.通过ufw --help可以查看所有相关命令. 打开防火墙 sudo ufw enable 重启防火墙 sudo ufw reload 打开指定 ...
- css的存在形式
1.css的样式,可以写在head头中: 1).通过ID(#CC{}) 2).通过class (.cc{}) 2.可以将样式,单独写入css的某一个页中 1)通过在head头中,引改该css样式,通过 ...
- FFmpeg加水印
ffmpeg中文水印乱码两种原因 1.字符编码格式原因,中文必须是utf8编码格式的(我遇到的问题,在vs2013上写的中文,已做编码格式转码,放到centos7.2上编译运行也会出现中文乱码的问题, ...
- 闲着无聊 一个python的,三级菜单。装逼版。
menu = { '北京': { '海淀': { '五道口': { 'soho': {}, '网易': {}, 'google': {} }, '中关村': { '爱奇艺': {}, '汽车之家': ...
- Atcoder regular Contest 073(C - Sentou)
Atcoder regular Contest 073(C - Sentou) 传送门 每个人对开关的影响区间为a[i]--a[i]+t,因此此题即为将所有区间离散化后求所有独立区间的长度和 #inc ...
- package control(转载)
Package Control(扩展包管理器) []Package Control 可以看做是一个ST的扩展管理器,可以去下载.安装.删除 Sublime Text 3的各种插件.皮肤等. [ ...
- 九度oj 题目1203:IP地址
题目1203:IP地址 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:3636 解决:1800 题目描述: 输入一个ip地址串,判断是否合法. 输入: 输入的第一行包括一个整数n(1< ...