react 项目实战(七)用户编辑与删除
添加操作列
编辑与删除功能都是针对已存在的某一个用户执行的操作,所以在用户列表中需要再加一个“操作”列来展现【编辑】与【删除】这两个按钮。
修改/src/pages/UserList.js
文件,添加方法handleEdit与handleDel,并在table中添加一列:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | ... 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方法中我们应该先确认用户是否想要执行删除操作,在用户确认后调用删除用户的接口来删除用户:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | ... // 删除 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
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | /** * 高阶组件 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里搬过去(省略号部分与原来的代码相同):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | ... 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的值设置到表单:
1 2 3 4 5 6 7 8 | ... componentWillMount () { const {editTarget, setFormValues} = this .props; if (editTarget) { setFormValues(editTarget); } } ... |
这样我们的UserEditor就基本完成了,当我们要作为一个用户添加器使用时,只需要:
1 2 3 | ... <UserEditor/> ... |
而作为一个用户编辑器使用时,则需要将编辑的目标用户对象传给editTarget这个属性:
1 2 3 | ... <UserEditor editTarget={user}/> ... |
完成代码(编辑器组件):
src / components / UserEditor.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | /** * 编辑器组件 */ 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文件改成这样了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /** * 用户添加页面 */ 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
文件作为编辑用户的页面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | /** * 编辑用户页面 */ 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来定义路由的参数(参数名与页面组件中获取参数时的参数名相对应):
1 2 3 4 5 6 7 8 | import UserEditPage from './pages/UserEdit' ; // 用户编辑页面 ReactDOM.render(( <Router history={hashHistory}> ... <Route path= "/user/edit/:id" component={UserEditPage}/> </Router> ), document.getElementById( 'root' )); |
完成handleEdit方法
最后,来补上UserList页面组件的handleEdit方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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。
项目目录:
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从二进制到误差:逐行拆解C语言浮点运算中的4008175468544之谜
· .NET制作智能桌面机器人:结合BotSharp智能体框架开发语音交互
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· C# 13 中的新增功能实操
· Ollama本地部署大模型总结
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(4)
· 卧槽!C 语言宏定义原来可以玩出这些花样?高手必看!
· langchain0.3教程:从0到1打造一个智能聊天机器人