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

項目目錄:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM