React + Redux - วิธีที่ดีที่สุดในการจัดการ CRUD ในองค์ประกอบของฟอร์มคืออะไร


128

ฉันมีฟอร์มหนึ่งที่ใช้ในการสร้างอ่านอัปเดตและลบ ฉันสร้างส่วนประกอบ 3 ชิ้นในรูปแบบเดียวกัน แต่ฉันส่งอุปกรณ์ประกอบฉากต่าง ๆ ให้พวกเขา ฉันได้ CreateForm.js, ViewForm.js (อ่านอย่างเดียวพร้อมปุ่มลบ) และ UpdateForm.js

ฉันเคยทำงานกับ PHP ดังนั้นฉันจึงทำสิ่งเหล่านี้ในรูปแบบเดียวเสมอ

ฉันใช้ React และ Redux เพื่อจัดการร้านค้า

เมื่อฉันอยู่ในองค์ประกอบ CreateForm ฉันจะส่งไปยังส่วนประกอบย่อยของฉันcreateForm={true}เพื่อประกอบฉากนี้เพื่อไม่เติมอินพุตด้วยค่าและไม่ปิดใช้งาน ในองค์ประกอบ ViewForm ของฉันฉันผ่านอุปกรณ์ประกอบฉากreadonly="readonly"นี้

และฉันมีปัญหาอีกอย่างหนึ่งกับ textarea ซึ่งเต็มไปด้วยคุณค่าและไม่สามารถอัปเดตได้ ทำปฏิกิริยา textarea ที่มีค่าเป็นแบบอ่านอย่างเดียว แต่ต้องมีการอัพเดท

โครงสร้างที่ดีที่สุดในการมีองค์ประกอบเดียวเท่านั้นที่จัดการสถานะที่แตกต่างกันของแบบฟอร์มเหล่านี้คืออะไร?

คุณมีคำแนะนำแบบฝึกหัดวิดีโอตัวอย่างเพื่อแบ่งปันหรือไม่?

คำตอบ:


115

ฉันพบแพ็คเกจRedux Form มันทำงานได้ดีจริงๆ!

ดังนั้นคุณสามารถใช้ReduxกับReact-Redux

ก่อนอื่นคุณต้องสร้างองค์ประกอบของฟอร์ม (ชัด):

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;

หลังจากนี้คุณเชื่อมต่อองค์ประกอบที่จัดการกับรูปแบบ:

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);

และเพิ่มตัวลดรีฟอร์มแบบฟอร์มลงในตัวลดทอนรวม:

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;

และโมดูลตัวตรวจสอบความถูกต้องมีลักษณะดังนี้:

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;
}

หลังจากแบบฟอร์มเสร็จสมบูรณ์เมื่อคุณต้องการเติมเขตข้อมูลทั้งหมดด้วยค่าบางอย่างคุณสามารถใช้initializeฟังก์ชัน:

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

อีกวิธีในการเติมแบบฟอร์มคือการตั้งค่าเริ่มต้น

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);

หากคุณมีวิธีอื่นในการจัดการสิ่งนี้เพียงแค่ฝากข้อความไว้! ขอบคุณ.


3
แค่สงสัย - คุณยังใช้อยู่redux-formsหรือเปล่า? ฉันสงสัยว่าขนาดของ
หม้อไอน้ำนั้น

2
ใช่ฉันยังใช้มันอยู่! ดีมากฉันสร้างฟอร์มที่ใหญ่มากและใช้งานได้ดี # 1 คุณเพียงแค่ต้องระมัดระวังในสิ่งที่คุณส่งผ่านเป็นอุปกรณ์ประกอบฉากให้กับองค์ประกอบและการปรับปรุงของพวกเขา ขออภัยในความล่าช้าของคำตอบ
Mike Boutin

1
@ MikeBoutin คุณสามารถอธิบายความระมัดระวังเกี่ยวกับอุปกรณ์ประกอบฉากได้อย่างละเอียดหรือไม่? ขอบคุณ
Adam K Dean

มันคุ้มค่าที่ชี้ให้เห็นว่าแม้ในขณะที่ของ v6.4.3 ถ้าคุณใช้มันอย่างเต็มประสิทธิภาพของการredux-formเป็นสุดซึ้งในทุกรุ่นของ IE รวมทั้งขอบ หากคุณต้องสนับสนุนให้ดูที่อื่น
Stephen Collins

2
เป็นเพียงการเข้มงวดมากกับ shouldComponentUpdate เพื่อไม่สร้างความล่าช้าในแบบฟอร์มของคุณ
Mike Boutin

11

อัปเดต: 2018 และฉันจะใช้Formik เท่านั้น (หรือไลบรารีที่คล้ายกับ Formik)

