redux, redux-saga 복습

Tony·2021년 7월 27일
0

react

목록 보기
10/82

리덕스 흐름 분석

기본 : Action creator > action > dispatch > reducer > state

  1. component 내 이벤트 호출(클릭, 입력 등)
  2. 이벤트와 연결된 action creator 호출
  3. action creator가 생성한 action호출
  4. action이 reducer로 전달 됨(dispatch에 의해)
  5. dispatch된 action의 영향으로 reducer의 state값이 변화
  6. 렌더링

노드버드 로그인

  1. redux 세팅
// store/configureStore.js
import { createWrapper } from 'next-redux-wrapper';
import { applyMiddleware, compose, createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import createSagaMiddleware from 'redux-saga';
import reducer from '../reducers';
import rootSaga from '../sagas';

const configureStore = context => { // store 설정
  console.log(context); // store가 생성될 때 최초 1회 실행 : {}, 빈 오브젝트 임
  const sagaMiddleware = createSagaMiddleware(); // saga 생성
  const middlewares = [sagaMiddleware]; // 미들웨어에 saga 포함
  const enhancer = // 미들웨어를 적용
    process.env.NODE_ENV === 'production'
      ? compose(applyMiddleware(...middlewares))
      : composeWithDevTools(applyMiddleware(...middlewares));
  const store = createStore(reducer, enhancer); // 미들웨어와 리듀서로 store 생성
  store.sagaTask = sagaMiddleware.run(rootSaga); // 미들웨어를 실행하고 store.sagaTask에 주소값 저장
  return store;
};

const wrapper = createWrapper(configureStore, { // next의 HOC wrapper에 store 장착 및 생성
  debug: process.env.NODE_ENV === 'development',
});

export default wrapper;

// 시작 컴포넌트 : pages/_app.js
import wrapper from '../store/configureStore';
const App = ({ Component }) => (
  <>
    <Head>
      <meta charSet="utf-8" />
      <title>NodeBird</title>
    </Head>
    <Component />
  </>
);

App.propTypes = {
  Component: PropTypes.elementType.isRequired,
};

export default wrapper.withRedux(App); // 리덕스세팅 HOC로 시작 컴포넌트 App을 감싸서 export : 자동으로 최상위 파일인 _document.js 에서 리덕스세팅 HOC로 감싼 App을 사용
  1. component 내 이벤트 호출
  2. 이벤트와 연결된 action creator 호출
  3. action creator가 생성한 action호출
  4. action이 reducer로 전달 됨(dispatch에 의해)
  • 로그인 버튼 클릭 - submit -> onSubmitForm 함수 실행
    -> dispatch안의 loginRequestAction에 의해 action이 만들어짐
// reducer/user.js
export const loginRequestAction = data => ({ // 3. action creator가 생성한 action호출
  type: LOG_IN_REQUEST,
  data,
});
// components/LoginForm.js
const LoginForm = () => {
  const dispatch = useDispatch(); 
  const { logInLoading } = useSelector(state => state.user);
  const [email, onChangeEmail] = useInput('');
  const [password, onChangePassword] = useInput('');

  const onSubmitForm = useCallback(() => { // 1-2. component 내 이벤트 호출
    console.log(email, password);
    dispatch(loginRequestAction({ email, password })); // 2. 이벤트와 연결된 action creator 호출
    // 4. action이 reducer로 전달 됨(dispatch에 의해)
  }, [email, password]); // state는 dependency에 넣어줘야 최신 상태로 가져올 수 있음

  return (
    <FormWrapper onFinish={onSubmitForm}>
      <div>
        <label htmlFor="user-id">이메일</label>
        <br />
        <Input
          name="user-id"
          type="email"
          value={email}
          onChange={onChangeEmail}
          required
        />
      </div>
      <div>
        <label htmlFor="user-password">비밀번호</label>
        <br />
        <Input
          name="user-password"
          type="password"
          value={password}
          onChange={onChangePassword}
          required
        />
      </div>
      <ButtonWrapper>
        <Button type="primary" htmlType="submit" loading={logInLoading}>
          로그인
        </Button> // 1-1. component 내 이벤트 호출
        <Link href="/signup">
          <a>
            <Button>회원가입</Button>
          </a>
        </Link>
      </ButtonWrapper>
    </FormWrapper>
  );
};
  1. (dispatch에 의해) action이 reducer와 saga로 전달 됨
  2. dispatch된 action의 영향으로 reducer의 state값이 변화
// reducers/user.js
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case LOG_IN_REQUEST: // 4-1. action이 reducer로 전달 됨(dispatch에 의해)
      return { // 5-1. dispatch된 action의 영향으로 reducer의 state값이 변화
        ...state,
        logInLoading: true,
        logInError: null,
        logInDone: false,
      };
    case LOG_IN_SUCCESS:  // 5-2. dispatch된 action의 영향으로 reducer의 state값이 변화
      return {
        ...state,
        logInLoading: false,
        logInDone: true,
        // me: action.data,
        me: dummyUser(action.data),
      };
    case LOG_IN_FAILURE:
      return {
        ...state,
        logInLoading: false,
        logInError: action.error,
      };
    default:
      return state;
  }
};

// sagas/user.js
import { all, delay, fork, put, takeLatest } from 'redux-saga/effects';
import {
  LOG_IN_FAILURE,
  LOG_IN_REQUEST,
  LOG_IN_SUCCESS,
} from '../reducers/user';

function* login(action) {
  try {
    console.log('login gen');
    yield delay(1000);
    yield put({
      type: LOG_IN_SUCCESS,
      data: action.data,
    });
  } catch (err) {
    yield put({
      type: LOG_IN_FAILURE,
      error: err.response.data,
    });
  }
}

function* watchLogin() { // 4-2. action이 saga로 전달 됨(dispatch에 의해)
  yield takeLatest(LOG_IN_REQUEST, login);
}

export default function* userSaga() {
  yield all([fork(watchLogin), fork(watchLogout), fork(watchSignUp)]);
}
  1. 렌더링
const AppLayout = ({ children }) => {
  const { me } = useSelector(state => state.user); // redux store의 state를 가져옴

  return (
    <div>
      {children}
      {me ? <UserProfile /> : <LoginForm />} // saga의 action에 의해 reducer에서 state값을 바꾸면 리덕스의 state값에 의존하고 있던 부분이 다시 렌더링 됨
    </div>
  );
};

AppLayout.propTypes = {
  children: PropTypes.node.isRequired,
};

export default AppLayout;
profile
움직이는 만큼 행복해진다

0개의 댓글