#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>
Небольшой оптимизацией было бы создать обработчик 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>