react-redux를 기반으로 한 예제를 다룹니다.
예제는 간단한 로그인 기능 + TodoList 구현입니다.
이번 글에서는 모든 서버 관련 동작을 동기적으로 임시 구현하여 처리합니다.
추후 middleware를 이용해 비동기 동작을 처리할 예정입니다.
설치
1. react 설치
npx create-react-app my-todo
2. react-redux 설치
cd ./my-todo
npm install redux react-redux
로그인 기능 구현
Server API - 동기(sync)로 임시 구현
로그인 동작을 임시로 구현하는 이유는 redux가 오직 동기적인 동작만을 처리할 수 있기 때문입니다.
비동기적인 처리는 redux-thunk나 redux-saga 등의 middleware를 사용해야 합니다.
/src/server/index.js
// 사용자 인증에 사용할 ID/PassWord
const ID = 'abcd';
const PASSWORD = '1234';
// 로그인 요청
function logInRequest(id, password) {
if (id === ID && password === PASSWORD) {
// 로그인 성공 시 nickname 반환
return { nickname: 'devdev' };
}
throw new Error();
}
export { logInRequest, logOutRequest };
Frontend
redux store 생성
ducks 패턴을 사용합니다.
ducks 패턴의 규칙
1. MUST export default a function called reducer()
2. MUST export its action creators as functions
3. MUST have action types in the form npm-module-or-app/reducer/ACTION_TYPE
4. MAY export its action types as UPPER_SNAKE_CASE, if an external reducer needs to listen for them, or if it is a published reusable library
/src/redux/module/user.js
유저의 로그인 처리를 위한 모듈입니다.
logInRequest / logOut 결과를 store에 반영합니다.
외부에서는 action creator의 실행 결과를 dispatch하는 식으로 사용하게 됩니다. (LogInComponent.jsx 참고)
import { logInRequest } from '../../server';
// Actions
const LOG_IN_SUCCESS = 'my-todo/user/LOG_IN_SUCCESS';
const LOG_IN_FAILED = 'my-todo/user/LOG_IN_FAILED';
const LOG_OUT = 'my-todo/user/LOG_OUT';
// Reducer
const initialState = { data: null, isFailed: false };
export default function reducer(state = initialState, action) {
switch (action.type) {
case LOG_IN_SUCCESS: {
return { ...state, data: action.payload, isFailed: false };
}
case LOG_IN_FAILED: {
return { ...state, isFailed: true };
}
case LOG_OUT: {
return { ...state, data: null, isFailed: false };
}
default:
return state;
}
}
// Action Creators
export function logIn(id, password) {
try {
const response = logInRequest(id, password);
return { type: LOG_IN_SUCCESS, payload: response };
} catch {
return { type: LOG_IN_FAILED };
}
}
export function logOut() {
return { type: LOG_OUT };
}
src/redux/module/index.js
추후 다른 모듈의 확장을 위해 combineReducers를 사용하여 rootReducer를 생성합니다.
import { combineReducers } from 'redux';
import user from './user';
const rootReducer = combineReducers({ user });
export default rootReducer;
src/redux/index.js
앞서 생성한 rootReducer로 store를 생성합니다. 이 부분에 추후 middleware 관련 코드가 추가되게 됩니다.
import { createStore } from 'redux';
import rootReducer from './module';
const store = createStore(rootReducer);
export default store;
페이지 구성
/src/App.jsx
전역적으로 store를 사용하기 위해 최상단에 Provider를 추가하였습니다.
import { Provider } from 'react-redux';
import store from './redux/index';
import LogInComponent from './components/LoginComponent';
function App() {
return (
<Provider store={store}>
<h1>My TodoList</h1>
<LogInComponent />
</Provider>
);
}
export default App;
/src/components/LogInComponents.jsx
useSelector hook을 사용해 store의 데이터를 가져와 사용합니다.
로그인/로그아웃 버튼 클릭시 onLogIn/onLogOut 함수가 실행되며, 내부에서 dispatch를 실행해 결과를 store에 반영합니다.
JSX에서는 store의 userData 여부로 로그인 여부를 판단, isFailed로 로그인 시도가 실패하였는지를 판단하도록 하였습니다.
import { useCallback, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { logIn, logOut } from '../redux/module/user';
export default function LogInComponent() {
const idRef = useRef('');
const pwRef = useRef('');
const dispatch = useDispatch();
const userData = useSelector((state) => state.user.data);
const isFailed = useSelector((state) => state.user.isFailed);
// event handlers
const onLogIn = useCallback((id, pw) => {
dispatch(logIn(id, pw));
idRef.current = '';
pwRef.current = '';
}, []);
const onLogOut = useCallback(() => {
dispatch(logOut());
}, []);
return (
<>
{userData ? (
<>
<div>
Hello <b>{userData.nickname}</b>
</div>
<button onClick={onLogOut}>Log Out</button>
</>
) : (
<>
<div>
<span>ID: </span>
<input onChange={(e) => (idRef.current = e.target.value)} />
</div>
<div>
<span>PW: </span>
<input onChange={(e) => (pwRef.current = e.target.value)} />
</div>
<button onClick={() => onLogIn(idRef.current, pwRef.current)}>
Log In
</button>
{isFailed ? (
<div style={{ color: 'red' }}>Invalid ID/Password. Try again.</div>
) : (
<></>
)}
</>
)}
</>
);
}
결과
로그인/로그아웃 성공 (/src/server/index.js에서 설정한 ID/Password와 일치하는 경우)
로그인 실패 (/src/server/index.js에서 설정한 ID/Password와 다른 경우)
소스 코드
GitHub repo: https://github.com/dev-wann/redux-practice, my-todo 폴더 내부 내용
이번 글에 해당하는 커밋: https://github.com/dev-wann/redux-practice/commit/f380f01fb40207314e3c141134a4b44db1addf5d
'Web development > 상태관리' 카테고리의 다른 글
Redux - 로그인 + TodoList 예제 구현 (4) - RTK (0) | 2023.11.16 |
---|---|
Redux - 로그인 + TodoList 예제 구현 (3) redux-thunk (0) | 2023.11.15 |
Redux - 로그인 + TodoList 예제 구현 (2) redux-thunk (1) | 2023.11.14 |
Redux - 기본 개념 (0) | 2023.11.02 |