Poznajemy coś takiego, jak funkcja jako children prop. Sam tego nie znałem i dopiero na przykładzie analizy cudzego kodu zauważyłem, że tak można. Zaczynajmy!
Ok, zobaczmy nad czym pracujemy:
import { useReducer, ChangeEvent } from 'react'
const initState = { count: 0, text: '' }
const enum REDUCER_ACTION_TYPE {
INCREMENT,
DECREMENT,
NEW_INPUT,
}
type ReducerAction = {
type: REDUCER_ACTION_TYPE,
payload?: string,
}
To będzie jakiś reducer. Tak:
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()
}
}
Już to omawialiśmy, teraz zobaczmy na RFC:
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;
Chyba wszystko jasne, jak nie to analizujemy do skutku + czytamy poprzednie lekcje. Teraz jak ten komponent wygląda w App.tsx:
<CounterRed/>
Tylko tak może wyglądać, bo nie pozwalamy mu mieć żadnych propsów. Żadnych, także dzieci, musi być self-closing (robimy w TS). W sumie nie musi, bo nie doprecyzowaliśmy typu propsów, ale teraz to zrobimy.
I zrobimy coś naprawdę dziwnego:
type ChildrenType = {
children: (num: number) => JSX.Element
}
const CounterRed = ({ children }: ChildrenType) => {
O co tu chodzi? Children jako funkcja? Można tak? Ok, zmieńmy sobie JSX:
return (
<>
<h1>{children(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;
Teraz pozostaje nam tę funkcję jako dziecko przekazać:
<CounterRed>{(num: number) => <>Current Count: {num}</>}</CounterRed>
Funkcja zgadza się z interfejsem, zaś typ state.count zgadza się z typem num, wszystko oplecione fragmentem <></> oraz nawiasami wąsatymi {}, zatem działa, działa bez problemu.
Można tak, pytanie jest jak często będziemy coś takiego w kodzie widzieli. Warto wiedzieć, że można. Więcej Reacta niedługo!