728x90
728x90

출처 : 인프런_따라하며 배우는 노드, 리액트 시리즈 — 기본 강의

 

 

이번 시간에는 권한에 따라 페이지 접근 제한을 두기 위해 회원 인증 단계를 구현해보자.

페이지 접근은 다음과 같이 나눌 수 있다. 

 

 

페이지 접근 뿐 아니라 댓글 작성, 파일 전송 및 업로드 등에 관련해서도 인증 과정이 필요하다.

 

들어갈 수 있는 페이지에 대한 관리는 HOC를 쓸 것이다.

 

HOC이란?

-function으로서 다른 컴포넌트를 받아 새로운 컴포넌트를 리턴한다.

 

 

 

Auth 컴포넌트는 backend에 request를 날리고 상태를 가져올 것이다. 

 

우리가 가지고 있는 페이지(LandingPage, LoginPage, RegisterPage)를 모두 Auth 에 넣은 다음 Auth에서 처리를 하는 방식이다.

 

예를 들어 LandingPage를 넣으면 Auth는 Backend에 Request를 날린다. 현재 LandingPage에 들어와있는 사람의 상태 정보(로그인이 되어 있는지 아닌지, Admin인지 아닌지 등)를 Auth로 가져온다. 

 

 

 

Step 1) 클라이언트는 서버쪽으로 요청을 보낸다. 

 

/client/src/components/views/hoc/auth.js 파일을 새로 생성한 뒤 아래와 같이 작성한다.

여기서 서버쪽의 API로 request를 보낼 것이다. 보내기 위해 redux를 사용했다.  dispatch로 action(action이름은 auth)을 날리자. 

 

페이지가 이동할 때마다 dispatch로 계속 서버에 request를 주고 있는 것이다. 

 

파라미터 설명

- SpecificComponent : LandingPage

- option : null (아무나 출입 가능한 페이지) , true(로그인한 유저만 출입 가능), false(로그인한 유저 출입 불가능)

- adminRoute : true(관리자만 들어갈 수 있는 페이지)

import React, {useEffect} from 'react';
import axios from 'axios';
import {useDispatch} from 'react-redux'
import {auth} from '../_actions/user_action'
export default function (SpecificComponent, option, adminRoute = null){


    function AuthenticationCheck(props){

        const dispatch = useDispatch();

        useEffect(() => {
            
            dispatch(auth()).then(response => { //backend에서 처리한 정보들이 response안에 다 들어있음 
                console.log(response)
            })
            
        }, [])
        
         return (
            <SpecificComponent/>
        )
        
    }


    return AuthenticationCheck
}

 

 

Step 2) 서버에서는 미들웨어로 이동 

 

 

서버 쪽에 구현해놓은 API 를 보자.

index.js에 이미 구현해놓았다. 

get request로 요청을 받으면 미들웨어 auth가

여기로 그 요청이 넘어온다.  여기서는 토큰을 사용해서 쿠키 안에 토큰을 집어 넣는다. 그 쿠키를 보고 로그인 한 유저인지 아닌지를 판단하고 그 결과를 리액트에 넘겨준다. 

 

 

 

Step 1)-1 Action 구현

 

그리고 user_action.js에도 아래와 같이 인증 기능을 하는 함수를 추가해준다.

export function auth() {

    const request = Axios.get('/api/users/auth')        //endpoint로 get request , get이니까 login과 다르게 get 이니까 body 파라미터 필요 없음 
        .then(response => response.data)                     


        return {                    //Action 했으니까 이제 Reducer로 보냄
            type: AUTH_USER,
            payload: request
        }
    

}

 

 

Step 1)-2 Reducer 구현

 

action에 따라 결과를 리턴해주는 곳이다. AUTH_USER 케이스를 추가해준다. 

import { 
    LOGIN_USER, REGISTER_USER, AUTH_USER
} from '../_actions/types';


export default function (state={}, action){  //state 는 이전 상태 

    switch(action.type){        //Action에는 여러 타입 존재함. 이 타입에 따라 다르게 반응하도록 작성
        case LOGIN_USER:
            return {...state, loginSuccess: action.payload}       //... : spread operator은 파라미터 state를 그대로 가져온 것으로 빈 상태를 의미 
            break;

        case REGISTER_USER:
            return { ...state, register: action.payload}
            break;

        case AUTH_USER:
        return { ...state, userData: action.payload}
        break;

        default:
            return state;

    }

}

 

 

 

