์ด ๊ธ์ udemy์ React ์๋ฒฝ ๊ฐ์ด๋ with Redux, Next.js, TypeScript ๊ฐ์ข๋ฅผ ๋ฐํ์ผ๋ก ์ ๋ฆฌํ ๋ด์ฉ์ ๋๋ค.
139. ๋ชจ๋ ์๊ฐ
์ง๋ ์น์ ์์ ์ค์ํ ๋ฆฌ์กํธ ๊ฐ๋ ๋ค์ ์ดํด๋ณด์์ต๋๋ค. useEffect, useContext, useReducer ๋ฑ๋ฑ / ์ ๊ฐ๋ ์ ๋ณต์ตํ๋ฉด์ ๋ค์ ์ดํด๋ณด๋๋ก ํฉ์๋ค.
140. ์ค์ ์์ํ๊ธฐ
- Layout folder : ex. Header
- UI folder: ex. Input
141. "ํค๋" ์ปดํฌ๋ํธ ์ถ๊ฐํ๊ธฐ
- header ์ปดํฌ๋ํธ ์ถ๊ฐ
142. Adding the "Cart" Button Component
- Cart Button ์ปดํฌ๋ํธ ์ถ๊ฐ
143. Adding a "Meals" Component
- Meals ์ปดํฌ๋ํธ ์ถ๊ฐ
144. Adding Individual Meal Items & Displaying Them
- Individual Meal Items ์ถ๊ฐ
145. Adding a Form
const Input = (props) => {
return (
<div className={classes.input}>
<label htmlFor={props.input.id}>{props.label}</label>
<input id={props.input.id} {...props.input} />
</div>
);
};
export default Input;
...props.input / {type: 'text' }
const MealItemForm = (props) => {
return (
<form className={classes.form}>
<Input
label="Amount"
input={{
id: "amount",
type: "number",
min: "1",
max: "5",
step: "1",
defaultValue: "1",
}}
/>
<button>+ Add</button>
</form>
);
};
export default MealItemForm;
146. Fixing Form Input IDs
<Input
label="Amount"
input={{
id: "amount",
type: "number",
min: "1",
max: "5",
step: "1",
defaultValue: "1",
}}
/>
2๊ฐ์ง ๋จ์ ์ด ์กด์ฌํ๋ค.
1. ๋์ผํ ์ฒซ๋ฒ ์งธ ์ ๋ ฅ ์์๊ฐ ์ ํ๋๋ค.
2. screen reader๋ ๋ ์ด๋ธ + ์ ๋ ฅ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ฐ๊ฒฐํ ์ ์๋ค. (๋ชจ๋ label์ด ๋์ผํ ์ ๋ ฅ์ ๊ฐ๋ฅดํค๊ธฐ ๋๋ฌธ์)
<Input
label='Amount'
input={{
id: 'amount_' + props.id, // this changed!
type: 'number',
min: '1',
max: '5',
step: '1',
defaultValue: '1',
}}
/>
<MealItemForm id={props.id} />
<MealItem
id={meal.id} // this is new!
key={meal.id}
name={meal.name}
description={meal.description}
price={meal.price}
/>
147. Working on the "Shopping Cart" Component
- ์ฅ๋ฐ๊ตฌ๋ ์ปดํฌ๋ํธ ๋ง๋ค๊ธฐ
- ์ถํ modal Wrapper๋ฅผ ์ถ๊ฐํ์ฌ ์ค๋ฒ๋ ์ด๋ก ๋ ๋๋งํ ์์ .
148. Adding a Modal via a React Portal
- public/index.html ์ ์๋์ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ค.
<div id="overlays"></div>
149. Managing Cart & Modal State
- cart ๋ฒํผ ํด๋ฆญ์์๋ modal ์ด ๋ณด์ด๊ณ , close ๋ฒํผ ํน์ backdrop์ ํด๋ฆญํ๋ฉด modal์ ๋ซ์ ์ ์๋๋ก ๊ตฌํํ๋ค.
- ์ปจํ ์คํธ๋ฅผ ์ฌ์ฉํ ์๋ ์์ง๋ง, ๋ฌธ์ ๊ฐ ์๋ค. ์ปจํ ์คํธ๋ฅผ ์ฌ์ฉํ ๋ cart๋ฅผ ๋ซ๊ธฐ ์ํด backdrop์ ํด๋ฆญ์ ๋ฐ์ธ๋ฉ ํ๋ฉด ์ด backdrop์ ๋งค์ฐ ํ์ ์ ์ผ๋ก ๋ง๋ค๊ฒ ๋๋ ๊ฒ์ด๋ค. ์ฆ ๋ค๋ฅธ ์ข ๋ฅ์ ์ฝํ ์ธ ์์๋ ์ด modal์ ์ฌ์ฉํ ์ ์๊ฒ ๋๋ค. backdrop ํด๋ฆญ ์ ํญ์ cart๊ฐ ๋ซํ๊ธฐ ๋๋ฌธ์ด๋ค. ๋ฐ๋ผ์ ์ด๊ณณ์์๋ props์ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ๋์ ์ ์๋ค. ๋ชจ๋ฌ์ ์ฌ ์ฌ์ฉ์ ๊ฐ๋ฅํ๊ฒ ํด์ฃผ๊ธฐ ๋๋ฌธ์ด๋ค.
150. Adding a Cart Context
- dynamic context provider
- cart component, header component, meals component
151. Using the Context
- HeaderCartButton ์ปดํฌ๋ํธ์์ ์ปจํ ์คํธ๋ฅผ ์ฌ์ฉํด๋ณด๋๋ก ํ์.
152. Adding a Cart Reducer
Any components affected by the context are re-evaluated whnever the cart data changes.
const cartReducer = (state, action) => {
if (action.type === "ADD") {
const updatedItems = state.items.concat(action.item);
const updatedTotalAmount =
state.totalAmount + action.item.price * action.item.amount;
return {
items: updatedItems,
totalAmount: updatedTotalAmount
}
}
return defaultCartState;
};
- ์ธ๋ถ์ cartReducer ํจ์๋ฅผ ์ปดํฌ๋ํธ ์ธ๋ถ์ ์ถ๊ฐํ๋ค. ์ ๋ฆฌ๋์ ํจ์๋ ๊ทธ ์ปดํฌ๋ํธ์์๋ ์ฐพ์ ์ ์๋ ์ฃผ๋ณ ๋ฐ์ดํฐ๊ฐ ์ ํ ํ์ํ์ง ์๊ธฐ ๋๋ฌธ์ด๋ค. ๋ํ ์ปดํฌ๋ํธ๊ฐ ์ฌํ๊ฐ ๋ ๋๋ง๋ค ์ฌ์์ฑ๋์ด์๋ ์๋๋ค.
const cartReducer = (state, action) => {
if (action.type === "ADD") {
const updatedItems = state.items.concat(action.item);
const updatedTotalAmount =
state.totalAmount + action.item.price * action.item.amount;
return {
items: updatedItems,
totalAmount: updatedTotalAmount
}
}
return defaultCartState;
};
- state๋ ๋ฆฌ๋์์ ์ํด ๊ด๋ฆฌ๋๋ state์ ์ต์ state ์ค๋ ์ต์ด๋ค. ๊ทธ๋ฆฌ๊ณ ๋ฆฌ๋์ ํจ์์์ ์ state๋ฅผ ๋ฐํํด์ผ ํ๋ค.
# useReducer๋ฅผ ์ฌ์ฉํ state ์์ฑ
const [cartState, dispatchCartAction] = useReducer(
cartReducer,
defaultCartState
);
- useReducer๋ ์ ํํ ๋ ๊ฐ์ ์์๋ก ๋ ๋ฐฐ์ด์ ๋ฐํํ๋ค. array destructuring์ ์ฌ์ฉํ์ฌ ๋ฐฐ์ด์์ ์์๋ค์ ๊บผ๋ด ๋ณ๋์ ์์์ ์ ์ฅํ ์ ์๋ค. ์ฒซ๋ฒ์งธ ์์๋ ํญ์ state snapshot, ๋๋ฒ์งธ ์์๋ ํจ์์ด๋ค. ์ด๋ฅผ ํฅํด ๋ฆฌ๋์์ ์ก์ ์ ์ ๋ฌํ ์ ์๋ค.
- useReducer์ ์ฒซ๋ฒ์งธ ์ธ์๋ ๋ฆฌ๋์ ํจ์๋ฅผ ๊ฐ๋ฆฌํจ๋ค. ์ด๋ฅผ ๋ฐ๋ก ์คํํ์ง ์๊ณ , ๊ฐ๋ฆฌํจ๋ค. ์ดํ ๋ฆฌ์กํธ๊ฐ ์คํ๋๊ณ , ์ด๊ธฐ state๋ฅผ ์ค์ ํ๋ค.
# cartContext
const cartContext = {
items: cartState.itmes,
totalAmount: cartState.totalAmount,
addItem: addItemToCartHandler,
removeItem: removeItemFormCartHandler,
};
- cartContext ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฑํ๊ธฐ ์ํด cartState๊ฐ ํ์ํ๋ค. ๋น ๋ฐฐ์ด์ ํ๋ ์ฝ๋ฉ ํ๋ ๋์ cartState.Items์ ์ฌ์ฉํ๋๋ก ํ์.
const addItemToCartHandler = (item) => {
dispatchCartAction({ type: "ADD", item: item });
};
const removeItemFormCartHandler = (id) => {
dispatchCartAction({ type: "REMOVE", id: id });
};
- ์ผ๋ฐ์ ์ผ๋ก ์ก์ ์ ๊ฐ์ฒด์ด๋ค. ์ด๋ค ์์ฑ์ ๊ฐ์ง๊ณ ์๊ณ ์ด๋ฅผ ํตํด ํด๋น ์ก์ ์ ๋ฆฌ๋์ค ํจ์ ๋ด๋ถ์์ ์๋ณํ ์ ์๋ค. ์์ฑ ์ด๋ฆ์ผ๋ก type์ ์ฌ์ฉํ๋๋ก ํ๊ฒ ๋ค. (์ก์ ์๋ณ์ ์ํด, ์ด๋ฆ์ ๋ง์๋๋ก ๋ถ์ฌ๋ ๊ด์ฐฎ๋ค)
- ๋ฌธ์์ด์ธ ๊ฒฝ์ฐ์ ๊ท์ฝ์ ๋ฐ๋ผ ๋ชจ๋ ๋๋ฌธ์๋ก ์์ฑํ๋ค. ๊ท์ฝ์ผ ๋ฟ! ์ด์ง๋ง, ์ผ๋ฐ์ ์ผ๋ก ๊ท์ฝ๋๋ก ํ๋ค. ADD ๋ฅผ ์ถ๊ฐํ๋ค. ์ดํ ๋ฆฌ๋์ ํจ์์ ํญ๋ชฉ์ ์ถ๊ฐํ๊ธฐ ์ํด ์ก์ ์ ์ผ๋ถ๋ก, ํญ๋ชฉ์ ์ ๋ฌํ๊ณ ์ถ์ผ๋ฏ๋ก ์ก์ ๊ฐ์ฒด์ ๋ ๋ฒ์งธ ์์ฑ์ ์ถ๊ฐํ๋ค. ์ด๋ฆ์ item!
const cartReducer = (state, action) => {
if (action.type === "ADD") {
const updatedItems = state.items.concat(action.item);
const updatedTotalAmount =
state.totalAmount + action.item.price * action.item.amount;
return {
items: updatedItems,
totalAmount: updatedTotalAmount
}
}
return defaultCartState;
};
- updatedItems ๋ณ์๋ฅผ ๋ง๋ค๊ณ , state.items๋ก ์ค์ ํ๋ค. ํ์ฌ state ์ค๋ ์ต์ ํญ๋ชฉ์ ๋ฆฌ๋์์์ ์ฒซ๋ฒ์งธ ์ธ์๋ก๋ถํฐ ์ป๊ณ ์๋ค. ๊ทธ๋ฆฌ๊ณ concate๋ฅผ ํธ์ถํ๋ค. ์ด๋ js ๋ฉ์๋๋ก, ๋ฐฐ์ด์ ์ ํญ๋ชฉ์ ์ถ๊ฐํด์ค๋ค. ๊ทธ๋ฌ๋ push์ ๋ฌ๋ฆฌ ๊ธฐ์กด ๋ฐฐ์ด์ ํธ์งํ๋ ๊ฒ ์๋๋ผ ์ ๋ฐฐ์ด์ ๋ฐํํ๋ค. (์ค์!)
- state๋ฅผ ๋ณ๊ฒฝํ ์ ์๋ ๋ฐฉ์์ผ๋ก ์ ๋ฐ์ดํธ ํ๊ณ ์ถ๋ค๋ ๊ฑด ์ด์ state ์ค๋ ์ต์ ํธ์งํ๊ณ ์ถ์ง ์๋๋ค๋ ๋ป์ด๋ค. js์ ์ฐธ์กฐ ๊ฐ ๋๋ฌธ. ์ฆ ๋ฉ๋ชจ๋ฆฌ์ ๊ธฐ์กด ๋ฐ์ดํฐ๊ฐ ํธ์ง๋๋ค. ๋ฆฌ์กํธ๋ ๊ทธ๊ฒ์ ๋ํด ๋ชจ๋ฆ.
- ๋ฐ๋ผ์ ์์ ํ ์๋ก์ด state ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ๋ฐํํ๊ณ ์ถ๋ค๋ฉด concat์ ์์ ํ ์๋ก์ด ๋ฐฐ์ด์ ์ค๋ค. ๋ฉ๋ชจ๋ฆฌ์์ ์ด์ ๋ฐฐ์ด์ ํธ์งํ๋ ๋์ ์. (๋ ์ข์)
153. Working with Refs & Forward Refs
const [amountIsValid, setAmountIsValid] = useState(true);
const amountInputRef = useRef();
const submitHandler = (event) => {
event.preventDefault();
const enteredAmount = amountInputRef.current.value;
const enteredAmountNumber = +enteredAmount;
if (
enteredAmount.trim().length == 0 ||
enteredAmountNumber < 1 ||
enteredAmountNumber > 5
) {
setAmountIsValid(false);
return;
}
props.onAddToCart(enteredAmountNumber);
};
- event.PreventDefault๋ฅผ ์ฌ์ฉํ์ฌ ๋ธ๋ผ์ฐ์ ๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ํ์ด์ง๋ฅผ ๋ค์ ๋ก๋ํ๋ ๊ฒ์ ๋ง์.
- ์ ๋ ฅ๋ input ์๋์ ๊ฐ์ ธ์ค๊ธฐ ์ํด ref๋ฅผ ์ฌ์ฉํ์. ์ฌ์ฉ์ ์ง์ ์ปดํฌ๋ํธ์ด๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ์ ์ง์ ์ปดํฌ๋ํธ์์ ref๊ฐ ์๋ํ๋๋ก ์ ์ฉํด์ผ ํ๋ค.
const Input = React.forwardRef((props, ref) => {
return (
<div className={classes.input}>
<label htmlFor={props.input.id}>{props.label}</label>
<input ref={ref} {...props.input} />
</div>
);
});
export default Input;
- ์ปดํฌ๋ํธ ํจ์๋ฅผ React.forwardRef ๋ก ๊ฐ์ธ๋ฉด, ๊ทธ ์ปดํฌ๋ํธ ํจ์๋ FOrwardRef์ ์ธ์๊ฐ ๋๋ค. ๊ทธ๋ฌ๋ฉด ref๋ฅผ ์ป์ ์ ์๋ค. ref ํ๋กญ์ ํตํด ์ปดํฌ๋ํธ์ ์ค์ ์ด ๊ฐ๋ฅํ๋ค.
154. Outputting Cart Items
const cartItems = (
<ul className={classes["cart-itemse"]}>
{cartCtx.items.map((item) => (
<CartItem
key={item.id}
name={item.name}
amount={item.amount}
price={item.price}
onRemove={cartItemRemoveHandler.bind(null, item.id)}
onAdd={cartItemAddHandler.bind(null, item)}
/>
))}
</ul>
);
Now on both these functions you should call bind and bind no item ID. This ensures that the idea of the to be added or removed item is passed here to remove handler and on end you should also call bind and bind null and pass the overall item. You'll learn that bind pre-configure as a function for future execution and basically allows you to pre-configure the argument that function will receive when it's being executed. And that's something we need here to ensure that both these functions do receive the ID or the item respectively.
155. Working on a More Complex Reducer Logic
- ๋ณด์ถฉ ์์
156. Making Items Removable
- ๋ณด์ถฉ ์์
157. Using the useEffect Hook
- ๋ณด์ถฉ ์์
158. Module Resources
- resources