【问题标题】:Two-way data binding on large forms大型表单上的双向数据绑定
【发布时间】:2022-01-13 12:03:46
【问题描述】:

(在完成 React.js 教程之后,我目前正在编写我的第一个小应用程序以获得更多练习。所以这将是一个新手问题,答案肯定在某个地方,但显然我不知道搜索什么。)

Google 列出了很多关于如何为一个输入字段实现双向数据绑定的示例。但是对于大型复杂的表单,可能还有更多动态添加的选项呢?

假设我的表单由输入字段的水平线组成。所有行都相同:名字、姓氏、出生日期等。在表格的底部,有一个按钮可以插入一个新的这样的行。所有这些数据都存储在一个数组中。如何将每个输入字段绑定到其各自的数组元素,以便在用户编辑值时更新数组?

两行两列的工作示例:

import { useState} from 'react';

function App() {
    var [name1, setName1] = useState('Alice');
    var [score1, setScore1] = useState('100');
    var [name2, setName2] = useState('Bob');
    var [score2, setScore2] = useState('200');

    function changeNameHandler1 (e) {
        console.log(e)
        setName1(e.target.value)
    }

    function changeScoreHandler1 (e) {
        setScore1(e.target.value)
    }

    function changeNameHandler2 (e) {
        setName2(e.target.value)
    }

    function changeScoreHandler2 (e) {
        setScore2(e.target.value)
    }

    return (
        <div>
            <table>
                <tbody>
                    <tr>
                        <td><input name="name1" id="id1" type="text" value={name1} onChange={changeNameHandler1} /></td>
                        <td><input name="score1" type="text" value={score1} onChange={changeScoreHandler1} /></td>
                    </tr>
                    <tr>
                        <td><input name="name2" type="text" value={name2} onChange={changeNameHandler2} /></td>
                        <td><input name="score2" type="text" value={score2} onChange={changeScoreHandler2} /></td>
                    </tr>
                </tbody>
            </table>
            {name1} has a score of {score1}<br />
            {name2} has a score of {score2}<br />
        </div>
    );
}

export default App;

如何在不必单独为数百个字段添加处理函数的情况下扩大规模?