Step 4) 컴포넌트를 Auth HOC에 넣기

 각 컴포넌트를 Auth로 감싸주고 파라미터를 지정해서 넘겨준다. 파라미터 설명은 Step1) 에 기재했다. 

import React from 'react'

import {
  BrowserRouter as Router,
  Switch,
  Route,
  // eslint-disable-next-line
  Link  
} from "react-router-dom"; 



import LandingPage from './components/views/LandingPage/LandingPage';
import LoginPage from './components/views/LoginPage/LoginPage';
import RegisterPage from './components/views/RegisterPage/RegisterPage';
import Auth from './hoc/auth'

function App() {
  return (
    <Router>
      <div>
         
        {/*
          A <Switch> looks through all its children <Route>
          elements and renders the first one whose path
          matches the current URL. Use a <Switch> any time
          you have multiple routes, but you want only one
          of them to render at a time
        */}


{         /* 로그인한 유저는 로그인 페이지에 못들어감 */}
        <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>
    </Router> 
  );
} 

export default App;


 

 

 

Step 5) 기능 확인하기

 

이제 실행해서 결과를 확인해보자.

 

1) 로그인 전 LandingPage

 

 

2)로그인 페이지 

isAuth 가 false이다. 

 

 

3) 로그인 후 LandingPage

 

 

3) 로그아웃 후 로그인 페이지 

 

 

 

 

 

Step 6) 페이지 접근 관리하기

위에 있는 payload.isAuth는 로그인 된 상태이고 아래 있는 payload.isAuth는 로그아웃된 상태이다. 각각 true, false 이므로 이에 맞게 코드를 작성하자. 

 

 

auth.js를 다음과 같이 수정해서 로그인 한 사람은 로그인 페이지나 회원가입 페이지에 접근하지 못하도록 하는 등 페이지 접근을 관리한다. 

import React, {useEffect} from 'react';
import axios from 'axios';
import {useDispatch} from 'react-redux'
import {auth} from '../_actions/user_action'
export default function (SpecificComponent, option, adminRoute = null){


    function AuthenticationCheck(props){

        const dispatch = useDispatch();

        useEffect(() => {
                    //auth는 action이름 
            dispatch(auth()).then(response => { //backend에서 처리한 정보들이 response안에 다 들어있음 
                console.log(response)


                //로그인 하지 않은 상태인데 
                if(!response.payload.isAuth) {  //option이 true이면 즉 로그인 된 유저만 들어갈 수 있어야하면 
                    if(option){
                        props.history.push('/login')
                    }
                } else{ //로그인 된 상태인데 
                    if(adminRoute && !response.payload.isAdmin){
                        props.history.push('/')
                    }
                    else{
                        if(option===false){ //option이 false면 로그인한 유저는 출입 불가능함. 로그인 페이지 or 회원가입 페이지
                            props.history.push('/')
                        }
                    } 
                }

            })
            
        }, [])

        return (
            <SpecificComponent/>
        )
    }


    return AuthenticationCheck
}

 

 

 

 

 

 

참고) 

Page 이동에 사용되는 history는 react-router-dom을 이용해서 쓰고 있는 것임. 

withRouter 로

export default withRouter(LandingPage)

코드 맨 아래에 위와 같이 처리를 해줘야 오류가 안 난다. 

 

 

 

 

 

 

 

 

여기까지 리엑트 노드를 이용한 로그인, 로그아웃, 회원가입 페이지 구현을 해 보았다.

 

직접 코드를 작성하면서 웹사이트 구현을 위한 기초를 다질 수 있었고, 이를 통해 서버 및 클라이언트의 전체적인 동작 원리 및 흐름과 MongoDB 사용법 또한 익힐 수 있었다. 

728x90
728x90

출처 : 인프런_따라하며 배우는 노드, 리액트 시리즈 — 기본 강의

 

 

LandingPage.js

먼저 로그아웃 버튼을 만들어주고 그에 따라서 onClickHandler 함수를 아래와 같이 구현해준다. 

 import React, {useEffect} from 'react'
