1. 程式人生 > >【React全家桶入門之八】使用者編輯與刪除

【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> &nbsp; <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 = confirm(`確定要刪除使用者 ${user.name} 嗎?`);

    if (confirmed) {
      fetch('http://localhost:3000/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的方法,用於在元件中主動設定表單的值。

抽離UserEditor

接下來新建/src/components/UserEditor.js檔案,將表單處理程式碼從UserAdd.js裡搬過去(省略號部分與原來的程式碼相同):

import React from 'react';
import FormItem from '../components/FormItem';
import formProvider from '../utils/formProvider';

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: React.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:3000/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}/>
  ...

所以現在就可以將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';
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:3000/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: React.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('app'));

完成handleEdit方法

最後,來補上UserList頁面元件的handleEdit方法:

class UserList extends React.Component {
  constructor (props) { ... }

  componentWillMount () { ... }

  handleEdit (user) {
    this.context.router.push('/user/edit/' + user.id);
  }

  handleDel (user) { ... }

UserList.contextTypes = {
  router: React.PropTypes.object.isRequired
};

在handleEdit方法中只需要使用router.push方法跳轉到該使用者的編輯頁面,別忘了加上contextTypes。

看看效果:

此處輸入圖片的描述

大功告成!