서버로부터 완전한 새로운 페이지를 불러오지 않고 페이지 갱신에 필요한 데이터만 받아 그 정보를 기준으로 현재 페이지를 업데이트함으로써 사용자와 소통하는 웹어플리케이션이나 웹사이트
기존방식과 SPA 방식의 차이
기존방식의 웹에서는 페이지 이동을 하고 서버에 요청을 하면 서버가 HTML 파일을 만들어서 클라이어트에 보내주고 브라우저가 HTML을 반영해서 보여주기 전 새로고침이 일어난다SPA에서는 페이지 이동시 서버에 AJAX로 요청하고 Server는 JSON만 전달해 준다
SPA 방식에서는 페이지 이동시 변경되는 부분만 JSON으로 받아온뒤 브라우저의 자바스크립트에서 받은 JSON을 토대로 DOM에 변경된 부분만 랜더링 해준다SPA는 HTML 랜더링을 서버가 하는게 아닌 클라이언트가 받은 json을 토대로 하기 때문에 CSR 방식으로 랜더링 한다고 말한다
즉,
기존엔 브라우저가 페이지를 보여주기 위해선 HTML파일로 된 페이지 전체를 불러와야 했다.
매번 모든 페이지를 불러오는 거에 따른 느린 반응성을 갖게 된다SPA는 서버로부터 완전히 새로운 페이지를 불러오는 것이 아닌, 화면을 업데이트하기 위해 필요한 데이터만 서버에서 전달 받아 브라우저에서 해당하는 부분만 업데이트하는 방식으로 빠른 반응성을 가진다
SPA 장점
- 전체페이지가 아니라 필요한 부분의 데이터만 받아서 화면을 업데이트하면 되기 때문에 사용자의 interaction에 빠르게 대응하고, 더 나은 유저경험을 제공한다
- 서버에서는 요청 받은 데이터만 넘겨주면되기 때문에 서버 과부하 문제가 현저하게 줄어든다
SPA 단점
- 자바스크립트 파일을 기다리는 시간으로 첫 화면 로딩 시간이 길다
SPA는 HTML파일은 거의 비여있고 자바스크립트를 이용하여 동적으로 HTML 요소를 생성하는 방식으로 자바스크립트가 모든일은 한다, 자바스크립트 파일이 커서 자바스크립트 파일을 기다리는 시간으로 첫 화면 로딩 시간이 길어진다- 검색 엔진 최적화에 좋지 않다
SPA는 HTML이 거의 비어있어서 검색 로봇이 충분한 자료를 수집하지 못한다 (검색 엔진은 HTML 파일에 있는 자료를 분석하는 방식으로 검색 기능을 구동한다 / 하지만 SPA에서도 검색 엔진 최적화에 대응할 수 있도록 검색 엔진이 발전하고 있어서 점차 이 단점은 사라지고 있는 추세이다)
AJAX (Asynchronous JavaScript + XML(AJAX))
Ajax는 빠르게 동작하는 동적인 웹 페이지를 만들기 위한 개발 기법의 하나다
Ajax는 웹 페이지 전체를 다시 로딩하지 않고도, 웹 페이지의 일부분만을 갱신할 수 있다
MPA (Multiple-Page Application)
사용자가 페이지를 요청할 때 마다 웹서버에서 요청한 UI와 필요한 HTML로 파싱해서 보여주는 방식
- 여러 개의 페이지로 구성된 Application
- MPA는 SSR(Server Side Application) 방식으로 랜더링 한다
새로운 페이지를 요청할 때마다 서버에서 랜더링된 정적 리소스(HTML, CSS, JS)가 다운로드 된다
페이지 이동하거나 새로고침하면 전체 페이지를 다시 랜더링 한다
MPA의 장점
SEO 관점에서 유리하다
MPA는 완정된 형태의 HTML 파일을 서버로부터 전달 받는다, 검색엔진이 페이지를 크롤링하기에 적합하다첫 로딩이 매우 짧다
서버에서 이미 랜더링해 가져오기 때문에 첫 로딩이 빠르다
(클라이언트가 JS 파일을 모두 다운로드하고 적용하기 전 까지는 각각의 기능은 동작하지 않는다)MPA의 단점
새로운 페이지로 이동하면 깜빡인다
매 페이지 요청마다 리로딩(새로고침) 발생페이지 이동시 불필요한 템플릿도 중복해서 로딩 (성능)
서버 랜더링에 따른 부하
merge는 branch를 통합하는 것, rebase는 branch의 base를 옮긴다는 개념
ex
B 지점을 base로 가진 branch가 D, E 커밋을 진행 한다
C 지점으로 base를 이동하기 위해 branch에서 C 지점으로 rebase를 한다
C 지점으로 rebase 되면 기존 D, E 커밋은 새롭게 정렬되어 C 지점 이후로 변경된다
Redux-thunk와 Redux-saga는 둘 다 Redux의 미들웨어 라이브러리이다
Redux 미들웨어는 dispatch()메소드를 통해 store로 가는 액션을 가로채는 코드
리덕스를 사용하는 어플리케이션에서 비동기 작업을 처리 할 때 가장 기본적인 방법으로 redux-thunk 미들웨어를 사용한다
redux-thunk를 사용하여 비동기 작업을 관리하는건 매우 직관적이고 간단하다
특정 작업을 나중에 하도록 미루기 위해서 함수형태로 감싼 것
ex
const x = 1 + 2;
위 코드는 1+2 연산이 바로 실행된다
const foo = () => 1 + 2;
위 코드는 foo() 함수가 호출되어야만 1 + 2 연산이 실행된다
redux-thunk 미들웨어는 객체 대신 함수를 생성하는 액션 생성함수를 작성 할 수 있게 해준다
redux는 기본적으로 액션 객체를 디스패치 한다
일반 액션 생성자
const actionCreator = (payload) => ({ action: 'ACTION', payload });
위와 같이 파라미터를 가지고 액션 객체를 생성하는 작업만 한다
만약 특정 액션이 몇초뒤에 실행되게 하거나, 현재 상태에 따라 액션이 무시되게 하려면 위와 같은 일반 액션 생성자로는 할 수 없다, 하지만 redux-thunk는 가능하다
1초뒤에 액션이 디스패치되게하는 코드
const INCREMENT_COUNTER = 'INCREMENT_COUNTER'; function increment() { return { type: INCREMENT_COUNTER }; } function incrementAsync() { return dispatch => { // dispatch를 파라미터로 가지는 함수를 리턴 setTimeout(() => { // 1초뒤 dispatch 실행 dispatch(increment()); }, 1000); }; }
위와 같이 코드를 짜고
store.dispatch(incrementAsync());
를 하면 INCREMENT_COUNTER 액션이 1초뒤에 디스패치 된다
액션을 디스패치 하거나 무시하는 코드
function incrementIfOdd() { return (dispatch, getState) => { const { counter } = getState(); if(counter % 2 === 0) { return; } dispatch(increment()); }; }
위와 같이 리턴하는 함수에 dispatch, getState를 파라미터로 받게 하면 스토어의 상태에도 접근 할 수 있다
현재의 스토어 상태의 값에 따라 액션이 dispatch 될 지 무시될지 정해줄 수 있다
redux-thunk는 일반 액션 생선자를 통제, 제어할 수 있게 해준다
보통 액션생성자는 그냥 하나의 액션 객체를 생성 할 뿐이지만,
redux-thunk를 통해 만든 액션 생성자는 그 내부에서 여러가지 작업을 할 수 있다
예를들어 네트워크 요청도, 액션을 여러번 디스패치 할 수도 있다
redux-thunk 미들웨어에서, 전달받은 액션이 함수 형태 일 때, 그 함수에 dispatch 와 getState 를 넣어서 실행해준다
예제 코드
function createThunkMiddleWare(extraArgument) { return ({ dispatch, getState }) => next => action => { if(typeof action === 'function') { retrun action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleWare(); thunk.withExtraArgument = createThunkMiddleWare; export default thunk;
// 이해가 가질 않는다 ;
설치
$ yarn add redux-thunk
스토어에 미들웨어 적용 // src/store.js
import { createStore, applyMiddleware } from 'redux'; import modules from './modules'; import { createLogger } from 'redux-logger'; import ReduxThunk from 'redux-thunk'; /* 로그 미들웨어를 생성 할 때 설정을 커스터마이징 할 수 있습니다. https://github.com/evgenyrodionov/redux-logger#options */ const logger = createLogger(); const store = createStore(modules, applyMiddleware(logger, ReduxThunk)) export default store;
일반 redux store // src/store.js
import { compose, createStore, applyMiddleware } from "redux"; import rootReducer from '../reducers/index'; import thunk from "redux-thunk"; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose; const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk))); export default store;
src/modunles/counter.js
import { handleActions, createAction } from 'redux-actions'; const INCREMENT = 'INCREMENT'; const DECREMENT = 'DECREMENT'; export const increment = createAction(INCREMENT); export const decrement = createAction(DECREMENT); export const incrementAsync = () => dispatch => { // 1초 뒤 액션 디스패치 setTimeout( () => { dispatch(increment()) }, 1000 ); } export const decrementAsync = () => dispatch => { // 1초 뒤 액션 디스패치 setTimeout( () => { dispatch(decrement()) }, 1000 ); } export default handleActions({ [INCREMENT]: (state, action) => state + 1, [DECREMENT]: (state, action) => state - 1 }, 0);
App 컴포넌트에서 increment -> incrementAsync, decrement -> decrementAsync 로 치환
import React, { Component } from 'react'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import * as counterActions from './modules/counter'; class App extends Component { render() { const { CounterActions, number } = this.props; return ( <div> <h1>{number}</h1> <button onClick={CounterActions.incrementAsync}>+</button> <button onClick={CounterActions.decrementAsync}>-</button> </div> ); } } export default connect( (state) => ({ number: state.counter }), (dispatch) => ({ CounterActions: bindActionCreators(counterActions, dispatch) }) )(App);
추가 내용
redux-actions
yarn add redux-actions
- 액션 생성 함수를 더 짧은 코드로 작성할 수 있게 해준다
- 리듀서를 작성할 때 switch문이 아닌 handleActions라는 함수를 사용할 수 있게 해준다
createAction
- 액션 생성 함수를 만들어주는 함수이다
- 직접 객체를 만들 필요 없어 훨씬 간단하다
// createAction을 사용하지 않을 때 const CHANGE_USER = 'user/CHANGE_USER'; // 액션 생성 함수 export const change_user = user => ({type: CHANGE_USER, user});
// createAction을 사용할 때 import { createAction } from 'redux-actions'; const CHANGE_USER = 'user/CHANGE_USER'; // 액션 생성 함수 export const change_user = createAction(CHANGE_USER, user => user);
handleActions
handleActions로 리듀서를 더 간단하게 작성할 수 있다
// handleActions를 사용하지 않을 때 const reducer = (state = initState, action) => { switch (action.type) { case CHANGE_USER: return { ...state, user: action.user } } }
// handleActions를 사용할 때 import { handleActions } from 'redux-actions'; const reducer = handleActions({ [CHANGE_USER]: (state, action) => ({...state, user: action.user}) });
redux-saga는 비동기작업처럼 리듀서에서 처리하면 안되는 순수하지 않은 작업들을 하기위해 사용한다
redux-thunk의 경우 함수를 dispatch 해주었고, redux-promise-middleware 나 redux-pender에선 promise가 들어있는 액션을 dispatch 해주었다면,
redux-saga에서는 일반 액션을 dispatch 하게 된다
redux-saga는 특정 액션이 발생하면 이를 모니터링 하여 이에 기반하여 추가적인 작업을 하도록 설계한다
redux-saga에서는 Generator를 사용하여 function* 같은 문법을 사용한다
yarn add redux-saga
redux-saga 미들웨어 적용 //src/store.js
import { createStore, applyMiddleware } from 'redux'; import { createLogger } from 'redux-logger'; import createSagaMiddleware from 'redux-saga'; import modules from './modules'; const logger = createLogger(); const sagaMiddleware = createSagaMiddleware(); const store = createStore(modules, applyMiddleware(logger, sagaMiddleware)); export default store;
일반 redux store // src/store.js
import { compose, createStore, applyMiddleware } from "redux"; import rootReducer from '../reducers/index'; import thunk from "redux-thunk"; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose; const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk))); export default store;
즉,
어려운 개념으로 추가 정리 필요
Thunks는 action에 응답을 줄 수 없다
반면 Redux-Saga는 스토어를 구독하고 특정 액션이 전달 될 때 saga를 실행하거나 계속할 수 있다
redux-saga
redux-saga로 비동기처리와 분투하다
스타일 적용 우선순위
ex
<html> <head> <meta charset="UTF-8"> <style> li{color:blue;} #apple{color:red;} .greenapple{color:green;} </style> </head> <body> <ul> <li>포도</li> <li id ="apple" class ="greenapple" style=" color:yellow">사과</li> <li>수박</li> </ul> </html>
id(빨간색) / class(초록색) / style(노란색) / li(파란색)
우선순위 : style 선택자 > id 선택자 > class선택자 > li(태그)선택자
태그 선택자의 경우 다른 태그인<ul> / <p> / <div>
... 등 여러 태그를 의미한다
Dumb component - 부모에서 내려온 props를 받아쓰는 컴포넌트
Smart component / Container - 리덕스와 소통하면서 앱의 상태(리덕스 state)를 제어하는 컴포넌트
리덕스를 사용할 때에는 Container와 Component를 구분해주는 게 좋다
Container는 앱의 상태를 관리하기 때문에 앱의 상태가 자주 바뀔수록 그에 따라 빈번하게 업데이트가 일어난다
필요없는 부분에 업데이트가 일어나지 않게 하려면 Container과 Component를 구분해주어야한다
react는 컴포넌트 단위로 업데이트한다
Container가 업데이트되어도 그 아래 Component와 상관이 없다면 업데이트가 일어나지않는다
프로젝트 과정에서 jwt token은 모두 쿠키에 저장
장점
단점
장점
단점
funtion*