리액트에서 상태 값을 관리할 때 useState를 사용해서 쉽게 관리할 수 있다.
그런데 만약 관리해야 할 상태 값이 많아진다면 하나씩 useState를 사용하는 방식은 번거로울 수 있다.
useState부터 useReducer와 Context API를 사용하는 과정을 과목을 선택하고 정원을 조절하는 프로그램을 만들어보면서 이해해보자.
useState + 객체 사용
먼저 상태 값을 객체로 정의해서 useState를 사용하는 방식이 있다.
import React, {useState} from 'react'
function App() {
return (
<Subject />
)
}
function Subject(){
const [subject, setSubject] = useState({name : "math", students : 10})
return (
<div>
<div>{`과목 : ${subject.name}, 정원 : ${subject.students}`}</div>
<button onClick = {()=>setSubject({...subject, name : "english"})}>영어</button>
<button onClick = {()=>setSubject({...subject, name : "math"})}>수학</button>
<button onClick = {()=>setSubject(prev => ({...subject, students : prev.students+1}))}>정원 증가</button>
<button onClick = {()=>setSubject(prev => ({...subject, students : prev.students-1}))}>정원 감소</button>
</div>
)
}
export default App
useState를 객체로 관리해서 내부 상태 값들을 관리하는 모습이다.
하지만 코드가 복잡한 느낌이 있다.
useReducer 훅을 사용해서 가독성 좋게 만들어보자.
useReducer 사용
1. useReducer를 import 한다.
import React, {useReducer} from 'react'
2. reducer 함수를 정의한다.
function reducer(state, action){
switch(action.type){
case 'setSubject':
return {...state, subject: action.subject}
case 'plusStudents':
return {...state, students: action.students}
case 'minusStudents':
return {...state, students: action.students}
}
}
action에는 객체가 들어오고 관례상 type을 통해 로직을 정의한다.
여기서 전개 연산자를 사용해서 새롭게 상태 값을 정의해서 반환하고 있다.
우리는 action의 type에 직관적인 행위 이름을 넣기만 하면 쉽게 값을 변경할 수 있다는 것을 알 수 있다.
3. 초기 값을 정의한다.
이 부분은 useState와 비슷하다.
다만 다른 점은 reducer 함수와 함께 초기 값을 정의한다는 점이다.
const [state, dispatch] = useReducer(reducer, INIT)
const INIT = {subject: "math", students: 10}
이제 리액트 요소를 살펴보자.
return (
<div>
<div>{`과목 : ${state.subject}, 정원 : ${state.students}`}</div>
<button onClick = {()=>dispatch({type: "setSubject", subject : "english"})}>영어</button>
<button onClick = {()=>dispatch({type: "setSubject", subject : "math"})}>수학</button>
<button onClick = {()=>dispatch({type: "plusStudents", students: state.students+1})}>정원 증가</button>
<button onClick = {()=>dispatch({type: "minusStudents", students: state.students-1})}>정원 증가</button>
</div>
)
useState를 사용할 때는 함수에 직접 전개 연산자를 사용했지만 지금은 type과 변경할 상태 값만 사용해주면 된다.
조금 더 직관적으로 함수의 기능을 알 수 있게 되었다.
useState와 다른 점
- reducer 함수 내부에서 action의 type별로 로직을 분리하고 그 안에서 전개 연산자를 사용한다.
- 초기 값을 정의할 때 reducer 함수도 함께 넣는다.
- dispatch로 상태 값을 변경할 때 type과 함께 변경할 상태 값만 넣어준다.
전체 코드
import React, {useReducer} from 'react'
function App() {
return (
<Subject />
)
}
const INIT = {subject: "math", students: 10}
function Subject(){
const [state, dispatch] = useReducer(reducer, INIT)
return (
<div>
<div>{`과목 : ${state.subject}, 정원 : ${state.students}`}</div>
<button onClick = {()=>dispatch({type: "setSubject", subject : "english"})}>영어</button>
<button onClick = {()=>dispatch({type: "setSubject", subject : "math"})}>수학</button>
<button onClick = {()=>dispatch({type: "plusStudents", students: state.students+1})}>정원 증가</button>
<button onClick = {()=>dispatch({type: "minusStudents", students: state.students-1})}>정원 증가</button>
</div>
)
}
function reducer(state, action){
switch(action.type){
case 'setSubject':
return {...state, subject: action.subject}
case 'plusStudents':
return {...state, students: action.students}
case 'minusStudents':
return {...state, students: action.students}
}
}
export default App
useReducer + Context
우리가 만든 Subject component가 만약 리액트 요소 깊숙히 있다고 하고 여기서 사용하는 상태 값들은 reducer로 root에서 관리한다고 해보자.
그러면 중간 컴포넌트들을 통해서 상태 값을 넘겨줘야 할텐데 이럴 경우 중간 컴포넌트에서는 사용하지도 않는 상태 값들을 받게 되고 의존성이 높아지는 문제가 생기게 된다.
이를 Context를 이용해서 간단하게 구현해보자.
import React, {useReducer} from 'react'
export const SubjectContext = React.createContext()
const INIT = {subject: "math", students: 10}
function App() {
const [state, dispatch] = useReducer(reducer, INIT)
return (
<SubjectContext.Provider value = {{state,dispatch}}>
<Subject />
</SubjectContext.Provider>
)
}
function Subject(){
return (
<SubjectContext.Consumer>
{({state, dispatch})=>(
<div>
<div>{`과목 : ${state.subject}, 정원 : ${state.students}`}</div>
<button onClick = {()=>dispatch({type: "setSubject", subject : "english"})}>영어</button>
<button onClick = {()=>dispatch({type: "setSubject", subject : "math"})}>수학</button>
<button onClick = {()=>dispatch({type: "plusStudents", students: state.students+1})}>정원 증가</button>
<button onClick = {()=>dispatch({type: "minusStudents", students: state.students-1})}>정원 감소</button>
</div>
)
}
</SubjectContext.Consumer>
)
}
function reducer(state, action){
switch(action.type){
case 'setSubject':
return {...state, subject: action.subject}
case 'plusStudents':
return {...state, students: action.students}
case 'minusStudents':
return {...state, students: action.students}
}
}
export default App
- Context를 생성한다.
- Context.Provider의 value 속성을 통해 원하는 값을 넘겨준다.
- 자식 컴포넌트에서는 Context.Consumer를 통해서 값을 넘겨받는다.
그런데 이 방식은 조금 복잡해보인다.
useContext를 사용해서 더욱 간단하게 만들어보자.
import React, {useReducer} from 'react'
export const SubjectContext = React.createContext()
const INIT = {subject: "math", students: 10}
function App() {
const [state, dispatch] = useReducer(reducer, INIT)
return (
<SubjectContext.Provider value = {{state,dispatch}}>
<Subject />
</SubjectContext.Provider>
)
}
function Subject(){
const {state, dispatch} = React.useContext(SubjectContext)
return (
<div>
<div>{`과목 : ${state.subject}, 정원 : ${state.students}`}</div>
<button onClick = {()=>dispatch({type: "setSubject", subject : "english"})}>영어</button>
<button onClick = {()=>dispatch({type: "setSubject", subject : "math"})}>수학</button>
<button onClick = {()=>dispatch({type: "plusStudents", students: state.students+1})}>정원 증가</button>
<button onClick = {()=>dispatch({type: "minusStudents", students: state.students-1})}>정원 감소</button>
</div>
)
}
function reducer(state, action){
switch(action.type){
case 'setSubject':
return {...state, subject: action.subject}
case 'plusStudents':
return {...state, students: action.students}
case 'minusStudents':
return {...state, students: action.students}
}
}
export default App
reducer만 사용할 때와 다른 점
- Context를 정의한다.
- Provider를 사용해서 자식 컴포넌트에게 넘겨 줄 값을 정한다.
- 자식 컴포넌트에서는 useContext를 사용해서 값을 넘겨받아서 사용한다.
'공부 > 프론트' 카테고리의 다른 글
[React] 리액트에서 public에 이미지 불러오기 (2) | 2024.06.21 |
---|---|
[React] redux 사용법 (0) | 2024.04.26 |
[html/css/js] 테이블 태그 안에 답글 기능 구현 (0) | 2024.04.06 |
[html/css] 테이블 꾸미기 (모서리 둥글게) (0) | 2024.04.03 |
[html/css] input checkbox 커스텀 이미지 사용하기 (0) | 2024.04.02 |
댓글