하나의 데이터가 어떤 작업을 하는지는 코드를 살펴봐야지만 알 수 있다. (데이터 추가, 제거, 수정..등) 그래서 하나로 모아서 관리가 되도록 useReducer
를 쓴다. useReducer는 상태관리 및 데이터 추가,제거,수정 등의 각종 Handler를 하나의 함수로 사용할 수 있다. 미리 어떤 동작을 하나의 데이터를 통해서 가능하게 할 건지 정해놓을 수 있다.
상태관리 라이브러리에도 동일하게 적용되는 원리이다.
const [state, dispatch] = useReducer(리듀서 이름, 초기 데이터)
dispatch
는 데이터 추가,제거,수정 등을 위한 함수이다. 데이터 제거 사용법은 dispatch({type:’REMOVE’})
이렇게 하고 추가는 dispatch({type:’ADD’})
이렇게 한다.
데이터와 관련한 작업을 명시해 놓는 곳이 Reducer
인거다. 그래서 dispatch
에 동작을 명시하면 함수안에 명시한 return값을 수행한다.
// Reducer 파일
// dispatch({ type: 'ADD, data: 새로운 데이터 }) -> action = 1
export function userReducer(state, action) {
switch (action.type) {
case 'ADD':
return [...state, action.data]
case 'REMOVE':
return ~~~
default:
throw new Error(`Unhandled action type: ${action.type}`)
}
}
// Reducer 활용 예시
import React, { useReducer, useState } from 'react'
import { userData } from '../constants/userData'
import { userReducer } from '../reducers/userReducer'
function UserList() {
const [userInput, setUserInput] = useState({id: '', name: '', email: ''})
const userInputHandler = (e) => {
const {name, value} = e.target
setUserInput({...userInput, [name]: value})
}
const [state, dispatch] = useReducer(userReducer, userData)
return (
<div>
{state.map((user) => {
return <p key={user.id}>{user.name}</p>
})}
<input name="name" onChange={userInputHandler} />
<input name="emali" onChange={userInputHandler} />
<button onClick={() => dispatch({type:'ADD', data: userInput})}>추가하기</button>
</div>
)
}
export default UserList
특정한 값을 모든 컴포넌트에서 사용할 수 있도록 만들어놓고, 어떤 컴포넌트에서든 값을 건내주는 Hook이다. props drilling
을 방지할 수 있다. props drilling
// 상위 컴포넌트
import { useReducer, createContext } from 'react';
import UserList from './components/UserList';
import { userData } from './constants/userData';
import { userReducer } from './reducers/userReducer';
// 다양한 컴포넌트에서 사용해야해서 밖에다 만든다.
export const UserContext = createContext()
function App() {
const [state, dispatch] = useReducer(userReducer, userData)
return (
<UserContext.Provider value={{ state, dispatch }}>
<UserList />
</UserContext.Provider>
);
}
// 하위 컴포넌트
import { UserContext } from '../App'
function UserInfo() {
const { count } = useContext(UserContext)
return (
<p>{count}</p>
)
}
export default UserInfo
그렇다고 해서 항상 useContext 를 써야하는건 아니다.
정말로 전 컴포넌트에서 공유되어야 하는 값이 있을 때만 사용하는 것이고, 각 컴포넌트로 별로만 관리하는 값이라면 그냥 useState
를 쓰는 것이 더 적절하다
// useContext 단순화
// app.js
import UserList from './components/UserList';
import { UserProvider } from './contexts/UserContext';
function App() {
return (
<UserProvider>
<UserList />
</UserProvider>
);
}
export default App;
// UserContext.jsx
import { useReducer, createContext } from 'react';
import { userData } from '../constants/userData';
import { userReducer } from '../reducers/userReducer';
// 다양한 컴포넌트에서 사용해야해서 밖에다 만든다.
export const UserContext = createContext()
export function UserProvider({ children }) {
const [state, dispatch] = useReducer(userReducer, userData)
return (
<UserContext.Provider value={{ state, dispatch }}>
{ children }
</UserContext.Provider>
);
}
useState는 상태가 변화면 다시 렌더링을 한다. 그래서 컴포넌트를 분리하면 분리할 수록 좋은 점은 해당 컴포넌트만 렌더링 되기 때문에 훨씬 더 효율적이다. 현재 컴포넌트 구조는 UserList
만 하위컴포넌트로 있는데 제공된 값이 변하면 하위구조의 컴포넌트가 전부다 렌더링이 새로 되기 때문에 useContext
를 남발하면 안되는 이유이다.
이런 문제들을 해결하기 위해 다양한 상태관리 라이브러리들이 등장했다.
useEffect
로 axios 요청을 하면 index.js
에 StrictMode
가 useEffect가 잘 작동하는지 확인하기 위해 두번 실행을 시킨다. 디버깅 과정을 위해 있는 건데 프로덕션 빌드에서는 필요하지 않아서 지워도 괜찮다. 그럼 요청이 한번만 간다.
CORS Error
는 동일한 오리진에서 보낸 요청이 아니면 거부를 한다. 카카오 서비스를 만들었을때 카카오채팅 기록은 카카오톡에서만 가져올 수 있다. localhost, dev 서버에서 api요청하는거랑 실제 주소로 요청하는 서로의 주소값이 다르다는 에러이다.
package.json에 "proxy": "https://apis.data.go.kr/"
를 추가해주면 이주소를 거쳐서 요청을 보내게 된다. 그러면 axios를 쓸때 요청 주소에 중복되는 주소를 지워도 된다. 내가 있는곳이 proxy 주소라는 의미. 그럼 CORS 에러는 해결했다.
로딩, 데이터, 에러 관리하는 라이브러리