리액트 심화반 2주차 - 1

46599 단어 ReactReact

22.04.17(일) ~ 18(월)
스파르타코딩클럽 리액트 심화반 - 2주차 과정 - 1

◎ Promise

  • 콜백 헬(멸망의 피라미드)
    • 꼬리에 꼬리를 무는 비동기 처리가 늘어나면 호출이 계속 중첩되고, 코드가 깊어지고, 관리는 어려워짐
    • 이런 콜백 헬이 발생하는 이유?
      • 비동기 처리 시에는 실행 완료를 기다리지 않고 바로 다음 작업을 실행
      • 비동기 처리 함수 내에서 처리 결과를 반환하는 걸로는 원하는 동작을 하지 않으니, 콜백 함수를 사용해 원하는 동작을 하게 하려고 콜백 함수 사용
      • 이 콜백 함수 내에서 또 다른 비동기 작업이 필요할 경우 중첩이 생기면서 콜백 헬이 생김
function async1('a', function (err, async2){
	if(err){
		errHandler(err);
	}else{
		...
		async2('b', function (err, async3){
			...
		}){
			...
		}
	}
});
  • Promise

    • 비동기 연산이 종료된 이후 결과를 알기 위해 사용하는 객체
    • 비동기 메서드를 마치 동기 메서드처럼 값을 반환할 수 있음 ( 비동기 처리 시점을 좀 더 명확하게 표현할 수 있음)
  • Promise 객체 만들기

    const promise = new Promise((resolve, reject) => {
        if(...){
            ...
            resolve("성공!");
        }else{
            ...
            reject("실패!");
        }
    });
    • 인자로는 (resolve, reject) => {} 이런 excutor 실행자(실행 함수)를 받는다.
    • 비동기 작업이 끝나고, 콜백 함수를 실행
  • Promise의 상태값

    • pending: 비동기 처리 수행 전(resolve, reject가 아직 호출되지 않음)
    • fulfilled: 수행 성공(resolve가 호출된 상태)
    • rejected: 수행 실패(reject가 호출된 상태)
    • settled: 성공 or 실패(resolve나 reject가 호출된 상태)
  • Promise의 후속처리 Method

    • Promise로 구현된 비동기 함수는 Promise 객체를 반환
    • {} 안에서는 비동기 처리 결과(성공 결과나 에러메시지)를 받아서 처리해야 한다.
    • .then : 첫 인자는 성공시 실행, 두번째 인자는 실패시 실행,
    let promise_success = new Promise((resolve, reject) => {
    setTimeout(() => resolve("완료!"), 1000);
    
    let promise_fail = new Promise((resolve, reject) => {
        setTimeout(() => reject(new Error("오류!")), 1000);
    });
    
      promise_success.then(result => {
        console.log(result); // 완료!
    }, error => {
        console.log(error); // 실행X
    });
    
      promise_fail.then(result => {
        console.log(result); // 실행X
    }, error => {
        console.log(error); // Error: 오류!
    });
  • Promise Chaining

    • Promise는 후속 처리 Method를 Chaining해서 여러개의 Promise를 연결 할 수 있다.
    new Promise((resolve, reject) => {
        setTimeout(() => resolve("promise 1"), 1000);
    }).then((result) => { // 후속 처리 메서드 하나를 쓰고,
        console.log(result); // promise 1
        return "promise 2";
    }).then((result) => { // 이렇게 연달아 then을 써서 이어주는 거예요.
        console.log(result);
        return "promise 3";
  • async

    • 함수 앞에 async를 붙여서 사용, 항상 Promise를 반환
    async function myFunc() {
        return "프라미스를 반환해요!"; // 프라미스가 아닌 걸 반환시
    }
    
    myFunc().then(result => {console.log(result)});
    // 프라미스를 반환해요!
    // Promise {<fulfilled>: undefined}
  • await

    • async 함수 안에서만 동작, Promise가 다 처리될 때까지 기다렸다가 결과 반환
      async function myFunc(){
        let promise = new Promise((resolve, reject) => {
            setTimeout(() => resolve("완료!"), 1000);
        });
    
        console.log(promise); // Promise {<pending>}
    
        let result = await promise;
        console.log(promise); // 1초 후, Promise {<fulfilled>: '완료!'}
        console.log(result); // 1초 후, 완료!
    }

◎ 토큰 기반 인증

  • 옛날 방법

    • 사용자의 로그인 상태를 서버에서 전부 가지고 있었음
    • 사용자가 많아지면 서버에 부하가 걸림 / 서버를 여러개 놓으면 로그인 상태 관리가 까다로움
    • 따라서 토큰 기반 인증 방법을 많이 사용한다.
  • OAuth2.0: 외부서비스의 인증 및 권한부여를 관리하는 프레임워크

    • 동작방식
      1. 클라이언트서버 사이에 인증(로그인)을 하면 서버가 access_token 을 줍니다.
      2. 클라이언트access_token을 이용해서 API 요청을 할 수 있어요.
      3. 서버는 API 요청을 받고, access_token을 가지고 권한이 있나 없나 확인해서 결과를 클라이언트에 보내줍니다.
    • 외부 서비스 엮어서 외부 서비스에서 접근 권한, 유저 정보들을 가져오게 할 수도 있다.
      1. 유저가 구글 로그인을 합니다.
        • 자원 소유자가 자원서버에 권한 요청을 한거죠!
      2. 구글은 로그인할 때 유저가 입력한 정보(아이디, 비밀번호 등)을 보고 클라이언트(우리 웹사이트!)에 접근 권한을 줍니다.
      3. 클라이언트는 이 권한을 가지고 Authorization server(권한 서버)access_token을 요청합니다.
      4. 클라이언트는 이 access_token을 가지고 구글에서 유저 정보를 가져올 수 있어요.
      5. 구글클라이언트가 보낸 access_token을 가지고 권한이 있나 없나 확인해서 결과클라이언트에 보내줍니다.
  • JWT: 데이터가 JSON 형태로 이루어져 있는 토큰

    • 생김새 : [header].[payload(내용)].[signature(서명)]
      • header: 토큰 타입과 암호화 방식 정보가 들어갑니다.
      • payload: 토큰에 담을 정보가 name: value 쌍으로 들어갑니다.
      • signature: 서명 정보입니다. secret key를 포함해서 header와 payload 정보가 암호화 되어 들어갑니다.
    • 동작 방식 : 토큰 기반 동작 방식대로 움직여요!
      • 유저가 로그인을 시도하면, 서버가 요청을 확인하고 secret key를 가지고 access_token을 발급합니다.
      • 클라이언트에 JWT를 전달하고
      • 클라이언트는 API 요청을 할 때 Authorization header에 JWT를 담아서 보냅니다.
      • 서버는 JWT의 서명을 확인하고 payload에서 정보를 확인해서 API 응답을 보냅니다.

◎ 웹 저장소

  • 크롬 개발자도구 → Application 탭 → 좌측 Storage 에서 확인가능
  • 쿠키(Cookie)
    • 클라이언트 로컬에 저장되는 key:value 형태의 저장소 (약 4KB)
    • 쿠키 만들기
    document.cookie = "MY_COOKIE=here;";
    • 쿠키 가져오기
    console.log(document.cookie); // 하나의 String 형태로 가져옴
    // 개별 쿠키 나누기
    document.cookie.split(";")
    • 쿠키 삭제 : 만료일을 이전으로 설정
    dateString = Date('2020-12-12').toUTCString()"
    document.cookie = "MY_COOKIE=here; expires=${dateString}";
  • 세션 스토리지(Session Stroage)
    • key:value 형태의 저장소, 세션 스토리지에 저장된 데이터는 브라우저를 닫으면 제거
    sessionStorage.setItem("MY_SESSION", "here"); // 설정
    sessionStorage.getItem("MY_SESSION"); // 값 가져오기
    sessionStorage.removeItem("MY_SESSION"); // 해당 값 삭제
    sessionStorage.clear(); // 세션 스토리지 전부 지우기
  • 로컬 스토리지(Local Stroage)
    • key:value 형태의 저장소, 로컬 스토리지는 따로 지워주지 않으면 계속 브라우저에 남아 있음 (중요한 정보를 넣어두면 매우 위험)
    localStorage.setItem("MY_LOCAL", "here"); // 설정
    localStorage.getItem("MY_LOCAL"); // 값 가져오기
    localStorage.removeItem("MY_LOCAL"); // 해당 값 삭제
    localStorage.clear(); // 로컬 스토리지 전부 지우기

◎ 리덕스(redux) 이용하기

  • 리덕스: 클라이언트에서 전역 데이터 관리를 용이하게 해줌

  • 리덕스(redux) 설치

# 리덕스와 리덕스 모듈 내에서 경로 이동까지 하게 해줄 히스토리, 라우터와 히스토리를 엮어줄 모듈 포함
yarn add redux react-redux redux-thunk redux-logger [email protected] [email protected]
# redux-actions : 액션 만들기 등을 자동화
# immer : 불변성 유지를 편하게 해줌
yarn add immer redux-actions
  • 모듈 만들기
// src > redux > modules > user.js
import { createAction, handleActions } from "redux-actions";
import { produce } from "immer";
import { setCookie, getCookie, deleteCookie } from "../../shared/Cookie";

// Action Type
const LOG_IN = "LOG_IN";
const LOG_OUT = "LOG_OUT";
const GET_USER = "GET_USER";

// action creators / Using redux-actions
const logIn = createAction(LOG_IN, (user) => ({ user }));
const logOut = createAction(LOG_OUT, (user) => ({ user }));
const getUser = createAction(GET_USER, (user) => ({ user }));

// initialState
const initialState = {
  user: null,
  is_login: false,
};

// Reducer / Using redux-actions immer
export default handleActions(
  {
    [LOG_IN]: (state, action) =>
      produce(state, (draft) => {
        setCookie("is_login", "success");
        draft.user = action.payload.user;
				draft.is_login = true;
      }),
		[LOG_OUT]: (state, action) =>
      produce(state, (draft) => {
        deleteCookie("is_login");
        draft.user = null;
				draft.is_login = false;
      }),
    [GET_USER]: (state, action) => produce(state, (draft) => {}),
  },
  initialState
);
  • Redux Store 만들기 (추후 기능 추가)
    1. import 하기
    2. rootReducer 준비
    3. middleware 준비 (redux-logger, redux devTools)
    4. middleware 묶기
    5. rootReducer + middleware로 스토어 만들기
      +history 전역에서 사용할 수 있게 주입
// src > redux > configureSrore.js
import { createStore, combineReducers, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import { createBrowserHistory } from "history";
import { connectRouter } from "connected-react-router";

import User from "./modules/user";

// History 만들기
export const history = createBrowserHistory();

// Reducer 묶기 + 만든 history를 Store와 묶기
const rootReducer = combineReducers({
    user: User,
    router: connectRouter(history),
});

// thunk 비동기 작업 처리
// withExtraArgument , thunk 에 다른 인수를 넘겨줌
const middlewares = [thunk.withExtraArgument({history:history})];

// 지금이 어느 환경인 지 알려줘요. (개발환경, 프로덕션(배포)환경 ...)
const env = process.env.NODE_ENV;
// 개발환경에서는 로거라는 걸 하나만 더 써볼게요. 
// console에 스토어 데이터에 뭐가 담기는지, 액션으로 인해 변한게 찍힘
if (env === "development") {
  const { logger } = require("redux-logger");
  middlewares.push(logger);
}

// redux devTools 설정
const composeEnhancers =
  typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
        // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
      })
    : compose;

// Middleware 묶기
const enhancer = composeEnhancers(
    applyMiddleware(...middlewares)
);

// 스토어 만들기
let store = (initialStore) => createStore(rootReducer, enhancer);

export default store();
  • Store 주입하기
// index.js
import store from "./redux/configureStore";
import { Provider } from "react-redux";

ReactDOM.render(
  <Provider store={store}> // Provider 세팅, store 주입
    <App />
  </Provider>,
  document.getElementById("root")
);
// App.js

import { ConnectedRouter } from "connected-react-router";
import { history } from "../redux/configureStore";
...
function App() {
  return (
    <React.Fragment>
      <Grid>
        <Header></Header>
        <ConnectedRouter history={history}> // ConnectedRouter로 변경, history 주입
          <Route path="/" exact component={PostList} />
          <Route path="/login" exact component={Login} />
          <Route path="/signup" exact component={Signup}/>
        </ConnectedRouter>
      </Grid>
    </React.Fragment>
  );
}
  • 액션 실행하기, state 값 가져오기
...
import {actionCreators as userActions} from "../redux/modules/user";
import { useDispatch, useSelector } from "react-redux";

const Login = (props) => {
	const dispatch = useDispatch();
    const login = () => {
		dispatch(userActions.login({user_name: "perl"})); // Action 실행
    }
    
    const is_login = useSelector((state) => state.user.is_login) // 값 가져오기
	...
}

좋은 웹페이지 즐겨찾기