我想出了一个解决方案,用于在React中的第二级嵌套状态中更改属性,该状态不可扩展且效率不高。为了更改handleOnChange
和reps
,如何重构weight
方法?
import React, { useState } from "react";
const workout = {
id: "123-234sdf-1213",
name: "wo name",
done: false,
exercises: [
{
name: "back squat",
sets: [
{
number: 0,
reps: 0,
weight: 0,
done: false,
},
{
number: 1,
reps: 0,
weight: 0,
done: false,
},
{
number: 2,
reps: 0,
weight: 0,
done: false,
},
],
},
{
name: "leg press",
sets: [
{
number: 0,
reps: 0,
weight: 0,
done: false,
},
{
number: 1,
reps: 0,
weight: 0,
done: false,
},
{
number: 2,
reps: 0,
weight: 0,
done: false,
},
],
},
],
};
export default function App() {
const [workoutState, setWorkoutState] = useState(workout);
const handleOnChange = (e, exIndex, setIndex) => {
const value = e.target.value ? parseFloat(e.target.value) : "";
const exercises = [...workoutState.exercises];
exercises[exIndex].sets[setIndex] = {
...exercises[exIndex].sets[setIndex],
[e.target.name]: value,
};
setWorkoutState({
...workoutState,
exercises,
});
};
return (
<div className="App">
<h1>{workoutState.name}</h1>
{workoutState.exercises.map((ex, exIndex) => (
<>
<h2>{ex.name}</h2>
{ex.sets.map((set, setIndex) => (
<div key={`${ex.name}_${setIndex}`}>
<div>
reps:{" "}
<input
value={set.reps}
name="reps"
type="number"
onChange={(e) => handleOnChange(e, exIndex, setIndex)}
/>
</div>
<div>
weight:{" "}
<input
value={set.weight}
name="weight"
type="number"
onChange={(e) => handleOnChange(e, exIndex, setIndex)}
/>
</div>
</div>
))}
</>
))}
</div>
);
}
答案 0 :(得分:0)
我想说您在更新嵌套状态的方法上是正确的,但是在当前的handleOnChange
实现中确实存在状态突变。您正在变异exercises[exIndex]
。
const handleOnChange = (e, exIndex, setIndex) => {
const value = e.target.value ? parseFloat(e.target.value) : "";
const exercises = [...workoutState.exercises];
exercises[exIndex].sets[setIndex] = { // <-- mutates exercises[exIndex]
...exercises[exIndex].sets[setIndex],
[e.target.name]: value,
};
setWorkoutState({
...workoutState,
exercises,
});
};
您应该浅化复制要更新的每个嵌套状态。
我建议使用功能状态更新,这样,如果出于任何原因而在渲染周期中排队了多个状态更新,则更新将从上一次更新开始,而不是使用渲染周期中的状态更新已入队。
我也更喜欢使处理程序成为咖喱函数。它以索引作为参数,并返回使用onChange
事件对象的函数。这样,您就可以删除匿名函数声明,并且在附加处理程序时需要自己代理事件对象,即onChange={handleOnChange(exIndex, setIndex)}
与onChange={e => handleOnChange(e, exIndex, setIndex)}
const handleOnChange = (eIndex, sIndex) => e => {
const { name, value } = e.target;
setWorkoutState((workoutState) => ({
...workoutState,
exercises: workoutState.exercises.map((exercise, exerciseIndex) =>
exerciseIndex === eIndex
? {
...exercise,
sets: exercise.sets.map((set, setIndex) =>
setIndex === sIndex
? {
...set,
[name]: Number(value)
}
: set
)
}
: exercise
)
}));
};
输入。我添加了step
属性并附加了咖喱处理程序。我还将每个输入都放在label
元素中以方便访问。您可以单击标签并聚焦该字段。
<div>
<label>
reps:{" "}
<input
value={set.reps}
name="reps"
step={1}
type="number"
onChange={handleOnChange(exIndex, setIndex)}
/>
</label>
</div>
<div>
<label>
weight:{" "}
<input
value={set.weight}
name="weight"
step={0.1}
type="number"
onChange={handleOnChange(exIndex, setIndex)}
/>
</label>
</div>
完整代码:
const workout = {
id: "123-234sdf-1213",
name: "wo name",
done: false,
exercises: [
{
name: "back squat",
sets: [
{
number: 0,
reps: 0,
weight: 0,
done: false
},
{
number: 1,
reps: 0,
weight: 0,
done: false
},
{
number: 2,
reps: 0,
weight: 0,
done: false
}
]
},
{
name: "leg press",
sets: [
{
number: 0,
reps: 0,
weight: 0,
done: false
},
{
number: 1,
reps: 0,
weight: 0,
done: false
},
{
number: 2,
reps: 0,
weight: 0,
done: false
}
]
}
]
};
export default function App() {
const [workoutState, setWorkoutState] = useState(workout);
const handleOnChange = (eIndex, sIndex) => (e) => {
const { name, value } = e.target;
setWorkoutState((workoutState) => ({
...workoutState,
exercises: workoutState.exercises.map((exercise, exerciseIndex) =>
exerciseIndex === eIndex
? {
...exercise,
sets: exercise.sets.map((set, setIndex) =>
setIndex === sIndex
? {
...set,
[name]: Number(value)
}
: set
)
}
: exercise
)
}));
};
return (
<div className="App">
<h1>{workoutState.name}</h1>
{workoutState.exercises.map((ex, exIndex) => (
<>
<h2>{ex.name}</h2>
{ex.sets.map((set, setIndex) => (
<div key={`${ex.name}_${setIndex}`}>
<h3>Set {setIndex}</h3>
<div>
<label>
reps:{" "}
<input
value={set.reps}
name="reps"
step={1}
type="number"
onChange={handleOnChange(exIndex, setIndex)}
/>
</label>
</div>
<div>
<label>
weight:{" "}
<input
value={set.weight}
name="weight"
step={0.1}
type="number"
onChange={handleOnChange(exIndex, setIndex)}
/>
</label>
</div>
</div>
))}
</>
))}
</div>
);
}