본문 바로가기
React

2021.09.22 React 로그인, 회원가입 (3)

by 해맑은 코린이 2021. 9. 22.

2021.09.22 React 로그인, 회원가입 (3)_정리노트

 

지금은 새벽 4시 11분에 쓰기 시쟉했균 홀홀 과연 몇시까지 할것인가..... 2일 놀았으니 빡세게 달리는중 .. ㅎ 사실 할머니집에서 쉬고싶다쉬고싶다....ㅋㅋ큐ㅠㅠㅠ 하루 자고 오는데 어쩌다보니 수, 목도 그렇고 팀 플젝 때문에 제대로는 못놀겠지만 그래도 편히 자고 싶어서 끝까지 달리는중 목표 시작 6시....껄껄 하지만 포스팅 기본 4시간 걸려서 장담은 못함 ㅎ 근ㄷ ㅔ 지금 알바 7시간 하고 계속 달리는 ㅈ ㅜ ㅇ 이라... 힘들당 하지만 더이상 미룰수업쯰

 

---- 어림도 없지 추석 당일 친척집 방문했다가 자고 바로 일어나서 카페로 달려와서.. 커피 2잔째 홀짝이며.. 포스팅하는 나.... 껄껄 빡세게 다시 달린다!!!!!!

 

저번에는 API 요청을 하고 기능을 구현하고, 에러 메세지까지 띄워보았다면 이번엔 헤더 컴포넌트를 만들어서 localStorage 사용으로 사용자의 로그인 정보를 기억하게 만들어서 로그인 후에 새로 고침을 해도 로그인이 유지되는 기능을 만들자!

 

 

헤더 컴포넌트를 만들기 전에 다른 컴포넌트에서 불러와서 사용하기 편하게  Responsive 라는 반응형 컴포넌트를 따로 분리해서 만들어주게쌈.

 

 src/components/common/Responsive.js (새로 생성)

 

import React from 'react';
import styled from 'styled-components';


const ResponsiveBlock = styled.div`
    padding-left: 1rem;
    padding-right: 1rem;
    width: 1024px;
    margin: 0 auto;

    /*  desktop  */
    @media (max-width : 1024px) {
        width: 768px;
    }
    /* tablet,mobile */
    @media (max-width: 768px){
        width: 100%;
    }
`;



const Responsive = ({children, ...rest}) =>{
    // style, className,onClick,onMouseMove 등의 props 를 사용할 수 있도록 ...rest 사용하여 ResponsiveBlock 전달
    // 이렇게 해주고 받을 때 rest 를 받으면 해당 객체 안에 있는 애들을 모두 props 로 인식함. 
    // props 가 많아서 일일이 추가해주기 귀찮을 때나, 해당 컴포넌트 마다 뭘 받는지 불투명하면 유용한 기능
    return <ResponsiveBlock {...rest }>{children}</ResponsiveBlock>;
}


export default Responsive;

요리조리 꿀팁들이 많이 나온당. props 가 여러개라면 rest props 사용~!~! 귀찮음이 훨줄어든다.

 

 

import React from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import Responsive from './Responsive';
import Button from './Button';

const HeaderBlock = styled.div`
  position: fixed;
  width: 100%;
  background: white;
  box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.08);
`;

/**
 * Responsive 컴포넌트의 속성에 스타일을 추가해서 새로운 컴포넌트 생성
 */
const Wrapper = styled(Responsive)`
  height: 4rem;
  display: flex;
  align-items: center;
  justify-content: space-between; /* flex option */
  .logo {
    font-size: 1.125rem;
    font-weight: 800;
    letter-spacing: 2px;
  }
  .right {
    display: flex;
    align-items: center;
  }
`;

/**
 * 헤더가 fixed로 되어 있기 때문에 겹치지 않게 페이지의 컨텐츠가 4rem 아래 나타나도록 해주는 컴포넌트
 */
const Spacer = styled.div`
  height: 4rem;
`;


