大型表单上的双向数据绑定
Two-way data binding on large forms
(完成 React.js 教程后,我目前正在编写我的第一个小应用程序以获得更多练习。所以这将是一个新手问题,答案肯定在某处,但显然我不知道要搜索什么。)
Google列举了很多关于如何实现一个输入框的双向数据绑定的例子。但是大型、复杂的表单又如何呢?可能还可以选择动态添加?
假设我的表单由水平行的输入字段组成。所有行都相同:名字、姓氏、出生日期等。在 table 的底部有一个按钮可以插入一个新的这样的行。所有这些数据都存储在一个数组中。如何将每个输入字段绑定到其各自的数组元素,以便在用户编辑值时更新数组?
每行两列的工作示例:
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;
如何在不必为数百个字段单独添加处理函数的情况下扩大规模?
这个问题有多种解决方案,例如:
解决方案#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/
等解决方案
您仍然可以将字段存储在一个对象中,然后在您想要添加字段时添加到该对象中。然后通过按键映射显示出来
简单示例:
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;
(完成 React.js 教程后,我目前正在编写我的第一个小应用程序以获得更多练习。所以这将是一个新手问题,答案肯定在某处,但显然我不知道要搜索什么。)
Google列举了很多关于如何实现一个输入框的双向数据绑定的例子。但是大型、复杂的表单又如何呢?可能还可以选择动态添加?
假设我的表单由水平行的输入字段组成。所有行都相同:名字、姓氏、出生日期等。在 table 的底部有一个按钮可以插入一个新的这样的行。所有这些数据都存储在一个数组中。如何将每个输入字段绑定到其各自的数组元素,以便在用户编辑值时更新数组?
每行两列的工作示例:
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;
如何在不必为数百个字段单独添加处理函数的情况下扩大规模?
这个问题有多种解决方案,例如:
解决方案#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/
您仍然可以将字段存储在一个对象中,然后在您想要添加字段时添加到该对象中。然后通过按键映射显示出来
简单示例:
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;