우아한 테크러닝 3기
✔ 우아한 테크러닝 3기: React & TypeScript 6회차
seungmin2
2020. 9. 20. 20:45
✌ 9월17일 (목) 우아한 테크러닝 3기 6회차 강의 정리
🚀 6회차 강의 목표
webpack
에 대한 흐름 이해하기- React & Typescript 살펴보기
- redux-saga 사용하기
🚀 Webpack 흐름 이해하기
- Webpack은 Webpack Cli라고 하는 도구가 존재한다.
- 단지
Hello world
를 찍기 위해서 필요한 설정들이 이렇게나 많이 필요하다 @babel/core
과 같은@
가 붙여져 있는 것들은 트랜스파일링 할때 사용된다. 일종의 플로그인이다.
webpack
은webpack.config.js
라는 이름을 갖는 설정 파일을 갖는다. 이름 변경될 수 있지만 관례적으로 사용된다.webpack
은webpack.config.js
읽어서 실행시켜주는데 실행시켜줄 때config
객체를 export하게 된다. 그 때webpack
은 받아서config
에 있는 내용대로 실행된다.
const config = {
...
}
module.exports = config;
- webpack은 기본적으로 node에서 실행되기 때문에
require
를 사용한다.
const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const { TsconfigPathsPlugin } = require("tsconfig-paths-webpack-plugin");
- webpack 공식문서를 참고하자.
🌈 Loader
Loader
는 리덕스의 미들웨어와 같은 역할을 한다고 생각하면 된다.- 첫번째 파일
entry
부터 읽어들이는 파일들을loader
한테 던저주고loader
가 역할을 수행하고 다시 리턴을 해주는 것을 반복한다. (미들웨어의 동작과 비슷)
const config = {
...
// entry
entry: {
main: ["core-js", `./src/index.tsx`],
},
...
};
- loader 설정은
module
속성 안에 작성되고rules
라는 배열안에 객체로 작성된다.
const config = {
// ...
module: {
rules: [
{
// 어떤 확장자를 가진 파일만 받을 껀지 정규표현식으로 작성할 수 있다.
test: /\.(ts|js)x?$/,
// 어떤 것을 포함시킬지
include: path.resolve("src"),
// 어떤 것을 제외 시킬지
exclude: /node_modules/,
use: {
// 위에서 찾은 설정 파일들을 babel-loader 한테 전달해준다.
loader: "babel-loader",
},
},
{
test: /\.(png|svg|jpg|gif)$/,
loader: "file-loader",
options: {
name: "[name].[ext]",
outputPath: "./images/",
},
},
],
},
// ...
};
🌈 Plugin
- Plugin은 loader보다 훨씬 복잡하다.
복잡한 이유는webpack
의 안쪽에 있는 Low-Level의 기능들을plugin
한테 다 노출을 시켜주고plugin
이 할 수 있게 한다.
그렇기 때문에 훨씬 더 많은 일을 할 수 있다.
const config = {
// ...
plugins: [
new webpack.SourceMapDevToolPlugin({}),
new CleanWebpackPlugin(),
// Html 플러그인으로 작성
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "public/index.html"),
}),
// 환경변수값을 주입하는 플러그인
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
}),
],
// ...
}
- 보통의 plugin은 loader의 프로세스가 다 끝나고 실행된 다음에 그 결과물을 가지고 plugin이 작업을 한다.
- 그렇기 때문에
loader
는 컨버팅이고plugin
은 후처리라고 생각하면 된다.
🌈 각각의 webpack 설정 파일들을 쪼개는 이유는??
babel
도 설정파일을 따로 생성해 사용한다. (.babelrc
)
애플리케이션을 만들때 관심사 분리라는 맥락으로 컴포넌트를 잘게 쪼개는데webpack
설정 파일들도 똑같다.plugins
하나에 하나의 일,loader
도 하나에 하나의 일을 하게 디자인하는게 유지보수에 좋다라고 되어있다.
그래서 종류가 굉장히 많다.
🌈 Babel?
- 3회차 때 진행했던 react 만들어보기에서 Babel에서
/* @jsx createElement */
처럼 사용해서 jsx의 태그 방식을 파싱해서 함수 호출 방식으로 트랜스파일링 해주는 것을 담당해주는 것이다.
.babelrc
{
"sourceType": "unambiguous",
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"useBuiltIns": "usage",
"corejs": {
"version": 3,
"proposals": true
}
}
],
// jsx의 태그 방식을 파싱해서 함수 호출 방식으로 트랜스파일링 해주는 것을 담당
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
"@loadable/babel-plugin",
"babel-plugin-styled-components",
[
"@babel/transform-runtime",
{
"corejs": 3
}
],
"@babel/plugin-proposal-optional-chaining",
["@babel/plugin-proposal-decorators", { "legacy": true }],
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-exponentiation-operator",
"@babel/proposal-object-rest-spread",
"@babel/plugin-transform-async-to-generator"
]
}
- 결국
Babel
안에plugin
이다.Babel
자체도 각종 언어 종류를 트랜스파일링하는 구조들을 또plugin
이라는 형식으로 가지고 있다. 그러니Babel
이 다해주는 것이 아니고Babel
안에 또plugin
들이 존재하는 구조이다.
📌 인프런 강의 듣고
Webpack
에 대해서 정리한 것입니다. 참고
📌 캡틴 판교님의 webpack 핸드북
📌 인프런 강의하신 김정환님의 블로그
🚀 React & Typescript 살펴보기
김민태님 CodeSandBox 참고 예제
redux
는 특정 프레임워크나 라이브러리에 종속되어있는 상태관리자가 아니다. 그래서vanilla.js
에서 사용도 가능하고 원한다면Vue.js
,Angular.js
에서도 사용이 가능하다.
import { createStore, applyMiddleware } from "redux";
- 그렇기 때문에
react
에서 직접 사용할려면react-redux
를 사용하면 된다. - 리액트의 Context API
를 이용해서 내부적으로 구현된
Provider`를 사용한다. <App />
를Provider
로 감싸주고store
를 주입시켜주면App
하위 어디서든 꺼내서 사용할 수 있다.
import { Provider } from "react-redux";
//...
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
reducer
상태 값을 나타낼 인터페이스이다.
// types
export interface StoreState { // typealias으로 변경해도 상관없다.
monitoring: boolean;
success: number;
failure: number;
}
// reducers
const initializeState: StoreState = {
monitoring: false,
success: 0,
failure: 0
};
export default (
state: StoreState = initializeState,
) => {
// ...
}
🌈 typesafe-actions
typesafe-actions
라이브러리의createAction
을 사용해서actions
을 생성할 수 있다.
import { createAction } from "typesafe-actions";
export const startMonitoring = createAction(
// action 이름(속성) 상수 선언이 필요없다.
// 타입 속성에 넣는다.
"@command/monitoring/start",
resolve => {
return () => resolve();
}
);
export const stopMonitoring = createAction(
"@command/monitoring/stop",
resolve => {
return () => resolve();
}
);
export const fetchSuccess = createAction("@fetch/success", resolve => {
return () => resolve();
});
export const fetchFailure = createAction("@fetch/failure", resolve => {
return () => resolve();
});
- 그래서
typesafe-actions
를 사용하면 위와 같이createAction
첫 번째 인자로 상수를 사용하지 않고 문자열로 넘길 수가 있다. 대신 상수역할을 해준다. - 위에서
createAction
의 두 번째 인자는 데이터가 존재하면payload
라는 속성으로 객체로 데이터를 넣어주고 어떤 타입인지를 감지하고 타이핑까지 해줘서 아래의reducer
에서 액션에서 어떤 속성인지 확인할 수 있다. typesafe-actions
사용하면 상수선언이 생략되고 type과 payload 만드는 한꺼번에 처리를 해줘서 편리하게 처리가 가능하다.typesafe-actions
과 비슷한 역할을 하는redux-toolkit
도 존재하며 리덕스에서 사용하라고 공식문서에도 나온다.
export default (
state: StoreState = initializeState,
action: ActionType<typeof Actions>
) => {
switch (action.type) {
case getType(Actions.fetchSuccess):
return {
...state,
success: state.success + Math.floor(Math.random() * (100 - 1) + 1)
};
case getType(Actions.fetchFailure):
return {
...state,
failure: state.failure + Math.floor(Math.random() * (2 - 0))
};
default:
console.log(action.type);
return Object.assign({}, state);
}
};
🌈 redux-saga
- 현재 미들웨어로
redux-saga
를 사용하고 있다.
// index.tsx
import * as React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import { StoreState } from "./types";
import reducer from "./reducers";
import createSagaMiddleware from "redux-saga";
import rootSaga from "./sagas";
import App from "./App";
const sagaMiddleware = createSagaMiddleware();
// sagaMiddleware
const store: StoreState = createStore(reducer, applyMiddleware(sagaMiddleware));
const rootElement: HTMLElement = document.getElementById("root");
// rootSaga 미들웨어 전달
sagaMiddleware.run(rootSaga);
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
redux-saga
는 비동기 처리를 위해서 제너레이터를 사용한다.
제너레이터와 비동기에 대한 설명은 우아한 테크러닝 4회차때 설명하셨다.
// sagas/index.ts
export default function* () {
yield fork(monitoringWorkflow);
}
redux-saga/effects
패키지 밑에 있는 헬퍼함수들이다.
// sagas/index.ts
import { fork, all, take, race, delay, put } from "redux-saga/effects";
redux-saga/effects
에서 불러오는fork, all, take, race, delay, put
들은 객체를 만든다.
//...
function* monitoringWorkflow() {
while (true) {
yield take(getType(Actions.startMonitoring));
let loop = true;
while (loop) {
yield all([
put({ type: getType(Actions.fetchSuccess) }),
put({ type: getType(Actions.fetchFailure) })
]);
const { stoped } = yield race({
waitting: delay(200),
stoped: take(getType(Actions.stopMonitoring))
});
if (stoped) {
loop = false;
}
}
}
}
export default function* () {
// monitoringWorkflow 함수를 전달
yield fork(monitoringWorkflow);
}
fork(monitoringWorkflow)
를console.log
를 해보면 객체가 만들어지는 것을 확인할 수 있다.
put
함수는 액션을dispatch
하는 것을redux-saga
에게 시키는 것이고all
을 사용하면 여러개의 액션을dispatch
할 수 있다.take
는 리덕스의 액션을 주고(문자열) 이 문자열에 액션이 들어오면 알려달라고next
를 던져준다.race
는 여러개의 객체로 전달해도 상관 없고, 그 안에 객체 중에 제일 먼저 돌아오는 상태 값을 넘긴다.delay
는 Promise객체이고 특정 시간동안 기달리고 다시 실행되게 된다.
const { stoped } = yield race({
waitting: delay(200),
stoped: take(getType(Actions.stopMonitoring))
});
🚀 6회차 후기
벌써 다음주면 마지막 주차이다..😥 마지막 시간은 치킨 뜯으면서 해보자는데 진짜일까? ㅋㅋㅋ
3주동안 정말 리액트를 까보면서 구현해보니까 리액트에 대해서 더 잘 파악할 수 있는 계기가 된거같다.
이번주는 진짜? 리액트와 typescript를 사용하면서 구조를 이해해봤다. 또한, redux-saga에 대해서도 다시한번 더 알게된 시간이였다.
그리고 redux-toolkit은 다음번에 꼭 사용해보고 싶다.🚀