const Header = ({ user }) => {
  return (
    <>
      <HeaderBlock>
        <Wrapper>
          {/* index 로 이동하는 Link 컴포넌트 */}
          <Link to="/" className="logo">
          	REACTERS
          </Link>
          {/* 로그인으로 이동하는 버튼 링크 컴포넌트 */}
          <div className="right">
              <Button to="/login">로그인</Button>
            </div>
 
        </Wrapper>
      </HeaderBlock>
      <Spacer />
    </>
  );
};

export default Header;

 

이제 이 헤더 컴포넌트를 

 

src/pages/PostListPage 

 

이 페이지에서 렌더링 하게 되면, 

 

이렇게 반응형에 따라서 ( 데스크탑 크기에서부터는 가로 길이가 768px )

잘 뜨면 성공!

 

 

이제 로그인 버튼을 눌렀을 때 링크로 이동하게 해봅시단.

 

 

src/components/commonn/Button

import React from 'react';
// css import
import styled,{css} from 'styled-components';
import palette from '../../lib/styles/palette';
// Link import
import { Link } from 'react-router-dom';

// css 스타일 따로 분리
const buttonStyle = css`
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  font-weight: bold;
  padding: 0.25rem 1rem;
  color: white;
  outline: none;
  cursor: pointer;
  background: ${palette.violet[8]};
  &:hover {
    background: ${palette.violet[6]};
  }
  /* width 100% props */
  ${props =>
    props.fullWidth &&
    css`
      padding-top: 0.75rem;
      padding-bottom: 0.75rem;
      width: 100%;
      font-size: 1.125rem;
    `}
    /* color props */
  ${props =>
    props.cyan &&
    css`
      background: ${palette.cyan[5]};
      &:hover {
        background: ${palette.cyan[4]};
      }
    `}
`;




// buttonStyle 값 재사용.
const StyledButton = styled.button`
  ${buttonStyle}
`;
// 마찬가지로 Link 태그에도 똑같은 스타일 재사용
const StyledLink = styled(Link)`
  ${buttonStyle}
`;
// Button 에 받아오는 props 를 모두 styledButton 에 전달한다는 의미(...props)
const Button = props => {
  // to 라는 props 가 있다면 링크 컴포넌트를 띄워주고, 아니면 버튼 컴포넌틀를 띄워줌.
  return props.to ? (
    // styled 로 직접 감싸서 만든 컴포넌트이기 때문에 임의로 props 가 필터링 되지 않는다. 
    // 그래서 StyledLink 를 사용하는 과정에서 props.cyan 의 값을 숫자 1과 0 으로 변환해주었는데, 
    // 필터링이 되지 않으면, true, false 의 불리언 값으로 Link에서 사용하는 a태그에 그대로 전달되는데, a 태그는 불리언값을 임의 props로 설정되지 않고 오직 숫자/문자열만 허용하기 때문에 숫자로 변환해준 것.
    <StyledLink {...props} cyan={props.cyan ? 1 : 0} />
  ) : (
    // styled.button 의 경우 cyan과 같은 임의의 props 가 자동으로 필터링되어 스타일을 만드는 용도로만 사용되고, 실제 button 엘리먼트에 속성이 전달되지 않는다.
    <StyledButton {...props} />
  );
};

export default Button;

 

저번에 배운 withRouter 를 사용해서 historty 값을 사용하여 to 값이 있을 경우 페이지를 이동하도록 구현하여 Button컴포넌트를 Link a 로 작동하게 하는 방법도 있지만, 이런식으로 새로운 Link 컴포넌트를 만드는 것이 좋다. 링크 이동은 a태그가 해야할 일이고, 사용자의 경험 측면에서는 비슷하겠지만, 웹 접근성에서는 이게 좀 더 맞는 방법. 그리고 

 

 

 

a 태그는 버튼에 올렸을 떄 하단에 이동할 주소도 뜨기 때문에 이 방법만 정리했움. 주석으로 정리했지만 생각보다는 막 쉽지는..? 않은 느낌ㅋㅋㅋㅋ 

 

src/components/common/Header

