Archive

2021-05-06 TIL

|

2021-05-06 TIL


  • 오늘 한 것
    1. 학원 대면수업 (15:30~22:00) 프로젝트 기간
    2. 어드민 게시판 관리 오류 수정 - 백엔드 팀원이 만들어놓은 게시판 관리의 CRUD 기능이 대부분 작동하지 않아서 오늘은 그것들을 고쳐나갔다. qna 관리에서는 해당 게시글의 댓글 리스트를 볼 수 있는 모달창을 화면에 출력하고 싶었다. 모달창 클릭시 ajax 요청으로 댓글 리스트를 불러와서 데이터 저장 후 select box의 onChange 이벤트로 하나씩 꺼내어 해결했다. 또, 리스트의 각각 댓글 등록 버튼을 클릭하여 어드민 페이지에서도 qna 게시판에 댓글을 달 수 있도록 했는데, 문제는 어느 버튼이 눌렸는지 id 값을 알아야한다는 것이었다. 처음에 동적으로 리스트 생성시 서버에서 받은 데이터의 id값을 버튼의 onclick 이벤트를 실행할 메서드의 매개변수로 넣어서 해결했다.
    3. Youtube Clone Project with React, Node.js - 지금껏 TIL에 포스팅은 안했지만 Auth Boiler-Plate 코딩 강의를 끝낸 뒤 이어서 Node.js와 React를 활용한 유튜브 클론코딩 강의를 수강하고 있었다. 인증 관련은 미리 만들어놓은 boiler-plate를 사용했고 drop-zone에 동영상 업로드 시 multer로 노드 서버와 mongoDB 저장 후 card layout에 출력되는 기능과 ffmpeg를 사용한 썸네일 생성, 비디오 디테일 페이지의 구독 기능 등을 배웠다. 오늘은 댓글 기능을 배웠다. 일반 댓글과 답글형 댓글을 구현했는데 하위 컴포넌트들에서 댓글 입력시 부모 컴포넌트의 state에 저장되도록 setState를 실행할 메소드를 선언 후 props로 전달하여 하위 컴포넌트에서 사용하였다. 또한 처음 렌더링시 일반 댓글을 먼저 보이게 하고 답글형 댓글은 특정 버튼을 클릭하여 보이게 하기위해 댓글 렌더링시 데이터에 받는사람 (responseTo)이 있는 경우와 없는 경우의 분기를 나눠서 해결했다.

  • 내일 할 것
    1. 학원 대면수업 (15:30~22:00) 프로젝트 기간
    2. 팀 프로젝트 error 랜딩 페이지 작성
    3. 테스트 실행
    4. Youtube Clone Project with React, Node.js



  • 끝으로

이제 팀 프로젝트는 개발 막바지에 접어들었고 어느정도 완성의 고지가 눈 앞에 다가왔다. 아직 ppt 제작, 시연 영상 편집 등 해야할 일이 남았지만 우선은 완벽한 상태로 배포를 하기 위해 테스트를 진행하는게 먼저다.

오늘의 한 줄 총평 : 이제 곧 완성


Auth Boiler Plate View with React,Redux - Auth

|

Boiler Plate with React,Redux - Auth


Auth

HOC (Higer Order Component)로 Auth 컴포넌트를 만들어서 이 안에 제한된 페이지의 컴포넌트를 넣어준다

그러면 이 제한된 페이지에 접속시 이 사용자가 인증된 사용자인지 HOC가 판별을 해준다

// hoc/auth.js

import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { auth } from '../_actions/user_action';

export default function (SpecificComponent, option, adminRoute = null) {
  // option settings
  // null => 아무나 출입
  // true => 로그인한 유저만 출입
  // false => 로그인한 유저는 출입 불가

  function AuthenticationCheck(props) {
    const dispatch = useDispatch();

    useEffect(() => {
      dispatch(auth()).then((response) => {
        console.log(response);

        // 로그인하지 않은 상태
        if (!response.payload.isAuth) {
          if (option) {
            props.history.push('/login');
          }
        } else {
          // 로그인 한 상태
          if (adminRoute && !response.payload.isAdmin) {
            // 관리자만 들어갈 수 있는 페이지 접속시
            props.history.push('/');
          } else {
            if (option === false) {
              props.history.push('/');
            }
          }
        }
      });
    }, []);

    return <SpecificComponent />;
  }

  return AuthenticationCheck;
}

// user_action.js

export function auth() {
  const request = axios.get('/api/users/auth').then((response) => {
    return response.data;
  });

  return {
    type: actions.AUTH_USER,
    payload: request,
  };
}

미리 서버에 만들어놓은 auth api를 통해 인증에 통과한 사용자의 데이터를 전부 받아다가 payload에 넣어준다


// user_reducer.js

import * as actions from '../_actions/types';

