【问题标题】:Auto save form fields in react and redux在 react 和 redux 中自动保存表单字段
【发布时间】:2018-12-21 02:53:40
【问题描述】:

我有一个父组件,它是一个表单,它通过 redux 连接到商店:

// FormComponent
export default connect(mapStateToProps, mapDispatchToProps)(FormComponent);

在该表单中,我有一个输入属性列表,它们是表单的子组件:

  • 表单组件
    • 名字组件
    • 姓氏组件
    • 地址组件

我需要通过在输入失去焦点后立即传递包含所有属性的 JSON 对象来进行更新 API 调用,从而将任何字段中的更改保存到服务器。

API 调用类似于:

updatePersonInfo({firstname: 'new name', lastname: 'new name', address: 'new address' });

我的想法是将PersonInfo 对象作为道具从表单组件传递给所有子组件。每个子组件将从 PersonInfo 属性更新一个属性,并使用更新的 PersonInfo 对象调度 UPDATE_PERSONINFO 操作。 但要做到这一点,我还需要将所有子组件连接到存储:

// FirstNameComponent
export default connect(mapStateToProps, mapDispatchToProps(FirstNameComponent);

// LastNameComponent
export default connect(mapStateToProps, mapDispatchToProps)(LastNameComponent);

// AddressComponent
export default connect(mapStateToProps, mapDispatchToProps)(AddressComponent);

但我认为出于性能原因我们应该避免使用连接。 此外,我不相信我需要 4 个连接器来调用一个操作。

处理这种情况的最佳选择是什么?

【问题讨论】:

  • 我认为在这里使用 redux-form 可能会对您有所帮助。
  • @AliaksandrSushkevich 另一个依赖!!!
  • 为什么不直接使用它们而不是连接它们并处理父级上的onChange 以调度操作?

标签: reactjs react-redux


【解决方案1】:

你可以直接在每个组件中调用API,只更新对应的属性,这样就可以避免Redux。
例如。 updatePersonInfo({ firstname: 'new name' })

您的 API 端点应该是一个 PATCH 方法,它只更新有效负载中的字段(在上面的示例中,它应该只修改与 firstname 对应的字段)。

【讨论】:

    【解决方案2】:

    对于这个问题,我可以提供一个我刚刚学到的好技巧,在这些情况下,我们应该使用容器组件的状态来创建受控表单组件并在每次表单更改时更新状态,这样您就可以随时访问您的从容器组件的状态形成数据。 (例如:由于实现了受控组件,我们可以始终通过保存按钮将我们可用的表单数据从容器组件的状态发送到 Redux case 专注于另一个领域)。

    让我们开始吧:

    (1)您需要为每个输入使用name 属性和onChange,如下所示:

    <input 
      type='text'
      name={name}
      className='form-control'
      value={value}
      onChange={onChange}/>
    

    (2) onChange 函数被放置在您的容器组件中,并通过 props 传递给您的输入组件,如下所示:

      updateCourseState(e) {
        const field = e.target.name
        const _formData = {...this.state.formData}
        _formData[field] = e.target.value // (*)
        return this.setState({ formData: _formData}) // <= then update the state on every change
      }
    

    在这里 (*) 通过使用预先计算的动态变量名称,我们为 temp _formData 对象中的每个输入字段创建一个属性,并将传递的事件的目标值分配给它,然后我们更新状态.

    (3) 最后,您现在可以随时将状态传递给 redux,当我阅读您的问题时,您可以使用 Refs() 来跟踪何时使用焦点在另一个字段中,您可以检查 this tutorial on YouTube ,当您更改焦点时,这里是您要将数据发送到 Redux 的地方。

    【讨论】:

      【解决方案3】:

      我认为您可以使用store.subscribe(listener) 来比较任何更改并调用 API 来保存数据。 https://redux.js.org/api-reference/store#subscribe

      我将它用于类似的功能,我将所有商店数据保存在浏览器的本地存储中。

      我的使用方法

          import { loadState, saveState, loadLanguage } from "./common/localStorage";
      
      
      
          class Main extends React.Component {
      
          ..........
      
          store.subscribe(() => {
             saveState(store.getState()); // Some DOM api calls.
          });
      
          ..........
      
         }
      

      我的 localStorage.js 文件

          export const loadState = () => {
          try {
              const serializedState = localStorage.getItem('state');
      
              if (serializedState === null) {
                  return undefined;
              }
      
              return JSON.parse(serializedState);
      
          } catch (err) {
              return undefined;
          }
      };
      
      export const saveState = (state) => {
          try {
              const serializedState = JSON.stringify(state);
              localStorage.setItem('state', serializedState);
          } catch (err) {
              // die
          }
      };
      
      
      export const saveLanguage = (code) => {
          try {
              localStorage.setItem('languageCode', code);
          } catch (err) {
              // die
          }
      };
      
      export const loadLanguage = () => {
          try {
              const languageCode = localStorage.getItem('languageCode');
      
              if (languageCode === null) {
                  return "pt-br";
              }
      
              return languageCode;
      
          } catch (err) {
              return "pt-br";
          }
      };
      

      【讨论】:

        【解决方案4】:

        我真的不知道您的子组件长什么样(即:FirstNameComponent...)

        首先,您实际上不需要将它们连接到商店,因为您拥有主要组件。

        class FormComponent extends Component {
        
            changed(data, item) {
               const updatedData = {...this.props.data, item: data};
               this.props.updateData(updatedData);
            }
        
            render() {
                return (
                    <div>
                        <FirstNameComponent onChange={this.props.update} name={this.props.data.firstname} />
                        <LastNameComponent onChange={this.props.update} name={this.props.data.lastname} />
                        <AddressComponent onChange={this.props.update} name={this.props.data.address} />
                    </div>
                );
            }
        

        正如您在此处看到的,我们有将连接到 redux 的“主”组件。 它将从存储中获取值,并获取用于更新后端数据的调用方法。

        “子组件”是“愚蠢的”,因为它们只是获取数据,并在输入模糊时从它们的 props 调用方法。

        【讨论】:

          【解决方案5】:

          但我认为出于性能原因我们应该避免使用连接。另外,我不相信我需要 4 个连接器来调用一个操作。

          实际上,使用多个connects 会带来更好的性能(大部分时间)
          因为connect 足够聪明,并且通过使用浅比较为我们节省了许多重新渲染。

          Proof: Should I Only Connect My Top Component Or Can I Connect Multiple Components In My Tree

          请注意,与冗余重新渲染(或 DOM 操作)相比,connect/mapStateToProps/mapDispatchToProps 计算相对便宜。复杂的选择器可能会例外(这就是 reselect 的用途)。

          您的情况不够复杂,根本没有太大区别。唯一的区别不是性能,而是connect的样板代码,将几个不同的组件用于相同的存储数据和相同的操作/thunk。

          而且,您的方案的最佳选择是仅连接表单组件并使输入字段尽可能简单和愚蠢。此外,例如,您可能需要添加表单提交逻辑或表单验证 - 使表单组件足够智能的另一个要点。

          // #1
          class FormComponent extends React.Component {
            onChange = (e) => {
              // assuming that form component receives `person` from `mapStateToProps`
              // create new person value: previous one + updated value
              const person = {
                ...this.props.person,
                [e.target.name]: e.target.value,
              }
          
              // update action, assuming that `updatePerson` accepts person object
              // and was injected by `mapDispatchToProps`
              updatePerson(person)
            }
          
            render() {
              const { person } = this.props
          
              // there are native HTML elements in example, you can swap with your own components
              // but, if your components will not paste name attribute value
              // you might need to use something like this: `value => this.onChange('firstName', value)
              return (
                <form>
                  <input name="firstName" value={person.firstName} onChange={this.onChange} />
                  <input name="lastName" value={person.lastName} onChange={this.onChange} />
                  <input name="address" value={person.address} onChange={this.onChange} />
                </form>
              )
            }
          }
          

          这是一个简单的解决方案。如果你绝对需要使用FirstName/LastName/AddressComponents,那么它可能看起来像这样(仍然,connecting 表单组件而已):

          // #2
          // form's onChange handler
          onChange = (name, value) => {
            const person = { ...this.props.person, [name]: value }
          
            this.props.updatePerson(person)
          }
          
          // form's render
          <FirstNameComponent value={person.firstName} onChange={this.onChange} />
          
          // FirstNameComponent, similar to LastName/AddressComponents
          const FirstNameComponent = ({ value, onChange }) =>
            <input value={value} onChange={e => onChange('firstName', value)} />
          

          IMO,第一个 (#1) 简单明了。此外,具有更好的性能。

          如果您在这些输入组件中有非常复杂的逻辑(即掩码输入、去抖动状态更新等),则第二个选项 (#2) 可能没问题。

          【讨论】:

            【解决方案6】:

            你真的需要 redux(或 redux-form)吗?

            试试 Formik。我发现它非常灵活,您可以使用模糊处理程序或验证(仅发送有效?)来进行 api 调用。

            【讨论】:

              猜你喜欢
              • 2016-10-27
              • 1970-01-01
              • 2015-06-19
              • 2017-10-16
              • 1970-01-01
              • 2021-02-01
              • 1970-01-01
              • 2021-08-16
              • 1970-01-01
              相关资源
              最近更新 更多