-
✔ 우아한 테크러닝 3기: React & TypeScript 6회차우아한 테크러닝 3기 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은 다음번에 꼭 사용해보고 싶다.🚀'우아한 테크러닝 3기' 카테고리의 다른 글
✔ 우아한 테크러닝 3기: React & TypeScript 8회차(마지막 회차) (1) 2020.09.26 ✔ 우아한 테크러닝 3기: React & TypeScript 7회차 (0) 2020.09.23 ✔ 우아한 테크러닝 3기: React & TypeScript 5회차 (4) 2020.09.19 ✔ 우아한 테크러닝 3기: React & TypeScript 4회차 (0) 2020.09.11 ✔ 우아한 테크러닝 3기: React & TypeScript 3회차 (0) 2020.09.09