const Header = ({ user }) => {
  return (
    <>
      <HeaderBlock>
        <Wrapper>
          {/* index 로 이동하는 Link 컴포넌트 */}
          <Link to="/" className="logo">
          	REACTERS
          </Link>
          {/* 로그인으로 이동하는 버튼 링크 컴포넌트 */}
          <div className="right">
              <Button to="/login">로그인</Button>
            </div>
 
        </Wrapper>
      </HeaderBlock>
      <Spacer />
    </>
  );
};

export default Header;

로그인 버튼에는 to props 가 설정되어 있기 때문에 링크 컴포넌트가 렌더링 될 것이고, 우리가 버튼 컴포넌트에 설정해준 대로 클릭했을 때 로그인 페이지로 들어가면 성공이다! 역시 props 를 잘 잡아놔야 이렇게 props 가 여러개 넘어갈 때의 세부 속성까지 흐름을 이해할 수 있을 것 같음. 기초만은 정말 계속 보고 또 봐서 헷갈리지 않아야겠다.

 

 

이제 로그인 페이지에서 로그인에 성공하면, 헤더 컴포넌트에 로그인 중인 상태를 받아와서 로그아웃 버튼이 뜨면서 새로고침을 해도 이 상태가 유지되도록 하는 작업 구현하기!

 

먼저 헤더컴포넌트를 리덕스에 연결하기 위해 컨테이너 컴포넌트 작성.

 

src/containers/common/HeaderContainers.js ( 새로 생성 )

import React from 'react';
import { useSelector } from 'react-redux';
import Header from '../../components/common/Header';
import user from '../../modules/user';


const HeaderContainer = () => {
    // user 리덕스 연결
    const { user } = useSelector(({user}) => ({ user : user.user}));
    return (
        <div>
            <Header user = {user} />
        </div>
    );
};

export default HeaderContainer;

헤더 컴포넌트에서 user 값이 주어질 경우 계정명과 로그아웃 버튼이 보여주도록 헤더 컴포넌트도 수정.

 

components/common/Header

import React from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import Responsive from './Responsive';
import Button from './Button';

const HeaderBlock = styled.div`
  position: fixed;
  width: 100%;
  background: white;
  box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.08);
`;

/**
 * Responsive 컴포넌트의 속성에 스타일을 추가해서 새로운 컴포넌트 생성
 */
const Wrapper = styled(Responsive)`
  height: 4rem;
  display: flex;
  align-items: center;
  justify-content: space-between; /* flex option */
  .logo {
    font-size: 1.125rem;
    font-weight: 800;
    letter-spacing: 2px;
  }
  .right {
    display: flex;
    align-items: center;
  }
`;



/**
 * 헤더가 fixed로 되어 있기 때문에 겹치지 않게 페이지의 컨텐츠가 4rem 아래 나타나도록 해주는 컴포넌트
 */
const Spacer = styled.div`
  height: 4rem;
`;


/**
 * user 정보 띄워줄 컴포넌트
 */
const UserInfo = styled.div`
  font-weight: 800;
  margin-right: 1rem;
`;

const Header = ({ user }) => {
  return (
    <>
      <HeaderBlock>
        <Wrapper>
          {/* index 로 이동하는 Link 컴포넌트 */}
          <Link to="/" className="logo">
            REACTERS
          </Link>
          {/* user 값이 있으면 즉, 로그인 상태면 로그아웃을 버튼을 보여주고, 그렇지 않으면 로그인 버튼 보여주기 */}
          {user ? (
            <div className="right">
              <UserInfo>{user.username}</UserInfo>
              <Button>로그아웃</Button>
            </div>
          ) : (
            <div className="right">
              <Button to="/login">로그인</Button>
            </div>
          )}
        </Wrapper>
      </HeaderBlock>
      <Spacer />
    </>
  );
};

export default Header;

 

이렇게 적은 후에

 

src/pages/PostListPage

// list
import React from 'react';
import Button from '../components/common/Button';
import Header from '../components/common/Header';
import HeaderContainer from '../containers/common/HeaderContainer';
const PostListPage = () => {
    return (
        <div>
            <HeaderContainer />
           <div>Korin's Frontend Project</div> 
        </div>
    );
};

