简体   繁体   English

React:改变深度嵌套状态优化

[英]React: Change deeply nested state optimisation

I come up with a solution for change the properties in a second level nested state in React that is not scalable and it doesn't seem quite efficient.我想出了一个解决方案,用于在 React 中更改二级嵌套状态中的属性,该解决方案不可扩展,而且看起来效率不高。 How can you do a refactor of handleOnChange method in order to change reps and weight ?您如何重构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>
  );
}

I would say you are on the right track with how you want to update the nested state, but you do have a state mutation in your current handleOnChange implementation.我会说您在如何更新嵌套状态方面处于正确的轨道上,但是您当前的handleOnChange实现中确实存在状态突变。 You are mutating exercises[exIndex] .你正在改变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,
  });
};

You should shallow copy each nested level of state you intend to update.您应该浅复制您打算更新的每个嵌套状态级别。

I recommend using functional state updates so if for any reason more than a single state update is enqueued during a render cycle the update will work from the previous update versus from using the state from the render cycle the update was enqueued.我建议使用功能状态更新,因此如果由于某种原因在渲染周期期间有多个状态更新入队,则更新将从上次更新开始,而不是使用更新入队的渲染周期中的状态。

I also prefer making the handler a curried function.我也更喜欢使处理程序成为咖喱函数。 It takes the indices as arguments and returns a function that consumes the onChange event object.它将索引作为参数并返回一个使用onChange事件对象的函数。 This allows you to remove the anonymous function declaration and need to proxy the event object yourself when attaching the handler, ie onChange={handleOnChange(exIndex, setIndex)} versus onChange={e => handleOnChange(e, exIndex, setIndex)}这允许您删除匿名函数声明并在附加处理程序时需要自己代理事件对象,即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
    )
  }));
};

The inputs.输入。 I added a step attribute and attach the curried handler.我添加了一个step属性并附加了咖喱处理程序。 I also placed each input in a label element for accessibility;我还将每个输入放在一个label元素中以方便访问; you can click the label and focus the field.您可以单击标签并聚焦该字段。

<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

Full code:完整代码:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM