Poznajemy hook useReducer w React i z TypeScriptem. Jeżeli przerobiliśmy poprzednie lekcje, to nie muszę nic więcej tłumaczyć, do dzieła zatem!
Ok, initState, typy akcji, typ akcja:
const initState = { count: 0, text: '' }
const enum REDUCER_ACTION_TYPE {
INCREMENT,
DECREMENT,
NEW_INPUT,
}
type ReducerAction = {
type: REDUCER_ACTION_TYPE,
payload?: string,
}
Payload nieobowiązkowy. Teraz funkcja reducer:
const reducer = (state: typeof initState, action: ReducerAction): typeof initState => {
switch (action.type) {
case REDUCER_ACTION_TYPE.INCREMENT:
return { ...state, count: state.count + 1 }
case REDUCER_ACTION_TYPE.DECREMENT:
return { ...state, count: state.count - 1 }
case REDUCER_ACTION_TYPE.NEW_INPUT:
return { ...state, text: action.payload ?? "" }
default:
throw new Error()
}
}
Dwie lekcje omawialiśmy jak to działa, więc w telegraficznym skrócie:
- Przyjmuje obiekt o typie wnioskowanym z initState
- Przyjmuje action typu ReducerAction
- Zwraca taki sam obiekt o type wnioskowanym z initState
- Funkcja upewnia się, że nie mutujemy obiektu, tylko zwracamy jego zmodyfikowaną kopię
- Funkcja osiąga to przez spread starego obiektu i nadpisanie interesującego nas pola
- W przypadku payload, które jest opcjonalne, ale próbuje nadpisać nam coś, co nie może być undefined, zabezpieczamy się, aby nigdy undefined nie przypisać do typu string
Ok, zaczynamy pisać komponent:
const CounterRed = () => {
const [state, dispatch] = useReducer(reducer, initState)
//(...)
}
export default CounterRed;
Swoją drogą mam nadzieję, że zaimportowaliśmy useReducer. Mam też nadzieję, że tamte treści trzymamy powyżej poziomu RFC. Ok, w odróżnieniu od useState, useReducer dobrze napisany nie wymaga od nas podawania typów, tu się wszystko samo wnioskuje.
Dopiszmy sobie jsx (tsx) abyśmy wiedzieli, dokąd zmierzamy:
return (
<>
<h1>Current count: {state.count}</h1>
<div>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
<input type="text" onChange={handleTextInput} />
<h2>{state.text}</h2>
</>
)
To teraz tylko pozostaje nam napisać te funkcje, które korzystają z dispatch:
const increment = () => dispatch({ type: REDUCER_ACTION_TYPE.INCREMENT })
const decrement = () => dispatch({ type: REDUCER_ACTION_TYPE.DECREMENT })
const handleTextInput = (e: ChangeEvent<HTMLInputElement>) => {
dispatch({
type: REDUCER_ACTION_TYPE.NEW_INPUT,
payload: e.target.value
})
}
Mam nadzieję, że w tej lekcji się wszystko nam pięknie złożyło do kupy, oto wynik:
const CounterRed = () => {
const [state, dispatch] = useReducer(reducer, initState)
const increment = () => dispatch({ type: REDUCER_ACTION_TYPE.INCREMENT })
const decrement = () => dispatch({ type: REDUCER_ACTION_TYPE.DECREMENT })
const handleTextInput = (e: ChangeEvent<HTMLInputElement>) => {
dispatch({
type: REDUCER_ACTION_TYPE.NEW_INPUT,
payload: e.target.value
})
}
return (
<>
<h1>Current count: {state.count}</h1>
<div>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
<input type="text" onChange={handleTextInput} />
<h2>{state.text}</h2>
</>
)
}
export default CounterRed;
Proste. Więcej TSa i Reacta niedługo!