export default PostListPage;

user 의 정보를 담고있는 컨테이너 컴포넌트로 헤더를 대신해서 로그인하고 들어가면,

 

 

이렇게 사용자 정보를 잘 띄워주는데, 새로고침하면 다시 정보가 초기화 된다. 이를 유지시키러 ㄲ!

 

 

src/containers/auth/LoginForm

src/containers/auth/RegisterForm

 

useEffect(()=>{
        if (user){
            // user 값이 제대로 들어갔으면 인덱스 페이지로
            history.push('/');
            try{
                // 리액트 앱에 브라우저에서 맨 처음 렌더링 될 때 localoStorage 에서 값을 불러와 리덕스 스토어 안에 넣도록 구현.
                // 첫 렌더링 시이기 떄문에 최상위 App 컴포넌트에서 useEffect를 사용하여 처리하거나, 클래스형 컴포넌트라면 componentDidMount 메서드에서 구현해도 됨.
                // 여기서는 엔트리 파일인 index.js 에서 처리할 것임. 
                // 왜 ?? >> componentDidMount, useEffect 는 컴포넌트가 한 번 렌더링 된 이후에 실행 되기 떄문. 그러면 사용자에게는 아주 잠깐의 깜짝임 현상 ( 로그인이 나타났다가 로그아웃이 나타나는 현상) 이 나타남. 
                // index에서 구현하게 되면 이러한 깜빡임 현상 나타나지 않음.
                localStorage.setItem('user', JSON.stringify(user));
            }catch(e){
                console.log('localStorage is not working')
            }
        }
    },[history,user]);

 

useEffect 에서 user 값을 검사하는 부분에 로컬스토리지를 이용한 코드를 넣는다.  주석에서 보면 앞으로 얘를 저장해서 어디서 불러와야할지도 정리해놨음.

 

그럼 localStorage 개념도 살짝 정리하고 가장.

 

localStorage

자주 비교되는 개념에는 세선 스토리지(sessionStorage) 가 있다. 두 매커니즘의 차이점은 데이터가 어떤 범위 내에서 얼마나 오래 보존되느냐이다. 세션 스토리지는 웹페이지의 세션이 끝날 때, 즉 창이 닫힐 때 저장된 데이터가 지워지는 반면에, 로컬 스토리지는 웹페이지의 세션이 끝나더라도 지워지지 않음. 

쉽게 예시로 같은 브라우저에서 여러개의 탭이나 창으로 띄우면, 세션 스토리지는 데이터가 서로 격리되어 저장되기 때문에 각 탭이나 창이 닫힐 때도 저장해둔 데이터가 같이 소멸되는 것과 다르게 로컬 스토리지에서는 여러 개의 탭이나, 창에서 데이터가 서로 공유되고, 탭이나 창을 닫아도 데이터는 브라우저에 남아있게 됨.

그렇지만 로컬 스토리지든, 세션 스토리지든 동일한 컴퓨터, 동일한 브라우저에 데이터가 저장되기 때문에 브라우저를 이동한다거나, 다른 컴퓨터에서 브라우저를 띄우면 엄연히 다른 브라우저이므로 다른 데이터가 각각의 브라우저에 저장되게 된다.

로컬 스토리지와 세션 스토리지는 같은 자바스크립트 API 를 쓰기 때문에 밑의 로컬 스토리지의 예시에서 세션 스토리지를 사용하고 싶다면 localStorage부분을 sessionStorage 로 바꾸면 된다!

 

 

로컬 스토리지 기본 API 예시

기본적으로 key, value 로 이루어진 데이터를 저장할 수 있다.

// 키에 데이터 쓰기 
localStorage.setItem("key", value); 

// 키로 부터 데이터 읽기 localStorage.getItem("key"); 
>> "value"

// 키의 데이터 삭제 
localStorage.removeItem("key"); 

// 모든 키의 데이터 삭제 
localStorage.clear(); 

// 저장된 키/값 쌍의 개수 
localStorage.length;

 

 

이렇게 저장하는군 음음

 

그리고 주의해야할 점 . 데이터 타입은 오직 문자형만 지원!! 

 

 

이렇게 숫자형으로 value 값을 넣어도 뱉어내는 건 문자열로 뱉어낸다. 특히 객체로 된 데이터 같은 경우 "[object Object]" 로 나오기 떄문에, 이를 JSON 형태로 데이터를 읽고 쓰게 만들어서 해결할 수 있다.

 

위와 같이 로컬 스토리지에 쓸 데이터를 JSON 형태로 만들어주면, 읽을 때 다시 parse 를 사용해서 원본의 데이터를 그대로 얻을 수 있다.

 

 

https://www.daleseo.com/js-web-storage/

 

[자바스크립트] 웹 스토리지 (localStorage, sessionStorage) 사용법

Engineering Blog by Dale Seo

www.daleseo.com

 

매우매우 내가 이해하기도 쉽게 예시를 들어서 잘 설명해주신 분께 감사를!

 

 

 

음음 이제 다시 우리 코드로 들어와서

 localStorage.setItem('user', JSON.stringify(user));

 

왜 이렇게 적는지도 알았고, 나중에 불러올 때 parse 를 이용하겠다는 것도 유추해볼 수 있음.

 

 

이제 index.js 에서 유저를 불러와봅시다!!

 

src/index

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import createSagaMiddleware from 'redux-saga';
import rootReducer, { rootSaga } from './modules';

// user module import
import { tempSetUser,check } from './modules/user';


const sagaMiddleware = createSagaMiddleware();
const store = createStore(
  rootReducer,
  composeWithDevTools(applyMiddleware(sagaMiddleware))
);

// loadUser func. 
function loadUser(){
  try{
    // user key 값으로 value 가져옴.
    const user = localStorage.getItem('user');
    // user 값이 없다면 아무것도 리턴하지 않음.
    if(!user) return;
    // user 값이 있다면 parse 로 user의 정보를 JSON.parse 로 가져옴. 그리고 tempSetUser 액션 생성함수를 디스패치
    store.dispatch(tempSetUser(JSON.parse(user)));
    // check 액션 생성 함수 역시 디스패치
    store.dispatch(check());
  }catch(e){
    console.log('localStorage is not working')
  }
}

sagaMiddleware.run(rootSaga);
// 사가를 먼저 호출하고 해당 함수를 실행하여야 CHECK 액션을 디스패치해서 이를 사가에서 제대로 처리함.
loadUser();

ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById('root')
);

 

해당 기능은 현재 페이지가 새로고침 될 때 localStorage 에 사용자 정보가 즉, user 값이 들어있다면 그 사용자 값을 리덕스의 스토어 안에 넣게 구현한 것이다. 그러고 나서 정말 사용자가 로그인 상태인지 CHECK 액션 디스패치를 통해 검증한 것.

 

해당 CHECK 액션이 디스패치 되면 사가를 통해 api/check API 를 호출하고, 이 API 는 성공할 수도있고, 실패할 수도있다. 만약 실패하면 사용자 상태를 초기화해야 하고, localStorage 에 들어 있는 값도 지워 주어야한다.

 

 

 

갸앍 새로고침해도 이제 로컬스토리지에 사용자가 잘 담겨서 그대로 유지된다!!! 굿굿 확실히 액션 타입을 모듈/액션네임 으로 해놓으니까 어떻게 이뤄지는지 과정도 잘 보이는구먼 홀홀

 

 

아까 그럼 말했던 API 가 실패하면, 즉 로그인 정보가 만료되었을 경우와 마찬가지로 사용자 정볼를 초기화하는 작업까지 추가로 해봅시단.

 

 

backend 작업 딴에서 쿠키를 발급해서 엑세스 토큰 만료 기간이 7일로 설정했으니까, 로그인이 7일 뒤 만료되면 되는 작업인 것 같음메! 가장!

 

src/modules/user

import { createAction, handleActions } from "redux-actions";
import { takeLatest } from 'redux-saga/effects';
import * as authAPI from '../lib/api/auth';
import createRequestSaga,{
    createRequestActionTypes
} from "../lib/createRequestSaga";