import axios from 'axios';

 function LandingPage(){//Functional Component 만들기
     
    
    useEffect(() => {
        axios.get('/api/hello')      //endpoint. getRequest를 server 즉 index.js로 보내질 것
        .then(response => {console.log(response)})   //server 에서 돌아온 response를 콘솔창에 출력해봄
    }, [])
    
    
    const onClickHandler = () => {
        axios.get('/api/users/logout')
        .then(response => {
            console.log(response.data)
        })
    }


    return(
         <div style={{
             display: 'flex', justifyContent: 'center', alignItems: 'center',
             width: '100%', height: '100vh'
         }}>
          
          <h2> 시작 페이지 </h2>
          

         <button onClick={onClickHandler}>
             로그아웃 
         </button>

         </div>
     )
 }

 export default LandingPage

 

 

그러면 아래의 index.js 에서 구현한 로그아웃 기능에 의해 로그아웃 버튼을 클릭하면 console 창에 success:true 가 뜰 것이다. 

 

 

 

 

console 창에 success:true가 뜬 것을 볼 수 있다. 

 

 

 

추가적으로 로그아웃했을 경우 로그인 창으로 페이지가 이동하도록 구현해보자.

 

위에서 구현한 onClickHandler를 아래와 같이 수정한다.

  const onClickHandler = () => {
        axios.get('/api/users/logout')
        .then(response => {
            //console.log(response.data)
            if(response.data.success){
                props.history.push("/login")
            }else{
                alert('로그아웃 하는데 실패 했습니다.')
            }
        })
    }

 

 

그리고 먼저 로그인을 한다. 

 

 

그 후 로그아웃 버튼을 눌러보자.

다시 로그인 페이지로 돌아온 것을 확인할 수 있다.

 

 

참고)

password의 저 한글자는 

const [PasswordsetPassword= React.useState("")

여기서 useState에 초기값을 넘겨줄 때 공백 한칸을 넘겨줘서 그런것이었다. ""로 공백 없이 넘겨주면 사라진다.

728x90
728x90

출처 : 인프런_따라하며 배우는 노드, 리액트 시리즈 — 기본 강의

 

Step 1) 회원가입 페이지 구현

 

client/src/components/views/RegisterPage/RegisterPage.js

 

import React, { useState } from 'react'
import {useDispatch} from 'react-redux';
import {loginUser} from '../../../_actions/user_action'
import {registerUser} from '../../../_actions/user_action'
import { withRouter} from 'react-router-dom'

function RegisterPage(props) {


    const dispatch = useDispatch();

    const [Email, setEmail] = React.useState(" ")
    const [Password, setPassword] = React.useState(" ")
    const [Name, setName] = React.useState(" ")
    const [ConfirmPassword, setConfirmPassword] = React.useState(" ")

    const onEmailHandler = (event) => {
        setEmail(event.currentTarget.value)
    }

    const onNameHandler = (event) => {
        setName(event.currentTarget.value)
    }

    const onPasswordHandler = (event) => {
        setPassword(event.currentTarget.value)
    }
    const onConfirmPasswordHandler = (event) => {
        setConfirmPassword(event.currentTarget.value)
    }


    const onSubmitHandler = (event) => {
        event.preventDefault(); //리프레시 방지-> 방지해야 이 아래 라인의 코드들 실행 가능 

        // console.log('Email', Email);
        // console.log('Password', Password);

        //비밀번호와 비밀번호 확인 같을띠 회원가입 되게 함
        if(Password !== ConfirmPassword){
            return alert('비밀번호와 비밀번호 확인은 같아야 합니다.')
        }   //여기서 걸리면 아래로 못감 



        let body={
            email: Email,
            password: Password,
            name: Name
        }


    //디스패치로 액션 취하기
    dispatch(registerUser(body))
    .then(response => {
        if(response.payload.success) {
            props.history.push('/login')
        } else {
            alert('Failed to sign up')
        }
        
    })


    
    }



    return (
        <div style={{
            display: 'flex', justifyContent: 'center', alignItems: 'center',
            width: '100%', height: '100vh'
        }}>
            
            
            <form style={{display: 'flex', flexDirection: 'column'}}
                onSubmit={onSubmitHandler}
            >

                <label>Email</label>
                <input type="email" value={Email} onChange={onEmailHandler}/>   
                
                <label>Name</label>
                <input type="text" value={Name} onChange={onNameHandler}/>


                <label>Password</label>
                <input type="password" value={Password} onChange={onPasswordHandler}/>
                
                <label>Confirm Password</label>
                <input type="password" value={ConfirmPassword} onChange={onConfirmPasswordHandler}/>

                <br />
                <button>
                    회원 가입 
                </button>
            </form>

        </div>
    )
}

