React + Redux - Cách tốt nhất để xử lý CRUD trong thành phần biểu mẫu là gì?


128

Tôi có một hình thức được sử dụng để Tạo, Đọc, Cập nhật và Xóa. Tôi đã tạo 3 thành phần với cùng một hình thức nhưng tôi chuyển cho chúng các đạo cụ khác nhau. Tôi đã nhận được CreatForm.js, ViewForm.js (chỉ đọc bằng nút xóa) và UpdateForm.js.

Tôi đã từng làm việc với PHP, vì vậy tôi luôn làm những việc này dưới một hình thức.

Tôi sử dụng React và Redux để quản lý cửa hàng.

Khi tôi ở trong thành phần CreatForm, tôi chuyển cho các thành phần phụ của mình đạo cụ này createForm={true}để không điền giá trị đầu vào và không vô hiệu hóa chúng. Trong thành phần ViewForm của tôi, tôi vượt qua đạo cụ này readonly="readonly".

Và tôi gặp một vấn đề khác với một textarea chứa đầy giá trị và không thể cập nhật được. Phản ứng textarea với giá trị là chỉ đọc nhưng cần được cập nhật

Cấu trúc tốt nhất để chỉ có một thành phần xử lý các trạng thái khác nhau của biểu mẫu là gì?

Bạn có lời khuyên, hướng dẫn, video, bản demo nào để chia sẻ không?

Câu trả lời:


115

Tôi tìm thấy gói Redux Form . Nó làm một công việc thực sự tốt!

Vì vậy, bạn có thể sử dụng Redux với React-Redux .

Trước tiên, bạn phải tạo một thành phần biểu mẫu (rõ ràng):

import React from 'react';
import { reduxForm } from 'redux-form';
import validateContact from '../utils/validateContact';

class ContactForm extends React.Component {
  render() {
    const { fields: {name, address, phone}, handleSubmit } = this.props;
    return (
      <form onSubmit={handleSubmit}>
        <label>Name</label>
        <input type="text" {...name}/>
        {name.error && name.touched && <div>{name.error}</div>}

        <label>Address</label>
        <input type="text" {...address} />
        {address.error && address.touched && <div>{address.error}</div>}

        <label>Phone</label>
        <input type="text" {...phone}/>
        {phone.error && phone.touched && <div>{phone.error}</div>}

        <button onClick={handleSubmit}>Submit</button>
      </form>
    );
  }
}

ContactForm = reduxForm({
  form: 'contact',                      // the name of your form and the key to
                                        // where your form's state will be mounted
  fields: ['name', 'address', 'phone'], // a list of all your fields in your form
  validate: validateContact             // a synchronous validation function
})(ContactForm);

export default ContactForm;

Sau này, bạn kết nối thành phần xử lý biểu mẫu:

import React from 'react';
import { connect } from 'react-redux';
import { initialize } from 'redux-form';
import ContactForm from './ContactForm.react';

class App extends React.Component {

  handleSubmit(data) {
    console.log('Submission received!', data);
    this.props.dispatch(initialize('contact', {})); // clear form
  }

  render() {
    return (
      <div id="app">
        <h1>App</h1>
        <ContactForm onSubmit={this.handleSubmit.bind(this)}/>
      </div>
    );
  }

}

export default connect()(App);

Và thêm bộ giảm tốc dạng redux trong bộ giảm tốc kết hợp của bạn:

import { combineReducers } from 'redux';
import { appReducer } from './app-reducers';
import { reducer as formReducer } from 'redux-form';

let reducers = combineReducers({
  appReducer, form: formReducer // this is the form reducer
});

export default reducers;

Và mô-đun trình xác nhận trông như thế này:

export default function validateContact(data, props) {
  const errors = {};
  if(!data.name) {
    errors.name = 'Required';
  }
  if(data.address && data.address.length > 50) {
    errors.address = 'Must be fewer than 50 characters';
  }
  if(!data.phone) {
    errors.phone = 'Required';
  } else if(!/\d{3}-\d{3}-\d{4}/.test(data.phone)) {
    errors.phone = 'Phone must match the form "999-999-9999"'
  }
  return errors;
}