//액션 타입 정의
const TEMP_SET_USER = 'user/TEMP_SET_USER'; // 새로고침 이후 임시 로그인 처리
// 액션 타입 정의 함수 가져와서 한번에 적어주기
const [CHECK,CHECK_SUCCESS,CHECK_FAILURE ] = createRequestActionTypes(
    'user/CHECK',
);


// 액션 생성 함수
export const tempSetUser = createAction(TEMP_SET_USER, user => user);
export const check = createAction(CHECK);


// saga 생성
const checkSaga = createRequestSaga(CHECK,authAPI.check);

// 로그인 정보 만료시 처리해줄 부분
function checkFailureSaga(){
    try{
        localStorage.removeItem('user') // localStorage 에서 user 를 제거
    }catch(e){
        console.log('localStorage is not working')
    }
}

export function* userSaga(){
    // takeLatest는 기존에 진행 중이던 작업이 있다면 취소 처리 하고 가장 마지막으로 실행된 작업만 수행.
    yield takeLatest(CHECK,checkSaga);
    // check localStorage user
    yield takeLatest(CHECK_FAILURE, checkFailureSaga);
};

// 초기 상태 정의
const initailState = {
    user: null,
    checkError: null
};


// 리듀서 함수
export default handleActions(
    {
        [TEMP_SET_USER]:(state, {payload: user}) =>({
            ...state,
            user,
        }),

        [CHECK_SUCCESS]:(state, {payload : user }) => ({
            ...state,
            user,
            checkError: null,
        }),
        [CHECK_FAILURE]:(state, {payload : error }) => ({
            ...state,
            user: null,
            checkError: null,
        }),
    },
    initailState,
)

checkFailureSaga 함수는 CHECK_FAILURE 액션이 발생할 때 해당 함수가 호출되도록 설정. 이 함수에서는 localStorage 안에 있는 user 값을 초기화해줌.

 

 스토어 안의 user 값은 CHECK_FAILURE 액션이 발생했을 때 user 값을 null로 설정하도록 이미 처리했으니 윗 부분만 신경써주면 된다. 

 

실행 화면

Application 탭에서 로컬 3000번 포트의 쿠키가 뜨는데,

 

요고요고 지우는 탭을 누르고 새로고침을 하면 초기화가 될테고

 

 

콘솔에서도 undefined 가 뜨면 성공!

 

 

 

 

 


으으 진짜 3편의 대장정 끝.... 로그아웃 기능만 구현하면 끝!! 로그아웃은 로그아웃 API 를 호출해서 로컬스토리지의 값을 없애주기만 하면 되는 비교적 간단한 작업.

 

 

src/api/auth

 

logout api 를 호출하는 logout 함수를 만들어서 LOGOUT 이라는 액션을 만들고, 이 액션이 디스패치 되었을 때 해당 API 호출 후 로컬스토리지의 user 값을 지워주겠음.

 

src/modules/user

import { createAction, handleActions } from "redux-actions";
import { takeLatest,call } from 'redux-saga/effects';
import * as authAPI from '../lib/api/auth';
import createRequestSaga,{
    createRequestActionTypes
} from "../lib/createRequestSaga";


//액션 타입 정의
const TEMP_SET_USER = 'user/TEMP_SET_USER'; // 새로고침 이후 임시 로그인 처리
// 액션 타입 정의 함수 가져와서 한번에 적어주기
const [CHECK,CHECK_SUCCESS,CHECK_FAILURE ] = createRequestActionTypes(
    'user/CHECK',
);


// 로그아웃 액션
const LOGOUT = 'user/LOGOUT';


// 액션 생성 함수
export const tempSetUser = createAction(TEMP_SET_USER, user => user);
export const check = createAction(CHECK);
// logout 생성 함수
export const logout = createAction(LOGOUT);

// saga 생성
const checkSaga = createRequestSaga(CHECK,authAPI.check);