//export default RegisterPage


export default withRouter(RegisterPage)

 

 

 

 

Step 2) Action 기능 구현

import Axios from 'axios';
import {
    LOGIN_USER, REGISTER_USER
} from './types';


export function loginUser(dataTosubmit) {

    const request = Axios.post('/api/users/login', dataTosubmit)        //서버에 리퀘스트 날리고 
        .then(response => response.data)                     //받은 데이터를 request에 저장


        return {                    //Action 했으니까 이제 Reducer로 보냄
            type: LOGIN_USER,
            payload: request
        }
    

}


export function registerUser(dataTosubmit) {

    const request = Axios.post('/api/users/register', dataTosubmit)        //서버에 리퀘스트 날리고 
        .then(response => response.data)                     //받은 데이터를 request에 저장


        return {                    //Action 했으니까 이제 Reducer로 보냄
            type: REGISTER_USER,
            payload: request
        }
    

}


 

 

 

Step 3) type 지정

//action의 타입들만 관리하는 파일
export const LOGIN_USER = "login_user";
export const REGISTER_USER = "register_user";

 

 

Step 4) Reducer 작성

 

user_reducer.js

import { LOGIN_USER, REGISTER_USER } from '../_actions/types';


export default function(state={}, action){  //state 는 이전 상태 

    switch(action.type){        //Action에는 여러 타입 존재함. 이 타입에 따라 다르게 반응하도록 작성
        case LOGIN_USER:
            return {...state, loginSuccess: action.payload}       //... : spread operator은 파라미터 state를 그대로 가져온 것으로 빈 상태를 의미 
            break;

        case REGISTER_USER:
            return { ... state, register: action.payload}
            break;

        default:
            return state;

    }

}

 

 

 

구현이 완료됐으니 회원가입을 해보자. 성공한 것을 볼 수 있다.

 

 

 

728x90
728x90

출처 : 인프런_따라하며 배우는 노드, 리액트 시리즈 — 기본 강의

 

Step 1) 시작 페이지 화면 구성

 

LandingPage.js 화면 구성하기 

div 태그 안에 style을 지정해주자.

import React, {useEffect} from 'react'
import axios from 'axios';

 function LandingPage(){//Functional Component 만들기
     
    
    useEffect(() => {
        axios.get('/api/hello')      //endpoint. getRequest를 server 즉 index.js로 보내질 것
        .then(response => {console.log(response)})   //server 에서 돌아온 response를 콘솔창에 출력해봄
    }, [])
    
    
    return(
         <div style={{
             display: 'flex', justifyContent: 'center', alignItems: 'center',
             width: '100%', height: '100vh'
         }}>
          
          <h2> 시작 페이지 </h2>
          


         </div>
     )
 }

 export default LandingPage

 

 

 

그럼 첫 시작 화면이 아래와 같이 뜬다.

 

 

 

 

Step 2) 로그인 페이지 작성

 

onChange에 함수를 지정해줌으로써 이메일이나 비밀번호를 입력할 때, 실시간으로 state가 변하게 해준다. 

 

 

//client/src/components/views/LoginPage/LoginPage.js


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

function LoginPage(props) {     //파라미터로 props 넣어줘야함! 로그인 완료된 후 처음 화면으로 돌아가게 하기 위함 

    const dispatch = useDispatch();

    const [Email, setEmail] = React.useState(" ")
    const [Password, setPassword] = React.useState(" ")

    const onEmailHandler = (event) => {
        setEmail(event.currentTarget.value)
    }
    
    const onPasswordHandler = (event) => {
        setPassword(event.currentTarget.value)
    }

    const onSubmitHandler = (event) => {
        event.preventDefault(); //리프레시 방지-> 방지해야 이 아래 라인의 코드들 실행 가능 

        // console.log('Email', Email);
        // console.log('Password', Password);


        let body={
            email: Email,
            password: Password
        }

        //디스패치로 액션 취하기
        dispatch(loginUser(body))
        .then(response => {
            if(response.payload.loginSuccess) {
                props.history.push('/')             //리액트에서 페이지 이동하기 위해서는 props.history.push() 이용.
               										// 로그인 완료된 후 처음 화면(루트 페이지-landingpage로)으로 돌악가게 하기 
            } else{
                alert(' Error')
            }
        })


        
    }

    return (
        <div style={{
            display: 'flex', justifyContent: 'center', alignItems: 'center',
            width: '100%', height: '100vh'
        }}>
            
            
            <form style={{display: 'flex', flexDirection: 'column'}}
                onSubmit={onSubmitHandler}
            >

                <label>Email</label>
                <input type="email" value={Email} onChange={onEmailHandler}/>   
                <label>Password</label>
                <input type="password" value={Password} onChange={onPasswordHandler}/>

                <br />
                <button>
                    Login
                </button>
            </form>

        </div>
    )
}

export default LoginPage

 

 

 

Step 3) Action 기능 구현 

Login버튼을 누르면 디스패치가 action을 수행할 수 있도록 기능을 구현한다. 그 기능들은 _action/user_action.js 를 새로 생성해서 아래와 같이 코드를 작성하자. 

- Server 에 보낼 때 Axios.post() 를 이용한다. 

//client/src/_actions/user_action.js

import Axios from 'axios';
import {
    LOGIN_USER
} from './types'
export function loginUser(dataTosubmit) {

    const request = Axios.post('/api/users/login', dataTosubmit)        //서버에 리퀘스트 날리고 
        .then(response => response.data)                     //받은 데이터를 request에 저장


        return {                    //Action 했으니까 이제 Reducer로 보냄
            type: LOGIN_USER,
            payload: request
        }
    

}

 

 

 

Step 4) Reducer 기능 구현 

 

이 다음은 Reducer 차례로, (previousState, action) 을 받아서 nextState를 리턴해주는 역할을 할것이다.

 

 

Action의 여러가지 type들을 쉽게 관리하기 위해 client/src/_actions/types.js파일을 새로 만들어서 아래와 같이 코드를 작성한다.

//action의 타입들만 관리하는 파일
export const LOGIN_USER = "login_user";
 

 

 

 

그리고 client/src/_reducers/user_reducer.js 파일도 아래와 같이 작성해준다.

import { LOGIN_USER } from '../_actions/types';


export default function(state={}, action){  //state 는 이전 상태 

    switch(action.type){        //Action에는 여러 타입 존재함. 이 타입에 따라 다르게 반응하도록 작성
        case LOGIN_USER:
            return {...state, loginSuccess: action.payload}       //... : spread operator은 파라미터 state를 그대로 가져온 것으로 빈 상태를 의미 
            break;

        default:
            return state;

    }

}

 

 

 

로그인 결과 화면 

 

MongoDB에 저장해놓은 데이터에 맞게 로그인 시도를 했는데 로그인에 실패했다.

 

터미널을 보니 아래와 같은 메세지가 떠서 구글링해보았다.

message: "Could not connect to any servers in your MongoDB Atlas cluster. One common reason is that you're trying to access the database from an IP that isn't whitelisted. Make sure your current IP address is on your Atlas cluster's IP whitelist:docs.atlas.mongodb.com/security-whitelist/",

 

 

아래 사이트를 참고해서 Network Access 문제를 해결했다. 처음 MongoDB에 접속했던 곳과 다른 곳에서 접속해서 IP 주소가 달라져서 접근 문제가 있는 것 같았다. 

 

stackoverflow.com/questions/62859081/error-could-not-connect-to-any-servers-in-your-mongodb-atlas-cluster

 

Error : “Could not connect to any servers in your MongoDB Atlas cluster”

i have a issue that i tried to fix it for a long time. i try to connect to mongo atlas cloud from nodejs with mongoose npm. now, its not my first time but i just can't find the answer my mongoose v...

stackoverflow.com

 

그러나 문제는 해결되지 않았고 포트번호를 이미 쓰고 있어서 오류가 난것이었다.

위와 같이 터미널에서 kill을 해주니 정상적으로 로그인이 된 것을 확인할 수 있었다.

 

 

로그인에 성공했고 시작페이지로 돌아갔다.

 

 

 

 

 

728x90
728x90