นอกจากนี้ยังมีreact-redux-form ( ทีละขั้นตอน ) ซึ่งดูเหมือนจะแลกเปลี่ยนจาวาสคริปต์ (& สำเร็จรูป) ของredux-formบางส่วนด้วยการประกาศมาร์กอัป มันดูดี แต่ฉันยังไม่ได้ใช้

การตัดและวางจาก 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);

แก้ไข: การเปรียบเทียบ

เอกสารปฏิกิริยา -uxux-form จัดเตรียมการเปรียบเทียบกับ redux-form:

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


4

สำหรับผู้ที่ไม่สนใจเกี่ยวกับห้องสมุดมหาศาลสำหรับรูปแบบการจัดการปัญหาที่เกี่ยวข้องกับฉันจะแนะนำRedux ฟอร์ม utils

มันสามารถสร้างคุณค่าและเปลี่ยนแปลงตัวจัดการสำหรับการควบคุมแบบฟอร์มของคุณสร้างตัวลดรูปแบบผู้สร้างแอ็คชั่นที่ใช้งานง่ายเพื่อล้างฟิลด์ (หรือทั้งหมด) บางฟิลด์ ฯลฯ

สิ่งที่คุณต้องทำคือรวบรวมไว้ในรหัสของคุณ

โดยการใช้redux-form-utilsคุณจะได้รูปแบบดังนี้

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>
    );
  }
}

อย่างไรก็ตามห้องสมุดนี้เพียงแก้ปัญหาCและUสำหรับRและDอาจจะบูรณาการเพิ่มเติมTableองค์ประกอบคือการ antipate


1

อีกสิ่งหนึ่งสำหรับผู้ที่ต้องการสร้างองค์ประกอบแบบฟอร์มที่ควบคุมโดยไม่ต้องใช้ห้องสมุดขนาดใหญ่

ReduxFormHelper - คลาส ES6 ขนาดเล็กน้อยกว่า 100 บรรทัด:

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}
  }
}

มันไม่ทำงานทั้งหมดสำหรับคุณ อย่างไรก็ตามมันช่วยอำนวยความสะดวกในการสร้างการตรวจสอบความถูกต้องและการจัดการขององค์ประกอบแบบฟอร์มที่มีการควบคุม คุณสามารถคัดลอกและวางรหัสข้างต้นลงในโครงการของคุณหรือแทนรวมถึงห้องสมุดที่เกี่ยวข้อง - redux-form-helper(plug!)

วิธีใช้

ขั้นตอนแรกคือการเพิ่มข้อมูลเฉพาะในสถานะ Redux ซึ่งจะแสดงสถานะของแบบฟอร์มของเรา ข้อมูลเหล่านี้จะรวมถึงค่าของเขตข้อมูลในปัจจุบันเช่นเดียวกับชุดของการตั้งค่าสถานะข้อผิดพลาดสำหรับแต่ละเขตข้อมูลในแบบฟอร์ม

สถานะของแบบฟอร์มอาจถูกเพิ่มลงในตัวลดที่มีอยู่หรือกำหนดไว้ในตัวลดที่แยกต่างหาก

นอกจากนี้ยังจำเป็นต้องกำหนดการดำเนินการเฉพาะที่เริ่มต้นการอัปเดตสถานะของฟอร์มรวมถึงผู้สร้างแอคชั่นที่เกี่ยวข้อง

ตัวอย่างการกระทำ :

export const FORM_UPDATE = 'FORM_UPDATE' 

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

ตัวอย่างการลด :

...
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 || {})
      }
    ...
  }
}

ขั้นตอนที่สองและขั้นสุดท้ายคือการสร้างส่วนประกอบคอนเทนเนอร์สำหรับฟอร์มของเราและเชื่อมต่อกับส่วนที่เกี่ยวข้องของสถานะ Redux และการกระทำ

นอกจากนี้เราต้องกำหนดรูปแบบการระบุการตรวจสอบความถูกต้องของเขตข้อมูลแบบฟอร์ม ตอนนี้เราสร้างอินสแตนซ์ของReduxFormHelperวัตถุในฐานะสมาชิกขององค์ประกอบและผ่านไปที่นั่นแบบจำลองรูปแบบของเราและการปรับปรุงการส่งกลับสถานะการโทรกลับของสถานะ

จากนั้นในrender()วิธีการขององค์ประกอบเราจะต้องผูกแต่ละเขตข้อมูลonChangeและonSubmitเหตุการณ์ของรูปแบบด้วยprocessField()และprocessForm()วิธีการตามลำดับเช่นเดียวกับการแสดงบล็อกข้อผิดพลาดสำหรับแต่ละเขตข้อมูลขึ้นอยู่กับธงข้อผิดพลาดแบบฟอร์มในรัฐ

ตัวอย่างด้านล่างใช้ CSS จากเฟรมเวิร์ก Bootstrap ของ Twitter

ตัวอย่างส่วนประกอบคอนเทนเนอร์ :

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)

การสาธิต

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.