Sau khi biểu mẫu được hoàn thành, khi bạn muốn điền vào tất cả các trường bằng một số giá trị, bạn có thể sử dụng initializehàm:

componentWillMount() {
  this.props.dispatch(initialize('contact', {
    name: 'test'
  }, ['name', 'address', 'phone']));
}

Một cách khác để điền vào biểu mẫu là đặt giá trị ban đầu.

ContactForm = reduxForm({
  form: 'contact',                      // the name of your form and the key to
  fields: ['name', 'address', 'phone'], // a list of all your fields in your form
  validate: validateContact             // a synchronous validation function
}, state => ({
  initialValues: {
    name: state.user.name,
    address: state.user.address,
    phone: state.user.phone,
  },
}))(ContactForm);

Nếu bạn có bất kỳ cách nào khác để xử lý việc này, chỉ cần để lại tin nhắn! Cảm ơn bạn.


3
Chỉ cần tự hỏi - bạn vẫn đang sử dụng redux-forms? Tôi đang tự hỏi làm thế nào cái vảy nồi hơi đó so với dạng phản ứng
Ashley Coolman

2
Có tôi vẫn đang sử dụng nó! Thực sự tốt đẹp, tôi đã tạo ra các hình thức rất lớn và nó hoạt động # 1. Bạn chỉ cần rất cẩn thận với những gì bạn chuyển làm đạo cụ cho các thành phần và cập nhật của chúng. Xin lỗi vì sự chậm trễ của câu trả lời.
Mike Boutin

1
@MikeBoutin bạn có thể giải thích về sự thận trọng đó về đạo cụ không? Cảm ơn
Adam K Dean

Thật đáng để chỉ ra rằng kể từ phiên bản 6.4.3, nếu bạn đang sử dụng hết tiềm năng của mình, hiệu suất của redux-formnó rất tệ trên tất cả các phiên bản IE, bao gồm cả Edge. Nếu bạn phải hỗ trợ nó, hãy tìm mọi nơi.
Stephen Collins

2
Điều này rất nghiêm ngặt với ShouldComponentUpdate, để không tạo ra độ trễ trong các biểu mẫu của bạn
Mike Boutin

11

CẬP NHẬT: năm 2018 của nó và tôi sẽ chỉ sử dụng các thư viện Formik (hoặc giống như Formik)

Ngoài ra còn có dạng phản ứng-redux-form ( từng bước ), có vẻ như trao đổi một số javascript của mẫu redux (& soạn sẵn) với khai báo đánh dấu. Nó có vẻ tốt, nhưng tôi chưa sử dụng nó.

Một cắt và dán từ readme:

import React from 'react';
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { modelReducer, formReducer } from 'react-redux-form';

import MyForm from './components/my-form-component';

const store = createStore(combineReducers({
  user: modelReducer('user', { name: '' }),
  userForm: formReducer('user')
}));

class App extends React.Component {
  render() {
    return (
      <Provider store={ store }>
        <MyForm />
      </Provider>
    );
  }
}

./components/my-form-component.js

import React from 'react';
import { connect } from 'react-redux';
import { Field, Form } from 'react-redux-form';

class MyForm extends React.Component {
  handleSubmit(val) {
    // Do anything you want with the form value
    console.log(val);
  }

  render() {
    let { user } = this.props;

    return (
      <Form model="user" onSubmit={(val) => this.handleSubmit(val)}>
        <h1>Hello, { user.name }!</h1>
        <Field model="user.name">
          <input type="text" />
        </Field>
        <button>Submit!</button>
      </Form>
    );
  }
}

export default connect(state => ({ user: state.user }))(MyForm);

Chỉnh sửa: So sánh

Các tài liệu dạng phản ứng-redux cung cấp sự so sánh so với dạng redux:

https://davidkpiano.github.io/react-redux-form/docs/guides/compare-redux-form.html


4

Đối với những người không quan tâm đến một thư viện khổng lồ để xử lý các vấn đề liên quan đến biểu mẫu, tôi sẽ khuyên bạn nên sử dụng redux-form-utils .

