React + Redux - CRUD'yi bir form bileşeninde işlemenin en iyi yolu nedir?


128

Oluşturmak, Okumak, Güncellemek ve Silmek için kullanılan bir formum var. Aynı formda 3 bileşen oluşturdum ama onlara farklı sahne geçiyorum. CreateForm.js, ViewForm.js (silme düğmesiyle salt okunur) ve UpdateForm.js'yi aldım.

PHP ile çalışıyordum, bu yüzden bunları her zaman tek bir biçimde yaptım.

Mağazayı yönetmek için React ve Redux kullanıyorum.

CreateForm bileşenindeyken, alt bileşenlerime bu createForm={true}girdileri bir değerle doldurmamayı ve devre dışı bırakmamayı destekler. ViewForm bileşenimde, bu sahne öğelerini geçiyorum readonly="readonly".

Ve bir değerle dolu olan ve güncellenemeyen bir metin alanında başka bir sorunum var. React textarea değeri ile salt okunurdur ancak güncellenmesi gerekir

Formun bu farklı durumlarını işleyen tek bir bileşene sahip olmanın en iyi yapısı nedir?

Paylaşacak herhangi bir tavsiye, eğitim, video, demo var mı?

Yanıtlar:


115

Redux Form paketini buldum . Gerçekten iyi bir iş çıkarıyor!

Yani Redux'u React-Redux ile kullanabilirsiniz .

Önce bir form bileşeni oluşturmanız gerekir (tabii ki):

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;

Bundan sonra, formu işleyen bileşeni bağlarsınız:

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

Redux-form redüktörü kombine redüktörlerinize ekleyin:

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;

Doğrulayıcı modülü şuna benzer:

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

Form tamamlandıktan sonra, tüm alanları bazı değerlerle doldurmak istediğinizde initializeişlevi kullanabilirsiniz :

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

Formları doldurmanın başka bir yolu da başlangıç ​​Değerlerini ayarlamaktır.

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

Bunu halletmek için başka bir yolunuz varsa, mesaj bırakın! Teşekkür ederim.


3
Merak ediyorum - hala kullanıyor redux-formsmusun? Tepki formlarına kıyasla bu şablonun nasıl ölçeklendiğini merak ediyorum
Ashley Coolman

2
Evet hala kullanıyorum! Gerçekten güzel, çok büyük formlar yarattım ve 1 numara işe yaradı. Bileşenlerinize ve bunların güncellemelerine destek olarak aktardığınız şeylere çok dikkat etmelisiniz. Cevabın gecikmesi için özür dilerim.
Mike Boutin

1
@MikeBoutin, sahne donanımı ile ilgili bu uyarıyı detaylandırır mısınız? Teşekkürler
Adam K Dean

Hatta v6.4.3 itibariyle, ona kullanıyorsanız işaret It değerinde tam potansiyelini, performansı redux-formolan dipsiz Kenar dahil tüm IE sürümlerinde. Desteklemeniz gerekiyorsa, başka yere bakın.
Stephen Collins

2
Formlarınızda gecikme yaratmamak için shouldComponentUpdate konusunda çok katı olunması
Mike Boutin

11

GÜNCELLEME: 2018 ve sadece Formik (veya Formik benzeri kitaplıklar) kullanacağım

Orada da reaksiyona-Redux biçimli ( adım adım bazı alışverişi görünüyor), Redux-formu biçimlendirme beyanı ile sitesindeki JavaScript (ve klişe). İyi görünüyor ama henüz kullanmadım.

Benioku'dan bir kesip yapıştırın:

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

Düzenleme: Karşılaştırma

React-redux-form belgeleri, redux-form'a karşı bir karşılaştırma sağlar:

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


4

Formla ilgili sorunları ele almak için muazzam bir kitaplığı önemsemeyenler için redux-form-utils'i tavsiye ederim .

Form kontrolleriniz için değer üretebilir ve işleyicileri değiştirebilir, formun indirgeyicileri oluşturabilir, belirli (veya tüm) alanları temizlemek için kullanışlı eylem oluşturucular vb.

Tek yapmanız gereken onları kodunuzda bir araya getirmektir.

Kullanarak redux-form-utils, aşağıdaki gibi form manipülasyonu elde edersiniz:

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

Ancak bu kütüphane sadece sorunu çözer Cve U, için Rve Dbelki bir daha entegre, Tablebileşen antipate etmektir.


1

Büyük boyutlu kitaplık kullanmadan tamamen kontrollü form bileşeni oluşturmak isteyenler için başka bir şey.

ReduxFormHelper - küçük bir ES6 sınıfı, 100 satırdan az:

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

Senin için tüm işi yapmıyor. Bununla birlikte, kontrollü bir form bileşeninin oluşturulmasını, doğrulanmasını ve işlenmesini kolaylaştırır. Yukarıdaki kodu kopyalayıp projenize yapıştırabilir veya bunun yerine ilgili kitaplığı ekleyebilirsiniz - redux-form-helper(fiş!).

Nasıl kullanılır

İlk adım, formumuzun durumunu temsil edecek olan Redux durumuna belirli veriler eklemektir. Bu veriler, formdaki her alan için geçerli alan değerlerini ve hata bayrakları kümesini içerecektir.

Form durumu, mevcut bir indirgeyiciye eklenebilir veya ayrı bir indirgeyicide tanımlanabilir.

Ayrıca, form durumunun güncellemesini başlatan belirli eylemin yanı sıra ilgili eylem oluşturucunun tanımlanması gerekir.

Eylem örneği :

export const FORM_UPDATE = 'FORM_UPDATE' 

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

Redüktör örneği :

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

İkinci ve son adım, formumuz için bir konteyner bileşeni oluşturmak ve onu Redux durumunun ve eylemlerinin ilgili bölümlerine bağlamaktır.

Ayrıca form alanlarının geçerliliğini belirleyen bir form modeli tanımlamamız gerekir. Şimdi ReduxFormHelpernesneyi bileşenin bir üyesi olarak başlatıyoruz ve oraya form modelimizi ve form durumunun geri arama gönderme güncellemesini iletiyoruz.

Daha sonra, bileşenin render()yönteminde, her alanın onChangeve formun onSubmitolaylarını sırasıyla processField()ve processForm()yöntemleriyle bağlamamız ve durumdaki form hata bayraklarına bağlı olarak her alan için hata bloklarını görüntülememiz gerekir.

Aşağıdaki örnek, Twitter Bootstrap çerçevesinden CSS kullanır.

Konteyner Bileşeni örneği :

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)

gösteri

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.