728x90
1. JSX로 리액트 element 정의하기
- JSX는 javascript의 확장으로 HTML과 비슷한 구문을 사용해 리액트 element를 정의할 수 있게 해준다.
- JSX에서는 태그를 사용해 element의 type을 지정하고, 태그의 attribute는 프로퍼티를 표현하며, 여는 태그와 닫는 태그 사이에 element의 자식을 넣는다.
- JSX element에 다른 JSX element를 자식으로 추가할 수 있다.
// 번호가 붙지 않은 리스트에 대한 JSX
<ul>
<li>연어 500그램</li>
<li>잣 1컵</li>
<li>버터 상추 2컵</li>
<li>옐로 스쿼시 1개</li>
<li>올리브 오일 1/2컵</li>
<li>마늘 3쪽</li>
</ul>
// JSX에서는 단순히 클래스 이름을 사용해 component를 정의하면 된다.
<IngredientsList list={[...]}/>
- JSX에서 배열을 component로 넘길 때는 중괄호로 감싸야 한다. 이렇게 중괄호로 감싼 코드를 javascript 식이라고 한다. javascript 식에는 배열, 객체, 함수 등이 있다. 이러한 javascript 값을 포함시키려면 javascript 값 주변을 중괄호로 감싸야 한다.
- JSX에서는 다른 component의 자식으로 component를 추가할 수 있다.
- javascript에서는 class가 예약어이므로 class attribute 대신 className을 사용한다.
- javascript 식을 중괄호로 감싸면 중괄호 안의 식을 평가해서 결과값을 반환한다. 문자열이 아닌 다른 타입의 값은 모두 javascript 식 안에 넣어야 한다.
- JSX는 javascript이므로 javascript 함수 안에서 JSX를 직접 사용할 수 있다.
// JSX와 Array.map()을 사용하는 예제
<ul>
{this.props.ingredients.map((ingredient, i) =>
<li key={i}>{ingredient}</li>
)}
</ul>
- JSX는 깔끔하고 읽기 쉽지만 브라우저는 JSX를 해석할 수 없다. createElement나 factory를 사용해 모든 JSX를 변환해야 한다. 이러한 작업에 사용하는 도구가 바로 Babel이다.
2. Babel
- 어떤 브라우저도 JSX를 지원하지 않기 때문에 이러한 소스 코드를 브라우저가 해석할 수 있는 코드로 변환해줄 수단이 필요하다. 이런 변환 과정을 transpiling이라 하고 Babel이 이러한 일을 한다.
- Babel을 사용하는 방법은 많지만 가장 간단한 방법은 babel-standalone transpiler에 대한 링크를 HTML에 포함시키는 것이다. 그러면 Babel은 'text/babel'인 script 태그 안에 있는 모든 코드를 실행하기 전에 transpiling해준다.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSX를 사용한 리액트 예제</title>
</head>
<body>
<div id="react-container"></div>
<!-- React 라이브러리와 ReactDOM 라이브러리 -->
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
// JSX 코드를 여기에 넣거나 별도의 javascript 파일에 대한 링크를 script 태그에 넣을 것
</script>
</body>
</html>
3. 조리법을 JSX로 작성하기
// 조리법의 배열
const data = [
{
"name": "Baked Salmon",
"ingredient": [
{ "name": "연어", "amount": 500, "measurement": "그램" },
{ "name": "잣", "amount": 1, "measurement": "컵" },
{ "name": "버터 상추", "amout": 2, "measurement": "컵" },
{ "name": "옐로 스쿼시", "amout": 1, "measurement": "개" },
{ "name": "올리브 오일", "amout": 0.5, "measurement": "컵" },
{ "name": "마늘", "amout": 3, "measurement": "쪽" }
],
"steps": [
"오븐을 350도로 예열한다.",
"유리 베이킹 그릇에 올리브 오일을 두른다.",
"연어, 마늘, 잣을 그릇에 담는다.",
"오븐에서 15분간 익힌다.",
"옐로 스쿼시를 추가하고 다시 30분간 오븐에서 익힌다.",
"오븐에서 그릇을 꺼내서 15분간 식힌 다음 상추를 곁들여서 내놓는다."
],
},
{
"name": "생선 타코",
"ingredients": [
{ "name": "흰살 생선", "amount": 500, "measurement": "그램" },
{ "name": "치즈", "amout": 1, "measurement": "컵" },
{ "name": "아이스버그 상추", "amout": 2, "measurement": "컵" },
{ "name": "토마토", "amout": 2, "measurement": "개(큰 것)" },
{ "name": "또띠야", "amout": 3, "measurement": "개" },
],
"step": [
"생선을 그릴에 익힌다.",
"또띠야 3장 위에 생선을 얹는다.",
"또띠야에 얹은 생선 위에 상추, 토마토, 치즈를 얹는다."
]
}
]
- 이 조리법을 가지고 두 가지 component가 들어 있는 UI를 만들 수 있다. Menu component는 조리법의 목록을 표시하고 Recipe component는 각 조리법의 UI를 표현한다. DOM에 렌더링해야 하는 대상은 Menu component다. 데이터를 Menu component의 recipes 프로퍼티로 넘긴다.
// 조리법 앱의 구조
var data = { ... }
const Recipe = (props) => ( ... )
const Menu = (props) => { ... }
ReactDOM.render(
<Menu recipes={data} title="맛있는 조리법" />,
document.getElementById("react-container")
)
// Menu component를 JSX를 이용해 표현한 예제
const Menu = (props) =>
<article>
<header>
<h1>{props.title}</h1>
</header>
<div className="recipes">
{props.recipes.map((recipe, i) =>
<Recipe key={i} name={recipe.name} ingredients={recipe.ingredients} steps={recipe.steps}/>
)}
</div>
</article>
// 스프레드 연산자, 객체 구조 분해를 활용하면 더 간단히 표현 가능하다.
const Menu = ({title, recipes}) =>
<article>
<header>
<h1>{title}</h1>
</header>
<div className="recipes">
{recipes.map((recipe, i) =>
<Recipe key={i} {...recipe} />
)}
</div>
</article>
// Recipe component
const Recipe = ({ name, ingredients, steps }) =>
<section id={name.toLowerCase().replace(/ /g, "-")}>
<h1>{name}</h1>
<ul className="ingredients">
{ingredients.map((ingredient, i) =>
<li key={i}>{ingredient.name}</li>
)}
</ul>
<section className="instructions">
<h2>조리 절차</h2>
{steps.map((step, i) =>
<p key={i}>{step}</p>
)}
</section>
</section>
4. 웹팩
- 웹팩은 모듈 번들러로 여러 파일(javascript, LESS, CSS, JSX, ES6 등)을 받아서 하나의 파일로 묶어준다.
- 모듈을 하나로 묶음으로써 얻는 이익은 모듈성과 네트워크 성능이다.
- 모듈성은 소스 코드를 작업하기 쉽게 여러 부분 또는 모듈로 나눠서 다룰 수 있게 해준다.
- 의존 관계가 있는 여러 파일을 묶은 번들을 브라우저가 한 번만 읽어 들이기 때문에 네트워크 성능이 좋아진다.
- 웹팩은 transpiling 외에도 여러 가지 기능을 한다.
- 코드 분리 : 코드를 여러 덩어리(롤업, 레이어라고 한다)로 나눠서 필요할 때 각각을 로딩할 수 있다.
- 코드 축소 : 공백, 줄바꿈, 긴 변수 이름, 불필요한 코드 등을 없애서 파일 크기를 줄여준다.
- 특징 켜고 끄기 : 코드의 기능을 테스트해야 하는 경우 코드를 각각의 환경에 맞춰 보내준다.
- HMR(hot module replacement) : 소스 코드가 바뀌는지 감지해서 변경된 모듈만 즉시 갱신해준다.
- loader는 빌드 과정에서 코드를 변환하는 방식을 처리하는 기능이다.
- 브라우저가 이해할 수 없는 언어를 사용할 때 webpack.config.js에 필요한 loader를 지정해서 애플리케이션 코드를 브라우저가 이해할 수 있는 코드로 변환한다.
- 한 언어를 다른 언어로 transpiling하거나, 페이지의 스타일을 바꾸는 경우(css-loader는 .scss 확장자를 가진 파일을 찾아서 CSS 파일로 컴파일한다. 번들에 CSS 모듈을 포함시키고 싶을 때 css-loader를 사용할 수 있다.) 등 여러 경우에 사용된다.
- 웹팩을 이용해 위 조리법 예시를 다시 작성하면 다음과 같다.
// Instruction component
const Instructions = ({ title, steps }) =>
<section className="instructions">
<h2>{title}</h2>
{steps.map((step, i) =>
<p key={i}>{step}</p>
)}
</section>
export default Instructions
// Ingredient component
const Ingredient = ({ name, amount, measurement }) =>
<li>
<span className="name">{name}</span>
<span className="amount">{amount}</span>
<span className="measurement">{measurement}</span>
</li>
export default Ingredient
// IngredientsList
import Ingredient from './Ingredient'
const IngredientsList = ({ list }) =>
<ul className="ingredients">
{list.map((ingredient, i) =>
<Ingredient key={i} {...ingredient} />
)}
</ul>
export default IngredientsList
// Refactoring한 Recipe component
import IngredientsList from './IngredientsList'
import Instructions from './Instruction'
const Recipe = ({ name, ingredients, steps }) =>
<section id={name.toLowerCase().replace(/ /g, "-")}>
<h1>{name}</h1>
<IngredientsList list={ingredients} />
<Instructions title="조리 절차" steps={steps} />
</section>
export default Recipe
// 모듈로 바꿔 쓴 Menu component
import Recipe from './Recipe'
const Menu = ({ recipes }) =>
<article>
<header>
<h1>맛있는 조리법</h1>
</header>
<div className="recipes">
{ recipes.map((recipe, i) =>
<Recipe key={i} {...recipe} />)
}
</div>
</article>
export default Menu
// 모듈을 사용하게 작성한 index.js 파일
import React from 'react'
import { render } from 'react-dom'
import Menu from './components/Menu'
import data from './data/recipes'
window.React = React
render(
<Menu recipes={data}/>,
document.getElementById("react-container")
)
- window.React 프로퍼티의 값을 React로 만들면 브라우저 전체가 리액트 라이브러리를 볼 수 있다. 이렇게 하는 것은 React.createElement가 제대로 작동하게 만들기 위함이다.
5. 웹팩 설치 및 설정
- 웹팩으로 정적인 빌드 프로세스를 만들려면 몇몇 모듈을 설치해야 한다.
- npm을 사용해 필요한 모든 모듈을 설치하되, webpack 명령을 누구나 호출할 수 있게 -g 옵션을 줘서 글로벌 설치해야 한다.
// linux 사용자
sudo npm install -g webpack
// window 사용자
npm install -g webpack
- 웹팩은 Babel을 사용해 JSX와 ES6 코드를 모든 브라우저에서 사용할 수 있는 javascript 코드로 transpiling한다. Babel을 설치하면서 작업에 필요한 몇 가지 프리셋을 함께 설치해야 한다.
npm install babel-core babel-loader babel-preset-env babel-preset-react babel-preset-stage-0 --save-dev
- 예전에는 React와 ReactDOM에 대한 의존관계를 script 태그를 사용해 loading했었으나 이제는 웹팩이 이들을 번들에 넣도록 변경했다. 그래서 React와 ReactDOM에 대한 의존관계를 로컬 환경에 설치해야 한다.
npm install react react-dom --save
- 이와 같이 하면 react와 react-dom에 필요한 노드 모듈이 ./node_modules 폴더에 설치된다. 이로써 웹팩으로 정적 빌드 프로세스를 구축할 때 필요한 모듈을 모두 설치했다.
- 모듈화한 앱이 작동하게 만들려면 소스 코드를 어떻게 하나의 번들 파일로 만들 수 있는지 웹팩에 알려주어야 한다.
- 설정 파일을 사용해 그런 지시를 내릴 수 있다. 웹팩의 디폴트 설정 파일 이름은 webpack.config.js다.
- 웹팩은 import 문을 발견할 때마다 파일 시스템에서 해당 모듈을 찾아 번들에 포함시킨다.
- 참고로 ES6 import 문은 대부분의 브라우저에서 지원하지 않는데, 그럼에도 불구하고 ES6 import 문이 작동할 수 있는 이유는 Babel이 import를 require('모듈 경로')로 변환하기 때문이다.
- 웹팩이 번들을 빌드할 때 JSX를 순수 리액트 코드로 transpiling해야 하고, 또한 ES6 문법을 ES5로 변환해야 한다.
- 빌드 프로세스는 JSX를 리액트 element로 변환하고, ES6를 ES5로 변환한 뒤, 한 파일에 모든 모듈을 넣어 이루어진다.
- webpack.config.js 파일은 웹팩이 취해야 하는 동작을 기술하는 javascript literal 객체를 외부에 export하는 단순한 모듈에 불과하다. index.js 파일이 위치한 프로젝트의 루트 폴더에 이 파일을 만들어야 한다.
// webpack.config.js
module.exports = {
entry: "./src/index.js", // 클라이언트의 시작 파일 지정
output: {
path: "dist/assets",
filename: "bundle.js"
}, // 번들을 ./dist/assets/bundle.js라는 javascript 파일에 출력하라고 지정
module: {
rules: [ // 웹팩으로 적용할 수 있는 다양한 유형의 loader를 처리해야 하기 때문에 배열이다.
{
test: /\.js$/, // loader가 처리해야 하는 모듈의 파일 경로를 매치시켜주는 정규식
exclude: /(node_modules)/, // babel-loader를 node_modules 폴더에 npm으로 설치한 파일에는 적용하지 않는다.
loader: 'babel-loader',
query: {
presets: ['env', 'stage-0', 'react']
}
}
]
}
}
- 웹팩은 정적으로 실행된다. 보통 앱을 서버에 배포하기 전에 번들을 만든다. 웹팩을 전역 옵션으로 설치했기 때문에 명령줄에서 webpack을 실행할 수 있다.
- 번들을 만들면 dist 폴더에 index.html 파일을 넣어 리액트 Menu component를 마운트시킬 대상 div element를 찾을 수 있게 만든다.
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>리액트 조리법 앱</title>
</head>
<body>
<div id="react-container"></div>
<script src="assets/bundle.js"></script>
</body>
</html>
- 이 페이지는 bundle.js에 단 한 번 HTTP 요청을 보내서 필요한 모든 자원을 loading한다.
- 코드를 하나의 번들 파일로 만들면 브라우저에서 앱을 디버깅할 때 곤란해진다. 이 문제는 소스 맵을 제공하여 해결한다. 소스 맵은 번들과 원래 소스 파일을 연결해주는 파일이다.
- 웹팩에서는 webpack.config.js 파일을 약간 수정하면 소스 매핑을 추가할 수 있다.
// 소스 매핑을 추가한 webpack.config.js
module.exports = {
entry: "./src/index.js", // 클라이언트의 시작 파일 지정
output: {
path: "dist/assets",
filename: "bundle.js",
sourceMapFilename: 'bundle.map'
}, // 번들을 ./dist/assets/bundle.js라는 javascript 파일에 출력하라고 지정
devtool: '#source-map', // 웹팩이 소스 매핑을 사용하게 할 수 있다.
module: {
rules: [ // 웹팩으로 적용할 수 있는 다양한 유형의 loader를 처리해야 하기 때문에 배열이다.
{
test: /\.js$/, // loader가 처리해야 하는 모듈의 파일 경로를 매치시켜주는 정규식
exclude: /(node_modules)/, // babel-loader를 node_modules 폴더에 npm으로 설치한 파일에는 적용하지 않는다.
loader: 'babel-loader',
query: {
presets: ['env', 'stage-0', 'react']
}
}
]
}
}
- 소스 맵은 디버깅 시 원래 소스 코드를 사용할 수 있게 해준다.
- 브라우저의 개발자 도구에 있는 소스 탭에서 webpack://이라고 쓰인 폴더 안에 번들에 들어 있는 모든 소스 파일을 볼 수 있다.
- 브라우저 디버거의 단계별 실행 기능을 활용해 소스 파일을 디버깅할 수 있다.
- 축소(난독화) : 파일 크기를 줄이기 위해 공백을 줄이거나, 변수 이름을 한 글자로 바꾸거나, javascript 인터프리터가 결코 도달할 수 없는 코드를 제거하는 등의 일을 하는 것
- 난독화 플러그인을 사용하려면 웹팩을 로컬에 설치해야 한다.
npm install webpack --save-dev
- 웹팩 플러그인을 활용하는 단계를 빌드 프로세스에 추가한 결과는 다음과 같다.
// 난독화를 추가한 webpack.config.js
module.exports = {
entry: "./src/index.js", // 클라이언트의 시작 파일 지정
output: {
path: "dist/assets",
filename: "bundle.js",
sourceMapFilename: 'bundle.map'
}, // 번들을 ./dist/assets/bundle.js라는 javascript 파일에 출력하라고 지정
devtool: '#source-map', // 웹팩이 소스 매핑을 사용하게 할 수 있다.
module: {
rules: [ // 웹팩으로 적용할 수 있는 다양한 유형의 loader를 처리해야 하기 때문에 배열이다.
{
test: /\.js$/, // loader가 처리해야 하는 모듈의 파일 경로를 매치시켜주는 정규식
exclude: /(node_modules)/, // babel-loader를 node_modules 폴더에 npm으로 설치한 파일에는 적용하지 않는다.
loader: 'babel-loader',
query: {
presets: ['env', 'stage-0', 'react']
}
}
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
sourceMap: true, // 난독화하면 코드를 알아볼 수 없으므로 원본과 난독화 이후 코드를 연결해주는 소스 맵
warnings: false, // export한 번들에서 콘솔 경고 제거
mangle: true
})
]
}
- create-react-app은 자동으로 리액트 프로젝트를 생성해주는 명령줄 도구로 개발자가 직접 웹팩, Babel, ESLint 등 여러 두구의 설정을 손보지 않아도 빠르게 리액트 프로젝트를 시작할 수 있게 해준다.
// create-react-app 글로벌 설치
npm install -g create-react-app
// 설치 후 create-react-app 명령과 앱을 생성할 폴더 이름을 지정하면 앱을 만들 수 있다.
create-react-app my-react-project
// 앱을 설치한 폴더를 현재 디렉토리로 만든 후 입력하면 애플리케이션을 3000번 포트에서 실행한다.
npm start
yarn start
// 테스트를 실행한다.
npm test
yarn test
// transpiling과 축소를 거친 프로덕션에 사용할 수 있는 번들을 만든다.
npm run build
yarn build
출처 : learning react(2018)
728x90
'리액트' 카테고리의 다른 글
#6. Component 개선하기(1) - Component 생애주기(lifecycle) (0) | 2021.03.30 |
---|---|
#5. 프로퍼티, 상태, 컴포넌트 트리 (0) | 2021.03.29 |
#3. 순수 리액트 (0) | 2021.03.26 |
#2. 함수형 프로그래밍 (0) | 2021.03.25 |
#1. 최신 자바스크립트 (0) | 2021.03.23 |