리액트 state 구조
✨리액트 state
컴포넌트의 데이터(상태)
함수형 컴포넌트에서는 useState를 사용하여 상태를 관리한다.
📘useState()
1
2
3
import { useState } from "react";
const [state, setState] = useState();
- state: 렌더링 간에 데이터를 유지하기 위한 state 변수
- setState: 변수를 업데이트하고, react가 컴포넌트를 재렌더링하도록 만드는 state setter 함수
📘state 특징
1. 여러 개의 state를 지정할 수 있음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { useState } from "react";
import data from "./data.js";
function App() {
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
function handleNextClick() {
setIndex(index + 1);
}
function handleMoreClick() {
setShowMore(!showMore);
}
return (
<div>
<button onClick={handleNextClick}>Next</button>
<h2>data[index]</h2>
</div>
);
}
2. state는 격리된다.
동일한 컴포넌트를 한번이상 렌더링한다면 각 복사본은 완전히 독립적인 state를 가진다. 한 개의 컴포넌트의 state를 수정해도, 다른 컴포넌트에는 영향을 미치지 않는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import Gallery from "./Gallery.js";
function Page() {
return (
<div className="Page">
<Gallery />
<Gallery />
</div>
);
}
function Gallery() {
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
function handleNextClick() {
setIndex(index + 1);
}
function handleMoreClick() {
setShowMore(!showMore);
}
return (
<div>
<button onClick={handleNextClick}>Next</button>
<h2>data[index]</h2>
</div>
);
}
- 첫번째 Gallery 컴포넌트의 버튼을 눌러도, 두번째 Gallery 컴포넌트는 다음 state를 보여주지 않음
📘state 구조화 원칙
1. 연관된 state 그룹화하기
두 개 이상의 state 변수가 항상 동시에 업데이트 된다면, 하나의 state로 관리하는 것이 좋음
마우스 커서를 움직이면 좌표가 업데이트되는 상태를 관리할 때에는 x, y각각의 state로 관리하는 것보다는 하나의 state에서 관리하는 것이 좋다
1
2
3
// 각각 state로 관리
const [x, setX] = useState(0);
const [y, setY] = useState(0);
1
2
// 하나의 state로 관리
const [position, setPosition] = useState({ x: 0, y: 0 });
2. State의 모순 피하기
단일 소스 진실(Single Source of Truth)
여러개의 state들이 모순되고, 불일치되는 방식으로 state를 구성하면 안된다. 하나의 상태를 여러 하위 컴포넌트에 공유함으로써 모든 상태가 일관되게 유지해야한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import { useState } from "react";
function FeedbackForm() {
const [userData, setUserData] = useState({
username: "",
email: "",
age: 0
});
const handleUsernameChange = (event) => {
setUserData({ ...userData, username: event.target.value });
};
const handleEmailChange = (event) => {
setUserData({ ...userData, email: event.target.value });
};
const handleAgeChange = (event) => {
setUserData({ ...userData, age: event.target.value });
};
return (
<div>
<h1>User Information</h1>
<form>
<Input
inputType="text"
labelText="Username"
value={userData.username}
onChange={handleUsernameChange}
/>
<br />
<Input
inputType="email"
labelText="Email"
value={userData.email}
onChange={handleEmailChange}
/>
<br />
<Input
inputType="number"
labelText="Age"
value={userData.age}
onChange={handleAgeChange}
/>
</form>
<div>
<h2>Preview:</h2>
<p>Username: {userData.username}</p>
<p>Email: {userData.email}</p>
<p>Age: {userData.age}</p>
</div>
</div>
);
}
function Input({ inputType, labelText, value, onChange }) {
return (
<label>
{labelText}:
<input type={inputType} value={value} onChange={onChange} />
</label>
);
}
3. 불필요한 state 피하기
- 필요한 상태만 관리하기: 상태관리는 최소한으로 유지해야 함
- 계산 가능한 데이터는 상태로 관리하지 않기: 컴포넌트 렌더링시에 필요한 데이터를 계산할 수 있다면 상태로 관리하는것보다는, 필요할 때마다 계산하는 것이 더 나음
- 부모 컴포넌트 상태 끌어올리기: 여러 컴포넌트가 동일한 데이터에 접근하는 경우에는 부모 컴포넌트에서 상태관리하는 것이 나음
- 컴포넌트 간의 상태 동기화 최소화하기
아래의 코드는 이름(firstName), 성(lastName), 이름 + 성(fullName) 3가지의 상태를 관리한다. 이 중 fullName은 firstName과 lastName으로 연산할 수 있기 때문에 불필요한 state이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { useState } from "react";
function Name() {
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [fullName, setFullName] = useState("");
function handleFirstNameChange(e) {
setFirstName(e.target.value);
setFullName(e.target.value + " " + lastName);
}
function handleLastNameChange(e) {
setLastName(e.target.value);
setFullName(firstName + " " + e.target.value);
}
return (
<div>
<label>
First name: <input value={firstName} onChange={handleFirstNameChange} />
</label>
<label>
Last name: <input value={lastName} onChange={handleLastNameChange} />
</label>
<h4>FullName is {fullName}</h4>
</div>
);
}
firstName과 lastName을 사용해서 fullName을 구해 사용하는 방법으로 수정할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { useState } from "react";
function Name() {
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
function handleFirstNameChange(e) {
setFirstName(e.target.value);
}
function handleLastNameChange(e) {
setLastName(e.target.value);
}
const handleFullName = () => {
return `${firstName} ${lastName}`;
};
return (
<div>
<label>
First name: <input value={firstName} onChange={handleFirstNameChange} />
</label>
<label>
Last name: <input value={lastName} onChange={handleLastNameChange} />
</label>
<h4>FullName is {handleFullName()}</h4>
</div>
);
}
4. State의 중복 피하기
상태의 중복이란 동일한 데이터를 여러 컴포넌트에서 유지하는 것을 의미하고,상태가 중복일 경우 데이터의 불일치나 불필요한 복잡성이 발생할 수도 있다.
- 단일 소스로 상태 관리: 상태 관리를 한 곳에서 하고 필요한 컴포넌트에서 사용 (redux,jotai 등 상태관리 라이브러리 사용)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { useState } from "react";
const initialItems = [
{ title: "pretzels", id: 0 },
{ title: "crispy seaweed", id: 1 },
{ title: "granola bar", id: 2 }
];
function Menu() {
const [items, setItems] = useState(initialItems);
const [selectedItem, setSelectedItem] = useState(items[0]);
const handleItemChange = (id, e) => {
setItems(
items.map((item) =>
item.id === id ? { ...item, title: e.target.value } : item
)
);
};
return (
<>
<h2>What's your travel snack?</h2>
<ul>
{items.map((item, index) => (
<li key={item.id}>
<input
value={item.title}
onChange={(e) => {
handleItemChange(item.id, e);
}}
/>{" "}
<button
onClick={() => {
setSelectedItem(item);
}}
>
Choose
</button>
</li>
))}
</ul>
<p>You picked {selectedItem.title}.</p>
</>
);
}
위의 코드는
items = [{ id: 0, title: 'pretzels'}, ...]
selectedItem = {id: 0, title: 'pretzels'}
로 state가 중복되어있습니다.
selectedItem
에 해당 item의 모든 내용({id: 0, title: 'pretzels'}
)을 담는 것이 아닌, 해당 item의 id만 담아 필요한 state를 계산해서 사용할 수 있습니다.
- items = [{ id: 0, title: ‘pretzels’}, …]
setSelectedId = 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import { useState } from "react";
const initialItems = [
{ title: "pretzels", id: 0 },
{ title: "crispy seaweed", id: 1 },
{ title: "granola bar", id: 2 }
];
function Menu() {
const [items, setItems] = useState(initialItems);
const [selectedId, setSelectedId] = useState(0);
// 선택한 아이템 계산
const selectedItem = items.find((item) => item.id === selectedId);
const handleItemChange = (id, e) => {
setItems(
items.map((item) =>
item.id === id ? { ...item, title: e.target.value } : item
)
);
};
return (
<>
<h2>What's your travel snack?</h2>
<ul>
{items.map((item, index) => (
<li key={item.id}>
<input
value={item.title}
onChange={(e) => {
handleItemChange(item.id, e);
}}
/>{" "}
<button
onClick={() => {
setSelectedItem(item.id);
}}
>
Choose
</button>
</li>
))}
</ul>
<p>You picked {items.title}.</p>
</>
);
}
5. 깊게 중첩된 state 피하기
상태의 구조가 복잡하고 중첩되어 있어서 상태를 관리하고 업데이트하기 어렵기때문에 코드의 유지보수성이 떨어지고, 버그가 발생하기 쉽다.
- 단순한 상태 구조 유지: 깊게 중첩된 상태보다는 평면화된 상태 구조를 사용
- 상태를 분할하고 컴포넌트로 전달: 깊게 중첩된 상태 대신 상위 컴포넌트에서 상태를 관리하고 하위 컴포넌트로 필요한 데이터를 전달
- 불변성 유지: 깊에 중첩된 상태에서 하위 객체를 직접적으로 변경하는 것이 아니라 새로운 객체를 생성하여 상태를 업데이트 해야함
- 컴포넌트 분리: 관리하기 어렵다면, 컴포넌트를 분리하여 상태를 관리 (더 작은 단위의 상태)
참고사이트