Лучший способ обновления нескольких состояний с помощью react hook внутри инструкции if?

#javascript #reactjs

#javascript #reactjs

Вопрос:

Я создаю простое приложение, в котором пользователь может увеличивать или уменьшать количество взрослых, детей и младенца.

Я хочу управлять этими состояниями внутри одного состояния в виде объекта, подобного приведенному ниже.

   const [numberState, setNumberState] = useState({
    adultCount: 0,
    childCount: 0,
    babyCount: 0
  });
  

И приведенный ниже код — это часть, в которой пользователь может увеличить или уменьшить количество взрослых, детей и младенца.

             <div className="tit">Number</div>
                <dl>
                    <dt><span>Adult</span></dt>
                        <dd>
                            <div className="number_count">
                                <button type="button" className="btn_minus" onClick={() => handleNumberState("adult", "minus")}>minus</button>
                                <span className="input-num"><input type="number" value={adultCount} /></span>
                                <button type="button" className="btn_plus" onClick={() => handleNumberState("adult", "plus")}>plus</button>
                            </div>
                        </dd>
                    <dt><span>Child</span></dt>
                        <dd>
                            <div className="number_count">
                                <button type="button" className="btn_minus" onClick={() => handleNumberState("child", "minus")}>minus</button>
                                <span className="input-num"><input type="number" value={childCount} /></span>
                                <button type="button" className="btn_plus" onClick={() => handleNumberState("child", "plus")}>plus</button>
                            </div>
                        </dd>
                    <dt><span>Baby</span></dt>
                        <dd>
                            <div className="number_count">
                                <button type="button" className="btn_minus" onClick={() => handleNumberState("baby", "minus")}>minus</button>
                                <span className="input-num"><input type="number" value={babyCount} /></span>
                                <button type="button" className="btn_plus" onClick={() => handleNumberState("baby", "plus")}>plus</button>
                            </div>
                        </dd>
                    </dl>
                </div>
  

Как вы можете видеть, когда пользователь нажимает кнопку, он передает два аргумента функции обратного вызова handleNumberState.

Тогда функция выглядит следующим образом.

 const handleNumberState = useCallback((gen, state) => {
  if (gen === "adult" amp;amp; state === "minus" amp;amp; numberState.adultCount > 0) {
    setNumberState(prevState => {
      return {
        adultCount: prevState.adultCount - 1,
        childCount: prevState.childCount,
        babyCount: prevState.babyCount
      }
    }) 
  }
  if (gen === "adult" amp;amp; state === "plus") {
    setNumberState(prevState => {
      return {
        adultCount: prevState.adultCount   1,
        childCount: prevState.childCount,
        babyCount: prevState.babyCount
      }
    }) 
  }
  if (gen === "child" amp;amp; state === "minus" amp;amp; numberState.childCount > 0) {
    setNumberState(prevState => {
      return {
        adultCount: prevState.adultCount,
        childCount: prevState.childCount - 1,
        babyCount: prevState.babyCount
      }
    }) 
  }
  if (gen === "child" amp;amp; state === "plus") {
    setNumberState(prevState => {
      return {
        adultCount: prevState.adultCount,
        childCount: prevState.childCount   1,
        babyCount: prevState.babyCount
      }
    }) 
  }
  if (gen === "baby" amp;amp; state === "minus" amp;amp; numberState.babyCount > 0) {
    setNumberState(prevState => {
      return {
        adultCount: prevState.adultCount,
        childCount: prevState.childCount,
        babyCount: prevState.babyCount - 1
      }
    }) 
  }
  if (gen === "baby" amp;amp; state === "plus") {
    setNumberState(prevState => {
      return {
        adultCount: prevState.adultCount,
        childCount: prevState.childCount,
        babyCount: prevState.babyCount   1
      }
    }) 
  }
})
  

В зависимости от того, какие аргументы передаются функции, состояние изменяется внутри инструкции if.

Но код кажется беспорядочным.

Есть ли лучший способ заставить это работать, используя меньше инструкций if?

Комментарии:

1. Вы видели это? reactjs.org/docs/hooks-reference.html#usereducer

2. @ksav Да, я забыл упомянуть, что я хочу использовать состояния только внутри компонента

Ответ №1:

Обновите обработчик, чтобы вместо него использовать поле и значение.

 const handleNumberState = (field, value) => {
  setNumberState(prevState => ({
    ...prevState,
    [field]: Math.max(0, prevState[field]   value),
  }))
}
  

Передайте имя поля и значение для добавления в обработчик.

 <div className="tit">Number</div>
<dl>
  <dt>
    <span>Adult</span>
  </dt>
  <dd>
    <div className="number_count">
      <button
        type="button"
        className="btn_minus"
        onClick={() => handleNumberState("adultCount", -1)}
      >
        minus
      </button>
      <span className="input-num">
        <input type="number" value={adultCount} />
      </span>
      <button
        type="button"
        className="btn_plus"
        onClick={() => handleNumberState("adultCount", 1)}
      >
        plus
      </button>
    </div>
  </dd>
  <dt>
    <span>Child</span>
  </dt>
  <dd>
    <div className="number_count">
      <button
        type="button"
        className="btn_minus"
        onClick={() => handleNumberState("childCount", -1)}
      >
        minus
      </button>
      <span className="input-num">
        <input type="number" value={childCount} />
      </span>
      <button
        type="button"
        className="btn_plus"
        onClick={() => handleNumberState("childCount", 1)}
      >
        plus
      </button>
    </div>
  </dd>
  <dt>
    <span>Baby</span>
  </dt>
  <dd>
    <div className="number_count">
      <button
        type="button"
        className="btn_minus"
        onClick={() => handleNumberState("babyCount", -1)}
      >
        minus
      </button>
      <span className="input-num">
        <input type="number" value={babyCount} />
      </span>
      <button
        type="button"
        className="btn_plus"
        onClick={() => handleNumberState("babyCount", 1)}
      >
        plus
      </button>
    </div>
  </dd>