【问题讨论】:

    标签: reactjs forms data-binding


    【解决方案1】:

    您仍然可以将字段存储在对象中,然后在要添加字段时添加到对象中。然后通过键映射以显示它们。

    简单示例:

    import { useState } from 'react'
    
    const App = () => {
      const [fields, setFields ] = useState({
        field_0: ''
      })
    
      const handleChange = (e) => {
        setFields({
          ...fields,
          [e.target.name]: e.target.value
        })
      }
    
      const addField = () => setFields({
        ...fields,
        ['field_' + Object.keys(fields).length]: ''
      })
    
      const removeField = (key) => {
        delete fields[key]
        setFields({...fields})
      }
    
      return (
        <div>
          {Object.keys(fields).map(key => (
            <div>
              <input onChange={handleChange} key={key} name={key} value={fields[key]} />
              <button onClick={() => removeField(key)}>Remove Field</button>
            </div>
          ))}
    
          <button onClick={() => addField()}>Add Field</button>
          <button onClick={() => console.log(fields)}>Log fields</button>
        </div>
      );
    };
    
    export default App;
    

    以下是我认为您在问题中试图实现的目标:

    import { useState } from 'react'
    
    const App = () => {
      const [fieldIndex, setFieldIndex] = useState(1)
      const [fields, setFields ] = useState({
        group_0: {
          name: '',
          score: ''
        }
      })
    
      const handleChange = (e, key) => {
        setFields({
          ...fields,
          [key]: {
            ...fields[key],
            [e.target.name]: e.target.value
          }
        })
      }
    
      const addField = () => {
        setFields({
          ...fields,
          ['group_' + fieldIndex]: {
            name: '',
            score: ''
          }
        })
        setFieldIndex(i => i + 1)
      }
    
      const removeField = (key) => {
        delete fields[key]
        setFields({...fields})
      }
    
      return (
        <div>
          {Object.keys(fields).map((key, index) => (
            <div key={key}>
              <div>Group: {index}</div>
              <label>Name:</label>
              <input onChange={(e) => handleChange(e, key)} name='name' value={fields[key].name} />
              <label>Score: </label>
              <input onChange={(e) => handleChange(e, key)} name='score' value={fields[key].score} />
              <button onClick={() => removeField(key)}>Remove Field Group</button>
            </div>
          ))}
    
          <button onClick={() => addField()}>Add Field</button>
          <button onClick={() => console.log(fields)}>Log fields</button>
        </div>
      );
    };
    
    export default App;
    

    您可能希望保留用于命名的索引,在这种情况下您可以使用数组。然后你只需传递索引来改变你的输入。下面是一个使用数组的例子:

    import { useState } from 'react'
    
    const App = () => {
      const [fields, setFields ] = useState([
        {
          name: '',
          score: ''
        }
      ])
    
      const handleChange = (e, index) => {
        fields[index][e.target.name] = e.target.value
        setFields([...fields])
      }
    
      const addField = () => {
        setFields([
          ...fields,
            {
              name: '',
              score: ''
            }
        ])
      }
    
      const removeField = (index) => {
        fields.splice(index, 1)
        setFields([...fields])
      }
    
      return (
        <div>
          {fields.map((field, index) => (
            <div key={index}>
              <div>Group: {index}</div>
              <label>Name:</label>
              <input onChange={(e) => handleChange(e, index)} name='name' value={field.name} />
              <label>Score: </label>
              <input onChange={(e) => handleChange(e, index)} name='score' value={field.score} />
              <button onClick={() => removeField(index)}>Remove Field Group</button>
            </div>
          ))}
    
          <button onClick={() => addField()}>Add Field</button>
          <button onClick={() => console.log(fields)}>Log fields</button>
        </div>
      );
    };
    
    export default App;
    

    【讨论】:

    • 你原来的回复已经很有启发性了。我以前从未见过这种点-点-点运算符,但它似乎非常有用。但第二个例子可能正是我需要的——非常感谢!
    • 嗨史蒂夫,快速跟进问题:您将如何实现 removeField 处理程序?
    • @snorri 我用示例编辑了答案。我添加了一个如何只使用数组的示例,因为您可能希望保持组索引。无论哪种方式都有效。但是,如果您要使用对象示例,则必须添加索引状态以避免在添加和删除组时发生命名冲突。所以毕竟使用数组可能会更好。这取决于你,但我更新了答案,向你展示如何去做。如果您有任何问题,请随时提出。
    【解决方案2】:

    对这个问题有多种解决方案,例如:

    解决方案 #1 使用 useRef 存储字段的值

    import { useRef, useCallback } from "react";
    
    export default function App() {
      const fullNameInputElement = useRef();
      const emailInputElement = useRef();
      const passwordInputElement = useRef();
      const passwordConfirmationInputElement = useRef();
    
      const formHandler = useCallback(
        () => (event) => {
          event.preventDefault();
    
          const data = {
            fullName: fullNameInputElement.current?.value,
            email: emailInputElement.current?.value,
            password: passwordInputElement.current?.value,
            passwordConfirmation: passwordConfirmationInputElement.current?.value
          };
    
          console.log(data);
        },
        []
      );
    
      return (
        <form onSubmit={formHandler()}>
          <label htmlFor="full_name">Full name</label>
          <input
            ref={fullNameInputElement}
            id="full_name"
            placeholder="Full name"
            type="text"
          />
    
          <label htmlFor="email">Email</label>
          <input
            ref={emailInputElement}
            id="email"
            placeholder="Email"
            type="email"
          />
    
          <label htmlFor="password">Password</label>
          <input
            ref={passwordInputElement}
            id="password"
            placeholder="Password"
            type="password"
          />
    
          <label htmlFor="password_confirmation">Password Confirmation</label>
          <input
            ref={passwordConfirmationInputElement}
            id="password_confirmation"
            placeholder="Password Confirmation"
            type="password"
          />
    
          <button type="submit">Submit</button>
        </form>
      );
    }
    

    或仍然使用 useState 但将所有值存储在一个对象中

    import { useState, useCallback } from "react";
    
    const initialUserData = {
      fullName: "",
      email: "",
      password: "",
      passwordConfirmation: ""
    };
    
    export default function App() {
      const [userData, setUserData] = useState(initialUserData);
    
      const updateUserDataHandler = useCallback(
        (type) => (event) => {
          setUserData({ ...userData, [type]: event.target.value });
        },
        [userData]
      );
    
      const formHandler = useCallback(
        () => (event) => {
          event.preventDefault();
    
          console.log(userData);
        },
        [userData]
      );
    
      return (
        <form onSubmit={formHandler()}>
          <label htmlFor="full_name">Full name</label>
          <input
            id="full_name"
            placeholder="Full name"
            type="text"
            value={userData.fullName}
            onChange={updateUserDataHandler("fullName")}
          />
          <label>Email</label>
          <input
            id="email"
            placeholder="Email"
            type="email"
            value={userData.email}
            onChange={updateUserDataHandler("email")}
          />
          <label htmlFor="password">Password</label>
          <input
            id="password"
            placeholder="Password"
            type="password"
            value={userData.password}
            onChange={updateUserDataHandler("password")}
          />
          <label htmlFor="password_confirmation">Password Confirmation</label>
          <input
            id="password_confirmation"
            placeholder="Password Confirmation"
            type="password"
            value={userData.passwordConfirmation}
            onChange={updateUserDataHandler("passwordConfirmation")}
          />
          <button type="submit">Submit</button>
        </form>
      );
    }
    

    解决方案 #2 或者您有多个库也提供解决方案,例如 react-form-hook https://react-hook-form.com/

    【讨论】:

    • 哎呀,我觉得这些对于我现在的水平来说有点太高级了。一旦我对 React 有了更牢固的掌握,我会回到他们身边。非常感谢您的广泛回复!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-24
    • 2015-08-08
    相关资源
    最近更新 更多