Nó có thể tạo giá trị và thay đổi trình xử lý cho các điều khiển biểu mẫu của bạn, tạo các trình giảm biểu mẫu, trình tạo hành động tiện dụng để xóa các trường nhất định (hoặc tất cả), v.v.

Tất cả bạn cần làm là lắp ráp chúng trong mã của bạn.

Bằng cách sử dụng redux-form-utils, bạn kết thúc với thao tác biểu mẫu như sau:

import { createForm } from 'redux-form-utils';

@createForm({
  form: 'my-form',
  fields: ['name', 'address', 'gender']
})
class Form extends React.Component {
  render() {
    const { name, address, gender } = this.props.fields;
    return (
      <form className="form">
        <input name="name" {...name} />
        <input name="address" {...address} />
        <select {...gender}>
          <option value="male" />
          <option value="female" />
        </select>
      </form>
    );
  }
}

Tuy nhiên, thư viện này chỉ giải quyết vấn đề CU, RD, có lẽ, một Tablethành phần tích hợp hơn là để chống lại.


1

Chỉ một điều nữa cho những người muốn tạo thành phần biểu mẫu được kiểm soát hoàn toàn mà không sử dụng thư viện quá khổ.

ReduxFormHelper - một lớp ES6 nhỏ, dưới 100 dòng:

class ReduxFormHelper {
  constructor(props = {}) {
    let {formModel, onUpdateForm} = props
    this.props = typeof formModel === 'object' &&
      typeof onUpdateForm === 'function' && {formModel, onUpdateForm}
  }

  resetForm (defaults = {}) {
    if (!this.props) return false
    let {formModel, onUpdateForm} = this.props
    let data = {}, errors = {_flag: false}
    for (let name in formModel) {
      data[name] = name in defaults? defaults[name] :
        ('default' in formModel[name]? formModel[name].default : '')
      errors[name] = false
    }
    onUpdateForm(data, errors)
  }

  processField (event) {
    if (!this.props || !event.target) return false
    let {formModel, onUpdateForm} = this.props
    let {name, value, error, within} = this._processField(event.target, formModel)
    let data = {}, errors = {_flag: false}
    if (name) {
      value !== false && within && (data[name] = value)
      errors[name] = error
    }
    onUpdateForm(data, errors)
    return !error && data
  }

  processForm (event) {
    if (!this.props || !event.target) return false
    let form = event.target
    if (!form || !form.elements) return false
    let fields = form.elements
    let {formModel, onUpdateForm} = this.props
    let data = {}, errors = {}, ret = {}, flag = false
    for (let n = fields.length, i = 0; i < n; i++) {
      let {name, value, error, within} = this._processField(fields[i], formModel)
      if (name) {
        value !== false && within && (data[name] = value)
        value !== false && !error && (ret[name] = value)
        errors[name] = error
        error && (flag = true)
      }
    }
    errors._flag = flag
    onUpdateForm(data, errors)
    return !flag && ret
  }

  _processField (field, formModel) {
    if (!field || !field.name || !('value' in field))
      return {name: false, value: false, error: false, within: false}
    let name = field.name
    let value = field.value
    if (!formModel || !formModel[name])
      return {name, value, error: false, within: false}
    let model = formModel[name]
    if (model.required && value === '')
      return {name, value, error: 'missing', within: true}
    if (model.validate && value !== '') {
      let fn = model.validate
      if (typeof fn === 'function' && !fn(value))
        return {name, value, error: 'invalid', within: true}
    }
    if (model.numeric && isNaN(value = Number(value)))
      return {name, value: 0, error: 'invalid', within: true}
    return {name, value, error: false, within: true}
  }
}

Nó không làm tất cả công việc cho bạn. Tuy nhiên, nó tạo điều kiện cho việc tạo, xác nhận và xử lý một thành phần biểu mẫu được kiểm soát. Bạn chỉ có thể sao chép và dán mã trên vào dự án của mình hoặc thay vào đó, bao gồm thư viện tương ứng - redux-form-helper(plug!).

Cách sử dụng