</dl>
  

Редактировать лучший способ обновления нескольких состояний с использованием react hook внутри инструкции if

Небольшой оптимизацией было бы создать обработчик curried, который использует значение и возвращает функцию обратного вызова, использующую событие click

 const handleNumberState = value => e => {
  e.preventDefault();
  const { name } = e.target;
  setNumberState((prevState) => ({
    ...prevState,
    [name]: Math.max(0, prevState[name]   value)
  }));
};
  

Присвойте каждой кнопке name атрибут, который передается с событием click, например

 <button
  name="adultCount"
  type="button"
  className="btn_minus"
  onClick={handleNumberState(-1)}
>
  minus
</button>
<span className="input-num">
  <input type="number" value={adultCount} />
</span>
<button
name="adultCount"
  type="button"
  className="btn_plus"
  onClick={handleNumberState(1)}
>
  plus
</button>
  

Ответ №2:

Возможно, вы захотите useReducer

 const initialState = { adultCount: 0, childCount: 0, babyCount: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { ...state, [action.payload]: state[action.payload]   1 };
    case "decrement":
      return {
        ...state,
        [action.payload]: Math.max(state[action.payload] - 1, 0),
      };
    default:
      throw new Error();
  }
}

...

const [state, dispatch] = useReducer(reducer, initialState);

...

onClick={() => dispatch({type: "increment", payload:"adultCount"})}
  

Комментарии:

1. Это более чистый и масштабируемый подход, особенно когда что-то вроде redux

2. @malikbagwala Вам не обязательно использовать redux для использования useReducer .

3. @ksav Я хорошо осведомлен об этом. Я просто имел в виду, что это делает вещи согласованными, когда вы также используете что-то вроде redux

Ответ №3:

Измените свой обработчик на этот:

 const handleNumberState = useCallback((gen, state) => {
    setNumberState(prevState => {
      return { ...prevState, [gen]: prevState[gen]   state }
    }) 
  })
  

и обновите свои handleNumberState аргументы, как показано ниже, передав имя и значение, которое необходимо добавить, т.е. 1 (для плюса) и (-1) минус. Это уменьшит вашу функцию handleNumberState и поможет избавиться от длинного кода.

 <div className="tit">Number
      <dl>
        <dt><span>Adult</span></dt>
            <dd>
                <div className="number_count">
                    <button type="button" className="btn_minus" onClick={() => handleNumberState("adultCount", -1)}>minus</button>
                    <span className="input-num"><input type="number" value={adultCount} /></span>
                    <button type="button" className="btn_plus" onClick={() => handleNumberState("adultCount",  1)}>plus</button>
                </div>
            </dd>
        <dt><span>Child</span></dt>
            <dd>
                <div className="number_count">
                    <button type="button" className="btn_minus" onClick={() => handleNumberState("childCount", -1)}>minus</button>
                    <span className="input-num"><input type="number" value={childCount} /></span>
                    <button type="button" className="btn_plus" onClick={() => handleNumberState("childCount",  1)}>plus</button>
                </div>
            </dd>
        <dt><span>Baby</span></dt>
            <dd>
                <div className="number_count">
                    <button type="button" className="btn_minus" onClick={() => handleNumberState("babyCount", -1)}>minus</button>
                    <span className="input-num"><input type="number" value={babyCount} /></span>
                    <button type="button" className="btn_plus" onClick={() => handleNumberState("babyCount",  1)}>plus</button>
                </div>
            </dd>
        </dl>
    </div>
  

Ответ №4:

Мое предложение (и то, которое дали разработчики React в их FAQ) состояло бы в том, чтобы разделить эти переменные состояния на три отдельных, чтобы их можно было обновлять независимо друг от друга.

Как только они будут разделены, вы можете фактически полностью удалить всю handleNumberState() функцию и заменить onClick обратными вызовами, которые вызывают ваши методы set.

 const [adultCount, setAdultCount] = useState(0);
const [childCount, setChildCount] = useState(0);
const [babyCount, setBabyCount] = useState(0);

...

<div className="tit">Number</div>
   <dl>
      <dt><span>Adult</span></dt>
         <dd>
            <div className="number_count">
               <button type="button" className="btn_minus" onClick={() => if (adultCount > 0) { setAdultCount(adultCount - 1) }}>minus</button>
               <span className="input-num"><input type="number" value={adultCount} /></span>
               <button type="button" className="btn_plus" onClick={() => setAdultCount(adultCount   1)}>plus</button>
            </div>
         </dd>
      <dt><span>Child</span></dt>
         <dd>
            <div className="number_count">
               <button type="button" className="btn_minus" onClick={() => if (childCount > 0) { setChildCount(childCount - 1) }}>minus</button>
               <span className="input-num"><input type="number" value={childCount} /></span>
               <button type="button" className="btn_plus" onClick={() => setChildCount(childCount   1)}>plus</button>
            </div>
         </dd>
      <dt><span>Baby</span></dt>
         <dd>
            <div className="number_count">
               <button type="button" className="btn_minus" onClick={() => if (babyCount > 0) { setBabyCount(babyCount - 1) }}>minus</button>
               <span className="input-num"><input type="number" value={babyCount} /></span>
               <button type="button" className="btn_plus" onClick={() => setBabyCount(babyCount   1)}>plus</button>
            </div>
         </dd>
      </dl>
  </div>