- useReducer
- 사용자정의 hook
- 게시판 예제 React 변환
1) useReducer
reducer는 여러 state를 관리해야 할 때 주로 사용한다.
사용 문법은 아래와 같다.
const [state, dispatch] = useReducer(reducer, initialState, init);
action은 reducer가 state를 참고해서 새로운 state를 만들기 위해 필요하고, reducer는 dispatch 함수에 의해 실행되고 컴포넌트 외부에서 state를 업데이트해 준다.
아래는 axios를 이용한 api 로딩, 성공, 실패 예제이다.
const initialState = {
data: [],
loading: false,
error: null
}
초기값과 타입을 설정해 준다.
const reducer = (state, action) => {
switch (action.type) {
case "LOADING":
return {
data: [],
loading: true,
error: null
}
case "SUCCESS":
return {
data: action.data,
loading: false,
error: null
}
case "ERROR":
return {
data: [],
loading: false,
error: action.error
}
default:
return state;
}
}
const Test2 = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const { data, loading, error } = state
const getData = async () => {
// 로딩
dispatch({ type: 'LOADING' })
try {
// 성공
const res = await axios.get('https://jsonplaceholder.typicode.com/posts')
dispatch({ type: 'SUCCESS', data:res.data })
}
catch (error) {
// 실패
dispatch({ type: 'ERROR', error : error})
}
}
useEffect(() => {
getData()
}, [])
reducer에서 action.type이 로딩, 성공, 실패인지 확인을 한 후 state를 return해 준다.
성공 시에 data에 가져온 data를 넣어준다.
return (
<div>
{
data.map(item => <p key={item.id}>{item.id} / {item.title}</p>)
}
</div>
);
그 뒤엔 똑같이 가져온 data에 map을 사용해 출력을 해준다.
2) 사용자정의 hook
같은 원리를 이용해 내가 원하는 hook을 함수 형태로 만들 수 있다.
import { useCounter } from '../hooks/useCounter'
const Test3 = () => {
const { state, onIncre, onDecre, onReset } = useCounter(10)
return (
<div>
<h1>count : {state}</h1>
<p>
<button onClick={onIncre}>incre</button>
<button onClick={onDecre}>decre</button>
<button onClick={onReset}>reset</button>
</p>
</div>
);
};
export const useCounter = (initialState = 100) => {
const [state, setState] = useState(initialState)
const onIncre = () => {
setState(state + 1)
}
const onDecre = () => {
setState(state - 1)
}
const onReset = () => {
setState(0)
}
return { state, onIncre, onDecre, onReset }
};
아래 코드는 useCounter함수를 정의한 후 export를 했다.
default로 export 한 게 아니기 때문에 import 할 땐 { } 중괄호로 묶어 주어야 한다.
useCounter함수의 초기값은 100이고, state의 초기값은 ussCounter의 초기값으로 넣어주었으므로 똑같이 100이 된다.
하지만 Test3 컴포넌트에서 import 한 뒤 초기값에 10을 넣었으므로 처음 출력 시 10이 출력된다.
증가, 감소, 초기화에 대한 함수를 정의하고 return을 해 다른 컴포넌트에서 불러올 수 있게 했다.
3) 게시판 예제 React 변환
과거 vanilla js로 구현했던 게시판 예제를 useAxios를 만들어 사용해 보았다.
import axios from "axios"
import { useEffect, useState } from "react"
export const useAxios = (url = []) => {
const [data, setData] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
axios.get(url)
.then(res => {
setData(res.data)
setLoading(true)
setError(null)
})
.catch(error => {
setData([])
setLoading(false)
setError('--- ERROR ---')
})
}, [url])
return { data, loading, error }
}
api url을 받아 성공 시 data로 넣어주는 hook을 만들었다.
const url = 'https://jsonplaceholder.typicode.com/posts';
const { data, loading, error } = useAxios(url)
const [currentPage, setCurrentPage] = useState(1)
const [postsPerPage, serPostsPerPage] = useState(10)
const totalPage = data.length;
const lastPost = currentPage * postsPerPage;
const firstPost = lastPost - postsPerPage;
const pageNumber = Math.ceil(totalPage / postsPerPage)
const currentPosts = data.slice(firstPost, lastPost)
기존의 변수들을 선언문과 함께 새로 할당해 주고,
<tbody className="table-body">
<List currentPosts={currentPosts} loading={loading} />
</tbody>
</table>
<p className="paging">
<Paging pageNumber={pageNumber} setCurrentPage={setCurrentPage} />
</p>
데이터가 출력될 부분들은 컴포넌트로 만들었다.
return (
<>
{
currentPosts.map(item =>
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.title}</td>
<td>{item.body}</td>
</tr>
)
}
</>
);
table의 tbody 부분
return (
<>
{
pageNum.map((item, idx) =>
<a key={idx} href='#' onClick={e => { e.preventDefault(); setCurrentPage(item) }}>{item}</a>
)
}
</>
);
paging 부분