0

我想出了一个解决方案,用于更改 React 中二级嵌套状态的属性,该状态不可扩展,而且似乎效率不高。您如何对handleOnChange方法进行重构以更改repsweight

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

4

1 回答 1

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>

编辑 react-change-deeply-nested-state-optimisation

完整代码:

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>
  );
}
于 2020-10-30T19:08:04.700 回答