[STUDY 진행 과정은 깃허브에 계속 commit 하고 있습니다] (많관부!)
https://github.com/just-stopyoon/Seeds-study
5주차 과제 내용
장바구니 항목 업데이트하기
"+" 버튼을 누르면 해당 숫자가 증가하도록 handleIncrease 함수 완성하기
useState 컴포넌트 저장소
컴포넌트별 저장소를 state 라고 한다.
지역변수는 컴포넌트가 렌더링 될 때마다 초기화가 이루어진다. 따라서, 지역변수의 변경 사항을 기억하지 못한다.
컴포넌트가 업데이트되어 변경하려면?
렌더링 사이에 변경된 데이터를 저장하고 있어야 하고, 새로운 데이터로 렌더링 되도록 해야한다.
- useState의 리턴값인 "state 상태변수"는 렌더링간에 데이터를 유지해준다.
- useState의 두 번째 리턴값인 "state setter 함수"는 상태 변수를 업데이트하여 컴포넌트 리렌더링을 유발한다.
* 주의 : 상태변수를 업데이트 할 때 증감연산자를 사용하면 화면의 렌더링이 발생하지 않는다.
그리고 useState를 사용할 때 let이 아닌 const로 선언하는 이유는
변수의 수정이 일어나는 게 아니라, 변경된 값으로 새로 상태 변수가 생성되기 때문에 const로 선언한다.
값 자체를 수정하는 게 아닌 새로 상태 변수를 생성하는 것이다.
그리고 상태변수의 초기값은 첫 번째 렌더링에서만 쓰인다.
두 번째 렌더링부터는 새롭게 setter 함수에 넣은 값으로 상태변수 생성을 한다.
state는 내부함수에서는 만들 수 없고 무조건!
사용하려면 컴포넌트 최상단에 만들어두고 아래에서 사용해야 한다.
객체 state 업데이트
객체를 업데이트하는 경우 state의 객체를 직접 변경하면 안된다. (언제나 읽기 전용으로!)
따라서, 새로운 객체를 생성하거나 기존 객체의 복사본을 사용해야 한다.
리렌더링을 발생시키려면 새 객체를 생성하여 전달해야 한다.
import './App.css';
import { useState } from 'react';
export default function movingDot() {
const [position, setPosition] = useState({
x: 0,
y: 0, // x,y좌표를 다루는 객체
});
console.log(position);
return (
<div
onPointerMove={(e) => {
setPosition({
x: e.clientX,
y: e.clientY,
});
}}
style={{
position: 'relative',
height: '100vh',
border: '10px solid red',
}}
></div>
);
}
객체의 상태를 업데이트 하고 싶은데 객체의 일부속성만 업데이트 해야한다면
스프레드 연산자를 사용하여 객체를 복사한 뒤, 일부 속성만 변경시켜 준다.
import './App.css';
import { useState } from 'react';
export default function Form() {
const [person, setPerson] = useState({
firstName: 'xiao',
lastName: 'dejun',
email: 'djxiao@weishenv.com',
});
function handleChangeFirstName(e) {
setPerson({
// 스프레드 연산자를 사용해서 전개합니다
...person,
// 업뎃하고 싶은 속성
firstName: e.target.value,
});
}
function handleChangeLastName(e) {
setPerson({
...person,
lastName: e.target.value,
});
}
function handleChangeEmail(e) {
setPerson({
...person,
email: e.target.value,
});
}
return (
<>
<div>
<label>
First name:
<input value={person.firstName} onChange={handleChangeFirstName} />
</label>
</div>
<div>
<label>
Last name:
<input value={person.lastName} onChange={handleChangeLastName} />
</label>
</div>
<div>
<label>
Email:
<input value={person.email} onChange={handleChangeEmail} />
</label>
</div>
<p>
{person.firstName} {person.lastName} ({person.email})
</p>
</>
);
}
중첩된 내부 객체를 업데이트 해야 한다면?
이것도 중첩된 내부 객체를 복사해서 업데이트를 해줘야 한다.
import { useState } from 'react';
import './App.css';
export default function Form() {
const [person, setPerson] = useState({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
},
});
function handleNameChange(e) {
setPerson({
...person,
name: e.target.value,
});
}
function handleTitleChange(e) {
setPerson({
// 객체를 받아온뒤(복사)
...person,
artwork: {
// 객체안에서 또 받아옵니다(중첩객체)
...person.artwork,
title: e.target.value,
},
});
}
function handleCityChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
city: e.target.value,
},
});
}
function handleImageChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
image: e.target.value,
},
});
}
return (
<>
<div>
<label>
Name:
<input value={person.name} onChange={handleNameChange} />
</label>
</div>
<div>
<label>
Title:
<input value={person.artwork.title} onChange={handleTitleChange} />
</label>
</div>
<div>
<label>
City:
<input value={person.artwork.city} onChange={handleCityChange} />
</label>
</div>
<div>
<label>
Image:
<input value={person.artwork.image} onChange={handleImageChange} />
</label>
</div>
<p>
<i>{person.artwork.title}</i>
{' by '}
{person.name}
<br />
(located in {person.artwork.city})
</p>
<img src={person.artwork.image} alt={person.artwork.title} />
</>
);
}
배열 state 업데이트
배열을 업데이트 할 경우에도, 새 배열 생성 또는 기존 배열을 복사하여 업데이트 해야 한다.
하지만, 여기에는 몇 가지 규칙이 있는데,
1. push, pop, splice로 배열을 직접 변경하면 안된다.
2. concat, [...arr], map, filter, slice로 새 배열을 반환하여 업데이트해야 한다.
3. 보통 jsx의 배열반복문은 map이라고 생각하면 된다.
배열에 항목을 추가해야할 경우,
import { useState } from 'react';
import './App.css';
export default function List() {
const [name, setName] = useState('');
const [artists, setArtists] = useState([]);
const [nextId, setNextId] = useState(0);
// 배열상태 업데이트
function handleAddArtist() {
setName('');
setArtists([...artists, { id: nextId, name: name }]);
setNextId(nextId + 1);
}
// 버튼클릭시 배열요소를 객체{id:nextId,name:name}로 추가하기
return (
<>
<h2>영감을 주는 조각가 : </h2>
<form onSubmit={(e) => e.preventDefault()}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<button type="submit" onClick={handleAddArtist}>
Add
</button>
</form>
<ul>
{/* 데이터뿌리기 */}
{artists.map((artist) => (
<li key={artist.id}>{artist.name}</li>
))}
</ul>
</>
);
}
배열에서 항목을 제거하려면?
import { useState } from 'react';
import './App.css';
// 초기 데이터
const initialArtists = [
{ id: 0, name: 'Marta Colvin Andrade' },
{ id: 1, name: 'Lamidi Olonade Fakeye' },
{ id: 2, name: 'Louise Nevelson' },
];
export default function List() {
const [artists, setArtists] = useState(initialArtists);
function handleDeleteArtist(artist) {
setArtists(artists.filter((item) => item.id !== artist.id));
// 버튼을 눌렀을때 해당하는 id를 제외한 배열을 반환합니다!
}
return (
<>
<h2>영감을 주는 조각가 : </h2>
<ul>
{/* map을 사용하는 이유
(배열의 길이만큼 새로 반환해서 보여주기 위해서에요) */}
{artists.map((artist) => (
<li key={artist.id}>
{artist.name}
<button type="button" onClick={() => handleDeleteArtist(artist)}>
삭제하기
</button>
</li>
))}
</ul>
</>
);
}
1. artist로 전달 된 배열 요소의 id와, 배열 전체 요소의 id를 비교한다.
2. 버튼을 눌렀을 때 해당하는 id를 제외한 배열을 새로 반환해서 삭제시키는 로직이다.
배열을 변환해야 한 때는?
import { useState } from 'react';
// 초기 데이터
const initialShapes = [
{ id: 0, type: 'circle', x: 50, y: 100 },
{ id: 1, type: 'square', x: 150, y: 100 },
{ id: 2, type: 'circle', x: 250, y: 100 },
];
export default function ShapeEditor() {
const [shapes, setShapes] = useState(initialShapes);
function handleClick() {
// map을 통해 새로 생성된 배열을 담을 nextShape입니다
const nextShapes = shapes.map((shape) => {
if (shape.type === 'square') return shape;
// 타입이 네모면 그냥 반환
else {
return {
...shape,
y: shape.y + 50,
};
}
});
setShapes(nextShapes);
// map으로 새로 만들어진 배열을 setter함수로 리렌더링 해줍니다
}
return (
<>
<button onClick={handleClick}>원만 아래로 50px씩 이동</button>
{shapes.map((shape) => (
<div
key={shape.id}
style={{
background: 'purple',
position: 'absolute',
left: shape.x,
top: shape.y,
borderRadius: shape.type === 'circle' ? '50%' : '',
width: 20,
height: 20,
}}
/>
))}
</>
);
}
배열의 내부 항목을 교체하려면,
import { useState } from 'react';
import './App.css';
export default function CounterList() {
const [counters, setCounters] = useState([0, 0, 0]);
// onClick했을때의 이벤트 핸들러
// 클릭시 전달된 i와 배열요소의 index값이 동일하면 1증가하고, 같지않으면 그냥 기존값을 리턴하여 새로운 배열을 생성해요
function handleIncrease(i) {
// 새로운 배열을 생성합니다
const nextCounter = counters.map((counter, index) => {
if (i === index) return counter + 1;
else return counter;
});
// 새로운 배열로 리렌더링합니다
setCounters(nextCounter);
}
return (
<>
<ul>
{counters.map((counter, i) => (
<li key={i}>
{counter}
<button type="button" onClick={() => handleIncrease(i)}>
1씩 증가
</button>
</li>
))}
</ul>
</>
);
}
이렇게 하면 된다!
5주차 과제
장바구니 항목 업데이트 하기 ("+" 버튼을 누르면 해당 숫자가 증가하도록 handleIncrease 함수를 완성해보자)
import { useState } from 'react';
const initialProducts = [{
id: 0,
name: 'Baklava',
count: 1,
}, {
id: 1,
name: 'Cheese',
count: 5,
}, {
id: 2,
name: 'Spaghetti',
count: 2,
}];
export default function ShoppingCart() {
const [
products,
setProducts
] = useState(initialProducts)
function handleIncreaseClick(productId) {
// 이 부분을 작성해주세요 :)
}
return (
<ul>
{products.map(product => (
<li key={product.id}>
{product.name}
{' '}
(<b>{product.count}</b>)
<button onClick={() => {
handleIncreaseClick(product.id);
}}>
+
</button>
</li>
))}
</ul>
);
}
이거는 예전에 코딩애플 강의에서도 진행했었던 거라 수월하게 할 수 있었다
리셋 버튼까지 만들고 싶었는데 시간관계상 패스,,,
개강 너무 힘들다 흑흑
import React, { useState } from "react";
import "./App.css";
import Profile from "./Profile";
import Header from "./frame/Header";
import Footer from "./frame/Footer";
import Button from "./frame/Button";
function App() {
const [isBodyColor, setIsBodyColor] = useState(false);
const toggleBodyColor = () => {
setIsBodyColor(!isBodyColor);
};
const initialProducts = [{
id: 0,
name: '스테이크',
count: 1,
}, {
id: 1,
name: '된장찌개',
count: 5,
}, {
id: 2,
name: '스파게티',
count: 2,
}];
const [products, setProducts] = useState(initialProducts);
function handleIncreaseClick(productId) {
const updatedProducts = products.map(product => {
if (product.id === productId) {
return {
...product,
count: product.count + 1
};
}
return product;
});
setProducts(updatedProducts);
}
return (
<div className={`App-back ${isBodyColor ? "change-color" : ""}`}>
<Header />
<Profile />
<Section products={products} onIncreaseClick={handleIncreaseClick} />
<Button onClick={toggleBodyColor} isBodyColor={isBodyColor} />
<Footer />
</div>
);
}
function Section({ products, onIncreaseClick }) {
return (
<div className="set-middle">
<p className="post-box-title">장바구니</p>
<div className="square">
<ul className="post-box">
{products.map(product => (
<Item key={product.id} name={product.name} count={product.count} onIncreaseClick={() => onIncreaseClick(product.id)} />
))}
</ul>
</div>
</div>
);
}
function Item({ name, count, onIncreaseClick }) {
return (
<li className="item">
{name} <b className = "countNum">{count}</b>
<button className = "item-button" onClick={onIncreaseClick}>+</button>
</li>
);
}
export default App;


5주차도 얼레벌레 마무리!
힘들다 흑ㅜㅡㅜ
'Front-end > React 리액트' 카테고리의 다른 글
| [Seeds-study] (React/리액트) 4주차 이벤트 처리와 폼 (1) | 2024.03.18 |
|---|---|
| [Seeds-study] (React/리액트) 3주차 컴포넌트에 prop전달 / 조건부 렌더링 (0) | 2024.03.10 |
| [Seeds-study] (React/리액트) 2주차 JSX와 컴포넌트 기초 (0) | 2024.03.03 |
| [Seeds-study] (React/리액트) 1주차 React 개발 환경 설정 (2) | 2024.02.25 |
| [React 리액트/CSS] CSS가 제대로 적용되지 않을 때 해결법 (2) | 2024.02.20 |