export default function (state = {}, action) {
  switch (action.type) {
    case actions.LOGIN_USER:
      return {
        ...state,
        loginSuccess: action.payload,
      };
    case actions.REGISTER_USER:
      return {
        ...state,
        register: action.payload,
      };
    case actions.AUTH_USER:
      return { // 로그인한 사용자 정보 저장
        ...state,
        userData: action.payload,
      };
    default:
      return state;
  }
}


이렇게 만든 hoc를 router에 의해 페이지 이동을 하는 App.js에서 사용한다

// App.js

import Auth from './hoc/auth';

function App() {
  return (
    <BrowserRouter>
      <div>
        <Switch>
          <Route exact path='/' component={Auth(LandingPage, null)} />
          <Route exact path='/login' component={Auth(LoginPage, false)} />
          <Route exact path='/register' component={Auth(RegisterPage, false)} />
        </Switch>
      </div>
    </BrowserRouter>
  );
}

export default App;

Auth HOC로 각 페이지 컴포넌트들을 한 번 더 감싸주고 두 번째 인자로 option을 넣어주는데 메인 페이지는 로그인 하던 안하던 상관 없으니 null을 주고 나머지 로그인, 회원가입 페이지는 로그인한 유저가 진입할 수 없도록 false를 넣어준다

HOC에서 나눠놓은 분기에 이 option에 의해 로그인한 유저가 회원가입, 로그인창 진입시 다시 메인 페이지로 redirect되게 된다



참고 자료


기초 노드 리액트 강의 - John Ahn

Auth Boiler Plate View with React,Redux - Signup, Logout

|

Boiler Plate with React,Redux - Signup, Logout


Signup View Page

이미 로그인때 리덕스 관련 빌드를 다 해놓아서 특별히 크게 바뀌는건 없고 그저 추가가 될 뿐이다

view 페이지도 로그인 때의 로직을 그대로 복붙해서 state, input창 몇 개 더 추가하고 dispatch할 action명을 바꾸면 끝이다

dispatch(registerUser(body)).then((response) => {
    if (response.payload.success) {
        props.history.push('/login');
    } else {
        alert('SignUp Fail!');
    }
});


action도 회원가입용을 하나 더 추가한다

export function registerUser(dataSubmit) {
  const request = axios
    .post('/api/users/register', dataSubmit)
    .then((response) => {
      return response.data;
    });

  return {
    type: actions.REGISTER_USER,
    payload: request,
  };
}


reducer에 switch 구문의 case에도 회원가입 action을 추가한다

export default function (state = {}, action) {
  switch (action.type) {
    case actions.LOGIN_USER: // 로그인
      return {
        ...state,
        loginSuccess: action.payload,
      };
    case actions.REGISTER_USER: // 회원가입
      return {
        ...state,
        register: action.payload,
      };
    default:
      return state;
  }
}


테스트로 회원가입을 시도해본다

테스트

회원가입도 잘 되고 로그인도 잘 된다

성공~~🤟


Logout

로그아웃은 더 간단하다

로그인시 이동하는 LandingPage에 button 태그를 하나 파고 onClick 이벤트를 걸어준다. 끝

const onClickHandler = () => {
    axios.get('/api/users/logout').then((response) => {
        console.log(response.data);
        if (response.data.success) {
            props.history.push('/');
        } else {
            alert('Logout Fail!');
        }
    });
};

<button onClick={onClickHandler}>Logout</button>

미리 서버에서 만들어놓은 로그아웃 api에 axios 요청을 하고 응답받은 데이터로 그에 맞는 액션을 취해주면 된다

정상적으로 작동하는지 콘솔창에 response.data를 찍어보면

테스트

success: true가 잘 찍히는 것을 볼 수 있다

성공~~🤟



참고 자료


기초 노드 리액트 강의 - John Ahn

Auth Boiler Plate View with React,Redux - Login

|

Boiler Plate with React,Redux - Login


Login View Page

Node.js 로 구현한 로그인 기능을 실질적으로 눈으로 확인하기 위해 view 페이지를 만든다

view는 react로 구현하고 상태관리는 redux를 이용한다

또, 빠른 실습을 위해 create-react-app으로 빌드한다

먼저, 간단한 로그인창을 만들고 input태그에 onChange 이벤트, form 태그에 onSubmit 이벤트를 걸어준다

onSubmit시 action을 dispatch한다

// LoginPage.js
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { loginUser } from '../../../_actions/user_action';

function LoginPage(props) {
    const dispatch = useDispatch(); 
    
    const [Email, setEmail] = useState('');
 	const [Password, setPassword] = useState('');
    
    // Email onChange Event
    const onEmailHandler = (e) => {
      setEmail(e.currentTarget.value);
    };
    // Password onChange Event
    const onPasswordHandler = (e) => {
      setPassword(e.currentTarget.value);
    };
    // onSubmit Event
    const onSubmitHandler = (e) => {
      e.preventDefault();
        
	  // Request Body 생성
      let body = {
        email: Email,
        password: Password,
      };
	  // action에서 axios 요청을 실시하고 return값으로 응답을 받음
      dispatch(loginUser(body)).then((response) => {
        if (response.payload.loginSuccess) {
          props.history.push('/');
        } else {
          alert('Login Fail!');
        }
      });
    };
    return (
    <div>
      <form
        onSubmit={onSubmitHandler}
      >
        <label>Email</label>
        <input type='email' value={Email} onChange={onEmailHandler} required />
        <label>Password</label>
        <input
          type='password'
          value={Password}
          onChange={onPasswordHandler}
          required
        />
        <br />
        <button type='submit'>Login</button>
      </form>
    </div>
  );
}
export default LoginPage;
// user_action.js

