redux
: 원리가 간단해서 에러가 덜 남 , 코드량이 많아짐
mobx
: 코드량이 적어짐, 에러 추적이 힘듬
contextAPI
: 주로 컴포넌트에 코드가 직접 들어가서 보기 좋지 않음 ,쓰다보면 어차피 redux
처럼 쓰게 됨
State : 데이터 저장소
Action : State
를 변경하기 위한 명령
dispatch : Action
을 실행 해주는 것
Reducer : Action
을 사용해서 State
를 어떻게 바꿀지 정함 switch
문을 사용
Store : reducer
와 state
를 합침? ( 나중에 다시 알아보기 )
reducer
에서 state.name
같이 직접적으로 변경을 시도하면 참조 관계가 유지되어 History가 남지 않는다.
npm i redux next-redux-wrapper
redux
와 next
에 redux
를 붙이기 위한 next-redux-wrapper
를 설치한다.
// ./store/configureStore.js
import { createStore } from 'redux';
import createWrapper from 'next-redux-wrapper'
import reducer from '../reducer';
const configureStore = () => {
const store = createStore(reduce)
return store;
}
const Wrapper = createWrapper(configureStore, {
debug: process.env.NODE_ENV == 'development'
})
export default Wrapper;
configureStore.js
세팅을 해준다
// ./pages/_app.js
export default Wrapper.withRedux(App);
공용파일 _app.js
의 컴포넌트를 HOC
로 감싸준다
React에서는
react-redux
에 포함된Provider
로 이렇게 감싸준다
하지만Next에서는 알아서 감싸주기 때문에 따로 써주지 않는다.
npm i redux-devtools-extension
// ./store/configureStore.js
(...)
import { createStore, applyMiddleware, compose } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension'
const configureStore = () => {
const middlewares = []
const enhancer = process.env.NODE_ENV === 'production'
? compose(applyMiddleware(...middlewares)) // 배포용
: composeWithDevTools(applyMiddleware(...middlewares)) // 개발용
const store = createStore(reducer, enhancer)
return store;
}
(...)
// ./reducer/index.js
const initialState = {
user: {
isLoggedIn: false,
user: null,
signUpData: {},
loginData: {},
},
post: {
mainPosts: [],
}
} // State의 초기값
const LOG_IN = 'LOG_IN'; // ACTION명 이름 재정의
export const LogInAction = data => {
return {
type: LOG_IN,
data
}
} // 넘겨줄 Action
// 컴포넌트에서 dispatch(LoginAction(data로 넘겨줄값) 으로 사용)
const RootReducer = (state = initialState, action) => {
switch(action.type) {
case LOG_IN :
return {
// LOG_IN ACTION이 감지되면 아래의 행동을 취한다.
...state,
user: {
...state.user,
// 객체의 불변성을 유지하며 안의 값만 바꾸기 위함
isLoggedIn: true,
user: action.data
}
}
default :
// default 는 꼭 필요, 없으면 에러남
return {
...state
}
}
}
LoginForm
에서 LoginAction
을 쓰려면?// ./component/LoginForm.js
(...)
import { useDispatch } from 'react-redux';
import { LogInAction } from '../reducer'
(...)
const LoginForm = () => {
const dispatch = useDispatch()
const onSubmitLogin = useCallback(() => {
dispatch(LogInAction(id, password))
}, [id, password])
return (
<FormWrap onFinish={onSubmitLogin}>
(...)
</FormWrap>
)
(...)
}
AppLayout
에서 isLoggedIn State
가 필요하다면?// ./component/AppLayout.js
(...)
import { useSelector } from 'react-redux';
const AppLayout = ({ children }) => {
const isLoggedIn = useSelector(state => state.user.isLoggedIn)
(...)
{isLoggedIn ? <UserProfile/> : <LoginForm/>}
(...)
}
// ./reducer/user.js
// index에서 user에 해당하는 부분을 모두 가져옴
export const initialState = {
isLoggedIn: false,
user: null,
signUpData: {},
loginData: {},
}
const LOG_IN = 'LOG_IN';
const LOG_OUT = 'LOG_OUT';
export const LogInAction = data => {
return {
type: LOG_IN,
data
}
}
export const LogOutAction = () => {
return {
type: LOG_OUT
}
}
const UserReducer = (state = initialState, action) =>{
switch(action.type) {
case LOG_IN :
return {
...state,
isLoggedIn: true,
user: action.data
} // state의 구조가 바뀌었으니 이곳도 바뀜
case LOG_OUT :
return {
...state,
isLoggedIn: false,
user: null
}
default:
return state;
}
}
export default UserReducer;
// 이하 post폴더도 이와 같은 방식으로 세팅
// ./reducer/index.js
import { combineReducers } from 'redux'
import UserReducer from './user';
import PostReducer from './post';
const RootReducer = combineReducers({
//combineReducers로 묶어두고 객체 안에 묶을 컴포넌트들을 넣으면 된다.
UserReducer,
PostReducer
})
export default RootReducer;
reducer 구조가 바뀌었으니 Action을 사용하는 컴포넌트의 링크도 바꿔주자
import { LogInAction } from '../reducer/user'