// 로그인 정보 만료시 처리해줄 부분
function checkFailureSaga(){
    try{
        localStorage.removeItem('user') // localStorage 에서 user 를 제거
    }catch(e){
        console.log('localStorage is not working')
    }
}

// 로그아웃 사가 생성
function* logoutSaga(){
    try{
        yield call(authAPI.logout);
        localStorage.removeItem('user');
    }catch(e){
        console.log(e)
    }
}

export function* userSaga(){
    // takeLatest는 기존에 진행 중이던 작업이 있다면 취소 처리 하고 가장 마지막으로 실행된 작업만 수행.
    yield takeLatest(CHECK,checkSaga);
    // check localStorage user
    yield takeLatest(CHECK_FAILURE, checkFailureSaga);
    // logout user
    yield takeLatest(LOGOUT, logoutSaga);
};

// 초기 상태 정의
const initailState = {
    user: null,
    checkError: null
};


// 리듀서 함수
export default handleActions(
    {
        [TEMP_SET_USER]:(state, {payload: user}) =>({
            ...state,
            user,
        }),

        [CHECK_SUCCESS]:(state, {payload : user }) => ({
            ...state,
            user,
            checkError: null,
        }),
        [CHECK_FAILURE]:(state, {payload : error }) => ({
            ...state,
            user: null,
            checkError: null,
        }),
        [LOGOUT]:(state,{payload:user}) =>({
            ...state,
            user:null,
        }),
    },
    initailState,
)

이렇게 리덕스관련 코드를 적어주었고, 로그아웃 성공/ 실패의 경우의 여부가 중요하지 않아서 SUCCESS,FAILURE 와 같은 액션은 따로 만들지 않았음!

추가로 리듀서에도 스토어의 user 값을 null 로 설정!

 

 

그리고 컨테이너 컴포넌트에 로그아웃 함수 연결시켜줌. 그러고 헤더컴포넌트에 해당 함수를 전달해준다.

 

src/containers/common/HeaderContainer

import React from 'react';
import { useDispatch } from 'react-redux';
import { useSelector } from 'react-redux';
import Header from '../../components/common/Header';
import user from '../../modules/user';
import { logout } from '../../modules/user';

const HeaderContainer = () => {
    // user 리덕스 연결
    const { user } = useSelector(({user}) => ({ user : user.user}));
    const dispatch = useDispatch();

    const onLogout = ()=>{
    // logout 액션 생성함수 디스패치
        dispatch(logout());
    }
    return (
        <div>
            <Header user = {user} onLogout={onLogout} />
        </div>
    );
};

export default HeaderContainer;

 

이제 헤더 컴포넌트에서 해당 함수가 호출 되도록 받아옵씨단.

 

src/components/common/Header

 

 

이렇게!

 

 

실행화면

 

 

로그아웃 버튼 눌러보기..두근두그느느느는

 

 

 

와!!!!!!!!!!!!!! 성공!!!!! 


 

대장정.....끝....회원가입,,,, 로그인 정말 언제나 느끼는 거지만 User 가 제일 어려워 헝헝 일단 UI 구성하는 것도 은근 건드릴 게 많아서 놀랬고, 리덕스를 사용해서 상태를 관리하는 과정부터 api 호출까지.... 정말 쉬운게 하나도 없네.... ㅠㅠ 리덕스로 상태 관리를 하는 건 계속 CRUD 를 하면서도 비슷한 패턴으로 반복될테니 코드치기 + 블로그 글 정리하면서 쓰기 하면서 나도 익숙해질테다...! 

 

약간 나는 공부를 해본적이 없어서 그런가 ㅎ 확실히 그냥 하나하나 흐름 따라가면서 무식하게 흐름 하나하나 중간에 놓칠법한 부분들까지 세세하게 정리하면서 하는 스탈인가봐... 끌끌 앞으로할 CRUD 는 좀 더 간단하게 내가 흐름을 이해하고 정리해서 깔끔한 포스팅이 되도록 해볼게...! 휴ㅠㅠㅠ 길고 길었던 로그인, 회원가입 포스팅 끝!

 

 

댓글