import axios from 'axios';

export function loginUser(dataSubmit) {
  // LoginPage에서 작성한 body로 axios 요청
  const request = axios
    .post('/api/users/login', dataSubmit)
    .then((response) => {
      return response.data;
    });
  // action의 payload에 response 데이터를 담는다
  return {
    type: 'LOGIN_USER',
    payload: request,
  };
}
// user_reducer.js

export default function (state = {}, action) {
  switch (action.type) {
    case 'LOGIN_USER':
      return {
        ...state,
        loginSuccess: action.payload,
      };
    default:
      return state;
  }
}


reducer는 기능별로 여러개가 나올것이므로 이 흩어져있는 reducer들을 모을 rootReducer가 필요하다

// _reducers/index.js

import { combineReducers } from 'redux';
import user from './user_reducer';

const rootReducer = combineReducers({
  user,
  // 이 곳에 reducer 추가
});

export default rootReducer;

redux 패키지의 combineReducers 메소드를 이용하여 각가의 reducer들을 모아준다


redux에서는 상태를 store라는 하나의 큰 저장소에서 관리하는데, store는 보통 index 등 패키지의 제일 앞단에 설치한다

앞으로 redux-thunk, redux-promise 등의 미들웨어를 사용할 예정이므로 store 생성시 이 미들웨어도 같이 끼워넣어 주어야한다.

store에 인자로 reducer를 넣어주는데, 이를 위해 react-redux 패키지의 Provider로 App 컴포넌트를 감싸준다

// index.js

import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import promiseMiddleware from 'redux-promise';
import ReduxThunk from 'redux-thunk';
import Reducer from './_reducers';

const createStoreWithMiddleware = applyMiddleware(
  promiseMiddleware,
  ReduxThunk
)(createStore);

ReactDOM.render(
  <React.StrictMode>
    <Provider
      store={createStoreWithMiddleware(
        Reducer,
        window.__REDUX_DEVTOOLS_EXTENSION__ &&
          window.__REDUX_DEVTOOLS_EXTENSION__()
      )}
    >
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

window.머시기들은 redux-devtool을 사용하기 위한 환경변수이다


서버 구축시 테스트용으로 미리 넣어놓은 사용자로 로그인을 시도해본다

테스트

로그인 성공시 loginSuccess: true와 서버에서 만들어 보낸 JWT가 state에 잘 저장된 것을 볼 수 있다



참고 자료


기초 노드 리액트 강의 - John Ahn

2021-04-30 TIL

|

2021-04-30 TIL


  • 오늘 한 것
    1. 학원 대면수업 (15:30~22:00) 프로젝트 기간
    1. 사용자 QnA 댓글 기능 - 백엔드 팀원이 QnA 댓글기능을 완전 잊고있다가 부랴부랴 만들었는데 전혀 작동하지 않았다. 결국 조장님이 뒤를 맡아서 갈아엎었다. 댓글은 잘 나오는데 수정이 꽤나 필요할듯해서 나도 기존에 짜놓은 로직을 대폭 수정했다. 내가 써놓은 댓글은 수정, 삭제버튼이 나오고 다른사람이 봤을때는 수정, 삭제 버튼 없이 그냥 댓글만 나오도록 구현했다. 서버의 세션에 저장되어있는 회원번호를 jsp에서 모델로 받아서 hidden type의 input에 넣어놓고 js에서 이를 가져와서 동적으로 댓글 출력시 api response의 데이터에 들어있는 회원번호와 비교하여 수정, 삭제 버튼 생성 여부를 결정한다.
    2. 어드민 통계 페이지 그래프 출력 - 대시보드의 그래프와 마찬가지로 통계 페이지에서도 그래프가 출력되도록 로직을 수정했다. 처음 Ajax 요청시 그래프 데이터를 받아서 전역변수에 저장해놓고 그래프를 그리는 메소드가 실행되도록 했는데 그래프가 그려지지 않았다. 아마도 비동기 처리 로직보다 그래프를 그리는 로직이 먼저 실행되서 그런것 같다. 그래서 AJAX 요청 안에 그래프를 그리는 메소드를 넣어서 해결했다.

  • 내일 할 것
    1. 어드민 메뉴리스트, 상품리스트 수정
    2. boiler plate with react, node.js 수강



  • 끝으로

QnA 댓글 수정, 삭제 이벤트 테스트 후 수정 요청하기

오늘의 한 줄 총평 : 토요일 보일러 플레이트 완강