Bước đầu tiên là thêm dữ liệu cụ thể vào trạng thái Redux sẽ đại diện cho trạng thái của biểu mẫu của chúng tôi. Những dữ liệu này sẽ bao gồm các giá trị trường hiện tại cũng như tập hợp các cờ lỗi cho từng trường trong biểu mẫu.

Trạng thái biểu mẫu có thể được thêm vào một bộ giảm tốc hiện có hoặc được xác định trong một bộ giảm tốc riêng biệt.

Hơn nữa, cần xác định cập nhật hành động cụ thể bắt đầu trạng thái biểu mẫu cũng như người tạo hành động tương ứng.

Ví dụ hành động :

export const FORM_UPDATE = 'FORM_UPDATE' 

export const doFormUpdate = (data, errors) => {
  return { type: FORM_UPDATE, data, errors }
}
...

Ví dụ giảm tốc :

...
const initialState = {
  formData: {
    field1: '',
    ...
  },
  formErrors: {
  },
  ...
}

export default function reducer (state = initialState, action) {
  switch (action.type) {
    case FORM_UPDATE:
      return {
        ...ret,
        formData: Object.assign({}, formData, action.data || {}),
        formErrors: Object.assign({}, formErrors, action.errors || {})
      }
    ...
  }
}

Bước thứ hai và cuối cùng là tạo một thành phần chứa cho biểu mẫu của chúng tôi và kết nối nó với một phần tương ứng của trạng thái và hành động Redux.

Ngoài ra, chúng ta cần xác định một mô hình biểu mẫu xác định xác thực các trường biểu mẫu. Bây giờ chúng ta khởi tạo ReduxFormHelperđối tượng với tư cách là thành viên của thành phần và chuyển qua đó mô hình biểu mẫu của chúng ta và một bản cập nhật gửi lại cuộc gọi của trạng thái biểu mẫu.

Sau đó, trong render()phương thức của thành phần, chúng ta phải liên kết các sự kiện onChangecủa từng trường và biểu mẫu onSubmitvới processField()processForm()các phương thức tương ứng cũng như hiển thị các khối lỗi cho từng trường tùy thuộc vào các cờ lỗi của biểu mẫu trong trạng thái.

Ví dụ dưới đây sử dụng CSS từ khung Bootstrap của Twitter.

Ví dụ về Thành phần Container :

import React, {Component} from 'react';
import {connect} from 'react-redux'
import ReduxFormHelper from 'redux-form-helper'

class MyForm extends Component {
  constructor(props) {
    super(props);
    this.helper = new ReduxFormHelper(props)
    this.helper.resetForm();
  }

  onChange(e) {
    this.helper.processField(e)
  }

  onSubmit(e) {
    e.preventDefault()
    let {onSubmitForm} = this.props
    let ret = this.helper.processForm(e)
    ret && onSubmitForm(ret)
  }

  render() {
    let {formData, formErrors} = this.props
    return (
  <div>
    {!!formErrors._flag &&
      <div className="alert" role="alert">
        Form has one or more errors.
      </div>
    }
    <form onSubmit={this.onSubmit.bind(this)} >
      <div className={'form-group' + (formErrors['field1']? ' has-error': '')}>
        <label>Field 1 *</label>
        <input type="text" name="field1" value={formData.field1} onChange={this.onChange.bind(this)} className="form-control" />
        {!!formErrors['field1'] &&
        <span className="help-block">
          {formErrors['field1'] === 'invalid'? 'Must be a string of 2-50 characters' : 'Required field'}
        </span>
        }
      </div>
      ...
      <button type="submit" className="btn btn-default">Submit</button>
    </form>
  </div>
    )
  }
}

const formModel = {
  field1: {
    required: true,
    validate: (value) => value.length >= 2 && value.length <= 50
  },
  ...
}

function mapStateToProps (state) {
  return {
    formData: state.formData, formErrors: state.formErrors,
    formModel
  }
}

function mapDispatchToProps (dispatch) {
  return {
    onUpdateForm: (data, errors) => {
      dispatch(doFormUpdate(data, errors))
    },
    onSubmitForm: (data) => {
      // dispatch some action which somehow updates state with form data
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(MyForm)

Bản giới thiệu

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.