zoukankan      html  css  js  c++  java
  • react 项目实战(七)用户编辑与删除

    添加操作列

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

    修改/src/pages/UserList.js文件,添加方法handleEdithandleDel,并在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方法中,通过检查是否收到一个editTargetprops来判断这次的操作是添加操作还是编辑操作,并根据当前的操作切换调用接口的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

    项目目录:

  • 相关阅读:
    Mutex和RWMutex
    内核态和用户态
    runtime源码
    goroutine的结束与通信
    进程线程协程
    堆和栈
    array和slice区别
    逃逸分析
    单例模式
    WaitGroup用法
  • 原文地址:https://www.cnblogs.com/crazycode2/p/8537533.html
Copyright © 2011-2022 走看看