AntD 다운 (Css Framework)

$npm install antd --save

 

 

Redux 란?

: State 를 관리해주는 Tool

 

Component끼리 데이터를 주고받을 때 Props 이나 State를 이용한다.

 

<Props>

- Properties의 줄임말

- 컴포넌트끼리 서로 소통하는 방법

- 부모 컴포넌트에서 자식 컴포넌트로만 소통 가능

- 부모한테 받은 데이터는 자식 컴포넌트 안에서 수정 불가(immutable) -> 수정하려면 부모 컴포넌트에서 값을 새로 받아야함

 

<State>

- 컴포넌트 안에서 데이터를 교환하거나 전달할 때 사용됨(검색 창에 글을 입력할 때 글이 변하는 것)

- State 가 변하면 re-render 됨

 

 

 

- Redux Store에 저장해놓으면, 타고 올라가거나 내려가지 않아도, Store를 통해 효과적으로 State 관리 가능!! 

 

 


Redux의 데이터 Flow

- 단방향으로만 흐른다

 

 

 

<Action>

- 무엇이 일어났는지 설명하는 객체

 

<Reducer>

- 이전의 State와 Action 객체를 받은 후에 바뀐 State를 리턴해줌.

 

 

 

<Store>

- State를 감싸주는 역할

- Store안의 수많은 메소드들로 State를 관리한다.

 

 

 

 


 

 

 

Redux 설치

먼저 아래의 Dependency들을 다운 받아야한다.

1. redux

2. react-redux

3. redux-promise

4. redux-thunk

 

 

client디렉토리에 Dependency를 설치한다.

$npm install redux react-redux redux-promise redux-thunk --save

 

여기서 3번과 4번은 Middleware이다.  Middleware가 필요한 이유가 뭘까?

 

Store의 State를 변경하기 위해서는 Action을 Dispatch해야한다. 

그런데 Store에는 항상 객체 형식의 Action이 오는 것이 아니다. Promiese 또는 Function 형식으로 올 수도 있다. 이런 점을 해결하기 위해 Promise 형식으로 오는 것들은 reux-promise로, Function 형식으로 오는 것들은 redux-thunk로 처리하게 된다. 

 

 

 

 

 

Reduxdhk App을 연결해보자.

 

먼저 Redux DevTools를 다운한다.

 

chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd/related

 

Redux DevTools

Redux DevTools for debugging application's state changes.

chrome.google.com

 

 

 

그 다음 client/src/index.js 를 아래와같이 수정하자.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

import * as serviceWorker from './serviceWorker';
import { Provider} from 'react-redux';
import 'antd/dist/antd.css';

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


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


ReactDOM.render(
  //App을 Provider로 감싸서 Redux랑 어플리케이션이랑 연결시키는 것
  <Provider>   
    store={createStoreWithMiddleware(Reducer,
      
      window.__REDUX_DEVTOOLS_EXTENSION__ &&		//extension
      window.__REDUX_DEVTOOLS_EXTENSION__ ()
      
      )}
    <App />
  </Provider>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

 

 

client/src/_reducers/index.js

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

const rootReducer = combineReducers({
//user
})

export default rootReducer;

 

 

Store안에는 여러 State에 따른 다양한 Reducer들이 있다. 나눠진 Reducer들을 combineReducers를 이용해서 RootReducer에서 각 Reducer 들을 합쳐준다.

 

 


 

 

 

Class Component 와 Functional Component의 차이점

 

 

 

 

 

Class Component 

- Functional Component에서는 위 그림 중 어떠한 기능도 쓸 수 없다. 

- 그래서 대부분 Class Component를 썼었다.

- 그러다 React 16.8 버전에서 React Hook을 발표를 했고, 이 이후로부터는 Functional Component에서도 위의 기능들을 쓸 수 있게 됐다.

 

 

 

- Hook이 나오고 아래와 같이 같은 기능을 다른 방법으로 구현할 수 있게 됐다. 

 

- 요즘에는 대부분 Hook을 써서 개발을 한다.

 

 

728x90
728x90

 

 

Concurrently 를 이용해서 프론트, 백 서버를 한번에 켜보자.

 

$npm install concurrently --save

먼저 라이브러리를 설치한다.

 

 

 

 

 

 

 

Line 10 추가

 

이제 boiler-plate 디렉토리에서

$npm run dev

로 한번에 프론트, 백 서버를 켤 수 있다.

728x90
728x90

 

해결 방법

1) 개발자 도구 이용 
- 개발자들만 사용 ->제한적

