反应:更改深层嵌套状态优化

时间:2020-10-30 17:58:00

标签: reactjs state immutability nested-datalist

我想出了一个解决方案,用于在React中的第二级嵌套状态中更改属性,该状态不可扩展且效率不高。为了更改handleOnChangereps,如何重构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>
  );
}

1 个答案:

答案 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>

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