Props: 파라미터
값을 부모 컴포넌트가 자식 컴포넌트에게 넘겨줄때 props로 전달
- 자식 컴포넌트에게 넘겨줄 props값 일반적인 예시
- 부모 컴포넌트의 State
- 부모 컴포넌트이 State를 변경할 수 있는 SetState
[State Lifting]
State Lifting(상태 끌어올리기): 자식 컴포넌트가 부모 컴포넌트의 State를 SetState로 변경
state Liftring을 위해 매번 State, SetState 2개씩 보내주어야할까?
SetState 사용법 2가지
- SetState(새로운 상태값)
- SetState(새로운 상태값 생성 함수)
function ButtonComponent(
/* props → object destructure */ { className, onClick, children },
) {
// const className = props.className
// const onClick = props.onClick
// const children = props.children
return (
<button className={className} onClick={onClick}>
{children}
</button>
)
}
function App() {
const [count, setCount] = useState(0)
return (
<>
<div>{count}</div>
<div>
<ButtonComponent
className='increase-button'
onClick={() => setCount((previousCount) => previousCount + 1)}
>
증가
</ButtonComponent>
<ButtonComponent
// 중괄호 JS 로직으로 string 을 넘길수도 있다.
className={'decrease-button'}
onClick={() => setCount((previousCount) => previousCount - 1)}
>
감소
</ButtonComponent>
</div>
</>
)
}
App 부모 컴포넌트의 state를 변경할 수 있는 setState함수를 ButtonComponent라는 자식 컴포넌트에게 넘겨주는 코드이다.
[state Lifting에서 자주하는 실수]
부모-자식 관계에서 Props 파라미터로 넘겨받은 부모 state 값을 자식 state 초기값으로 사용
이렇게 사용할 경우 부모가 props 파라미터로 넘긴값을 변경했을때 자식은 어떠한 변경도 인지하지 못함.
이 문제의 원인은 동일한 1개의 값에 대해 2개의 부모 상태와 자식 상태를 갖기 때문이다.
- single source of Truth원칙
- 모든 상태들은 자신을 "소유"하는 특정 컴포넌트가 있어야한다.
- 아래 코드 실습을 통해 single source of turth원칙과 그것을 의도적으로 쪼갠것을 설명한다.
Single Source of Truth
1. single source of truth 원칙 예
import '@/App.css'
import { useState } from 'react'
function ListItem({ name, age, desc, setDesc }) {
const [activated, setActivate] = useState(false) //초기값 false
return (
<li
style={{ textAlign: 'left' }}
onClick={(e) => setActivate((previous) => !previous)} {/* 1. true */} {/* 3. true */}
>
{name} | {age} |{' '}
{activated ? (
<>
<input
value={desc}
onClick={(e) => e.stopPropagation()}
onChange={(e) => setDesc(e.currentTarget.value)}
/>
<button onClick={(e) => setActivate((previous) => !previous)}> {/* 2. flase */}
확인
</button>
<button onClick={(e) => setActivate((previous) => !previous)}>
취소
</button>
</>
) : (
<span>{desc}</span>
)}
</li>
)
}
function UnorderedList({ items, setItems }) {
return (
<ul>
{items.map((item, index) => (
<ListItem
key={index}
name={item.name}
age={item.age}
desc={item.desc}
setDesc={(input) => {
const updated = [...items]
updated[index].desc = input
setItems(updated)
}}
/>
))}
</ul>
)
}
function App() {
const [items, setItems] = useState([
{ name: 'Aaron', age: 10, desc: '안녕하세요' },
{ name: 'Baron', age: 30, desc: '반갑습니다' },
{ name: 'Caron', age: 22, desc: '처음뵙겠습니다' },
{ name: 'Daron', age: 17, desc: '보고싶었습니다' },
])
return (
<>
<div>
{/* ul = Unordered List */}
<UnorderedList items={items} setItems={setItems} />
{/* ol = Ordered List */}
<ol>
{/* li = List Item */}
<li>Ordered List Item 1</li>
<li>Ordered List Item 2</li>
<li>Ordered List Item 3</li>
</ol>
</div>
</>
)
}
export default App
문제: 이벤트 버블링에 의해 확인, 취소 버튼을 눌러도 input태그가 사라지지 않음
- 부모<li> 태그 클릭 시 activated 상태 true로 변경
- activated ture이므로 input태그 보이고 자식 (input 태그) 에서 다시 확인, 취소 버튼을 누르면 activated 상태 false로 변경
- 자식 버튼이 눌려진 다음 바로 동시에 부모<li>태그가 눌려져 다시 activated 상태 ture로 변경
원인: 취소 버튼이 눌려진 다음 바로 동시에 부모<li> 가 눌려져 다시 열림
//1
setActive(!activated)
//2
setActive((pervious) => !previous)
이벤트 버블링 이슈는 첫번째 코드인 경우 발생하지 않지만, 두번째 코드인 경우 발생함
setState는 비동기라면서 왜 동기처럼 동작하나?
- setState는 모두 큐에 쌓여 나중에 수행되기에 비동기로 동작
- 단, 큐에 쌓인 setState들이 수행될땐 동기로 동작된다.(업데이트 큐에 대한 자세한 설명)
- CPU는 한번에 하나만 수행 가능하므로...
[이벤트 버블링 해결 방법]
`StopPropogation`으로 자식에서 발생한 이벤트가 부모 이벤트로 전이되는 것을 방지
<button
onClick={(e) => {
e.stopPropagation()
setActivate((previous) => !previous)
}}
>
확인
</button>
2. single source of truth의 의도적 분리
single source of truth원칙의 기준: 상태 변경의 범주
부모 컴포넌트의 상태와 자식 컴포넌트의 상태를 잘라, 나눠내야할 때
이 실습은 의도적으로 부모의 상태와 자식의 상태 변화를 분리하는 경우이다.
일부러 Props로 넘겨준 부모의 state를 그대로 쓰지 않고, 자식 state초기 값으로 사용
문제: 취소 버튼을 눌렀지만 변경된 내용이 그대로 반영되는 경우..
원인: 취소 버튼을 눌러도 수정한 내역이 남아 있다.(제대로 콜백되지 않음)
[문제 해결 방법]
취소 버튼 눌렀을 시 setInput(desc) 호출
function ListItem({ name, age, desc, setDesc }) {
const [activated, setActivate] = useState(false)
const [input, setInput] = useState(desc) //입력한 값을 저장하기 위한 중간 저장소를 상태로 만듦
return (
<li
style={{ textAlign: 'left' }}
onClick={(e) => setActivate((previous) => !previous)}
>
{name} | {age} |{' '}
{activated ? (
<>
<input
value={input}
onClick={(e) => e.stopPropagation()}
onChange={(e) => setInput(e.currentTarget.value)} {/input에 입력할때마다 input 상태 변경*/}
/>
<button
onClick={(e) => {
e.stopPropagation()
setActivate((previous) => !previous)
setDesc(input) {/* 확인 버튼을 누르면 input 상태 값을 가지고 desc 상태 값을 변경 */}
}}
>
확인
</button>
<button
onClick={(e) => {
e.stopPropagation()
setActivate((previous) => !previous)
setInput(desc) {/* 부모로부터 받은 decs(수정되기 전)로 input 상태 변경 */}
}}
>
취소
</button>
</>
) : (
<span>{desc}</span>
)}
</li>
)
}
'ASAC 웹 풀스택' 카테고리의 다른 글
React 의 특장점, 렌더 라이프사이클 및 Hook(6) - Ref (0) | 2024.09.22 |
---|---|
React 의 특장점, 렌더 라이프사이클 및 Hook(4) - State (1) | 2024.09.22 |
React: setState에 대해 더 자세히 알아보자 (0) | 2024.09.21 |
React 의 특장점, 렌더 라이프사이클 및 Hook(2) - 단방향 바인딩 + Hook (1) | 2024.09.20 |
React 의 특장점, 렌더 라이프사이클 및 Hook(1) - JSX문법 (0) | 2024.09.20 |