2) JsonP 방식 
- 프론트엔드 부분만 고칠 수 있는 상황일 때
- 모든 request 를 get request로 바꿔서 보냄 

3)
- 백엔드, 프론트엔드 모두 다 제어가능 할 때

4) Proxy 사용

 

 

Proxy 

Proxy란?

 

 

 

Proxy로 CORS 에러를 해결해보자.

 

참고 사이트

create-react-app.dev/docs/proxying-api-requests-in-development/

 

Proxying API Requests in Development | Create React App

Note: this feature is available with react-scripts@0.2.3 and higher.

create-react-app.dev

 

 

 

http-proxy-middleware 를 설치한다. 

$ npm install http-proxy-middleware --save

 

 

그 후 src/setupProxy.js를 만들고 아래 코드를 추가한다.

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:5000',
      changeOrigin: true,
    })
  );
};

 

다시 백엔드를 켜고 프론트 엔드 서버를 켜서 확인해보자.

 

CORS 에러는 없어졌으나 오류가 여전하다...

 

 

해결 방법

: http-proxy-middleware를 다른 디렉토리에 설치해서 에러가 발생한거였다. 'boiler-plate/client'에 설치하면 에러가 해결된다!

 

728x90
728x90

 

그동안은 Client가 없어서 Postman으로 보냈으나, 이제는 Client쪽도 구현이 됐기 때문에 AXIOS를 통해서 Request를 보내보자.

axios 라이브러리를 다운받자.

$npm install axios --save

 

 

 

 

 

간단하게 테스트해보자. LandingPage.js를 아래와 같이 수정하고 여기서 보낸 getRequest를 받기 위해 server쪽인 index.js에도 두번째 코드를 추가해주자.

 

 import React, {useEffect} from 'react'
import axios from 'axios';

 function LandingPage(){//Functional Component 만들기
     
    
    useEffect(() => {
        axios.get('api/hello')      //endpoint. getRequest를 server 즉 index.js로 보내질 것
        .then(response => console.log(response.data))   //server 에서 돌아온 response를 콘솔창에 출력해봄
    }, [])
    
    
    return(
         <div>
          LandingPage
         </div>
     )
 }

 export default LandingPage

 

 

 

app.get('api/hello', (req,res) => {
    res.send("안녕하세요~")
})

 

 

이제 서버를 켜보자. 이때 client 디렉토리가 아닌 그 상위 디렉토리인 boler-plate 디렉토리에서 아래의 명령어를 실행시켜야한다.  또한 이전에 디렉토리 구조를 변경했기 때문에 package.json 파일에서

"backend""nodemon server/index.js" 로 수정해줘야 백엔드가 실행된다. 

$npm run backend

 

 

이제 프론트 서버도 켜보자. client 디렉토리로 이동 후 

$npm run start

 를 실행하자. 

 

 

결과

 

request가 잘 갔다 왔는지 확인을 위해 콘솔창을 켜보자. (fn + F12)

 

에러가 발생했다.

 

이유 : 사용자의 서버 포트는 5000번이고 클라이언트 포트는 3000번이기 때문
=> 클라이언트가 3000번으로 요청을 보냈으나 서버는 받지 못함 

 

그럼 5000번으로 요청을 보내보자.

useEffect(() => {
        axios.get('http://localhost;5000/api/hello')      //endpoint. getRequest를 server 즉 index.js로 보내질 것
        .then(response => console.log(response.data))   //server 에서 돌아온 response를 콘솔창에 출력해봄
    }, [])

 

 

CROS 에러

 

여전히 에러가 있다.

 

그 이유는 CORS 정책 때문이다. 만약 외부의 다른 클라이언트에서 요청을 보내면 보안상의 이슈가 있을 수 있기 때문에 CORS 정책이 있는것!
(CORS란? : Cross-Origin Resource Sharing의 약자로 서로 다른 Origin 사이에서 자원을 공유할 때 사용되는 정책)

 

 

 

다음 포스팅에서는 CORS 에러 해결방법에 대해 알아보자.

728x90

+ Recent posts