따라서 전역상태관리, 라우팅, 빌드 시스템 등을 각 개발자가 직접 구축해야한다.
create-react-app
이라는 툴을 제공해서 어느정도 이 단점을 보완해준다.자동으로 UI를 업데이트 해준다는 장점!
UI = render(state) 라고 함축적으로 표현할 수 있다.
render
함수는 순수함수로 작성하는 것이 좋다.
- 이렇게 코드에서 순수함수와 불변변수로 관리하게 되면 복잡도가 낮아지고 버그 발생 확률도 줄어든다.
- 또한 리액트 내에서 이 조건을 따른다면 렌더링 성능을 크게 향상 시킬 수 있다.
function LikeButton() {
const [liked,setLiked] = React.useState(false); // 기본값은 false
const text = liked ? '좋아요 취소' : '좋아요';
// createElement는 리액트 요소를 반환함
return React.createElement(
'button',
{ onClick:()=>setLiked(!liked) },
text,
)
// 문자열로 입력하면 html로 해당하는 태그가 만들어진다.
// likeButton 이라는 컴포넌트가 완성됨
}
// div안에 렌더링을 하기위한 작업
const domContainer1 = document.getElementById('root1');
// 렌더링 부분
ReactDOM.render(React.createElement(LikeButton),domContainer1)
// 컴포넌트를 렌더링할때도 마찬가지로 리액트 요소로 만들어준다.
React.createElement(
'button',
속성 값,
// 두번째요소는 속성값, 우리는 onclick이라는 이벤트 핸들러가 표현
children,
// 버튼의 children으로 텍스트를 출력
)
useState
는 컴포넌트의 상태값을 추가할때 사용한다.react.development.js
가 실행될때 전역변수로 노출됨ReactDom
도 마찬가지로 react-dom.development.js
이 실행될때 전역변수로 노출이 된다.createElement
를 이용해 다음과 같은 구조의 엘리먼트를 생성하고 싶다면?<div>
<p>hello</p>
<p>world</p>
</div>
const helloEl = React.createElement(
'div',
null,
React.createElement('p',null,'hello'),
React.createElement('p',null,'world'),
)
ReactDOM.render(helloEl,domContainer);
html을 다음과 같이 마크업 하고
<h3>root1</h3>
<div id="root1"></div>
<h3>root2</h3>
<div id="root2"></div>
<h3>root3</h3>
<div id="root3"></div>
// 루트1
const domContainer1 = document.getElementById('root1');
ReactDOM.render(React.createElement(LikeButton),domContainer1);
// 루트2
const domContainer2 = document.getElementById('root2');
ReactDOM.render(React.createElement(LikeButton),domContainer2);
// 루트3
const domContainer3 = document.getElementById('root3');
ReactDOM.render(React.createElement(LikeButton),domContainer3);
아까와는 모습이 조금 다르지만 얼마든지 리액트 안에서도 수정할 수 있다는 모습을 보여준다.
// 좋아요 버튼
function LikeButton() {
const [liked,setLiked] = React.useState(false);
const text = liked ? '좋아요 취소' : '좋아요';
// createElement는 리액트 요소를 반환함
return React.createElement(
'button',
{ onClick:()=> setLiked(!liked) },
text,
)
// 브라우저에 렌더링
const domContainer = document.getElementById('root');
ReactDOM.render(
React.createElement('div',
null,
React.createElement(LikeButton),
React.createElement(LikeButton),
React.createElement(LikeButton)
),domContainer
)
jsx
의 문법을 createElement
함수를 호출하는 코드로 변환하기 위해서 바벨을 사용한다.function LikeButton() {
const [liked,setLiked] = React.useState(false);
const text = liked ? '좋아요 취소' : '좋아요';
return <button onClick={() => setLiked(!liked)}>{text}</button> // jsx 문법
}
function Container() {
const [count,setCount] = React.useState(0);
// jsx 문법
return (
<div>
<LikeButton></LikeButton>
<div>
<span>현재 카운트:</span>
<span style={{ marginRight : 10 }}>{ count }</span>
<button onClick={()=> setCount(count+1)}>증가</button>
<button onClick={()=> setCount(count-1)}>감소</button>
</div>
</div>
)
const domContainer = document.getElementById('root');
ReactDOM.render(React.createElement(Container),domContainer)
jsx
문법으로 변환했을때 다음과 같은 SyntaxError
가 발생
Uncaught SyntaxError: Unexpected token '<'
jsx
문법은 자바스크립트 표준이 아니기 때문에 브라우저에서 실행하면 에러가 남바벨
을 이용해서 createElement
코드로 변환해줘야한다.npm
으로 패키지를 관리할때는 package.json
이 필요하다.
package.json
설치$ npm init -y
바벨
설치$ npm install @babel/core @babel/cli @babel/preset-react
@babel/core
는 바벨의 핵심 기능을 가지고 있는 패키지@babel/cli
는 cli
에서 사용할 바이너리가 들어있다.@babel/preset-react
는 리액트를 위한 플러그인 여러개를 모아놓은것프리셋
과 플러그인
이라는 개념이 있다.플러그인
: 하나의 변환하는 기능프리셋
: 여러개의 플러그인을 모아놓은 것이 프리셋, 특정 목적을 위해 하나의 프리셋으로 묶어주는 것컴파일을 통해 기존에 있던 위치에 simple.js
파일이 만들어지게 할것이다.
$ npx babel --watch src --out-dir . --presets @babel/preset-react
@babel/preset-react
을 사용함컴파일이 완료된 것을 확인
function Container() {
// ...생략
<span style={{ marginRight : 10 , color:'red'}}>{ count }</span>
}
스타일에 color:red
속성을 추가해줬더니
컴파일이 실시간으로 완료된 것을 터미널로 확인되고
컴파일본에 스타일 속성이 추가된 것이 확인된다.
./node_moduls/.bin
를 보면 babel
이 보인다..bin
폴더는 바이너리 파일만 모아놓은 폴더$ npx babel
.bin
폴더에 있는 바벨 바이너리를 실행시켜준다.가장 큰 이유는 모듈 시스템 사용
- 모듈 시스템은
ESM
과commonJS
가 있다.
-ESM
ES6에 추가
-commonJS
노드에서 많이 사용하는 시스템
모듈 시스템이란?
- export
와 import
를 사용한 코드
- ESM문법
모듈 시스템 사용법
- default
는 괄호 없이 사용
- 중괄호로 가져와서 as
를 이용해 이름 변경
기존 코드 사용시 문제점
- 자바스크립트가 동적으로 변하면서 자바스크립트 파일이 많아짐
- 글로벌 변수 충돌 가능성
- 외부 라이브러리 사용시
만약 웹팩을 사용한다면 변수 이름 충돌 같은 문제를 빌드 단계에서 잡아낼 수 있다.
외부라이브러리는npm
으로 관리할 수 있고, 그것들이 번들링 단계에서 하나의 파일로 합쳐지면서 앞서 말한 여러가지 문제들이 해결된다.
ESM
을 지원ESM
사용해서 서비스commonJS
로 작성됐다는 것commonJS
는 처리를 못하기 때문에 웹팩을 사용해야 한다.<script type="module" src="index.js">
type="module"
로 줄 경우 ESM
으로 동작index.js
로 연결된 a,b,c.js 코드들이 있고 이 파일들을 가지고 실습함.serve 패키지 사용해서 연결 (직접 브라우저 연결시 에러 발생 가능성있기 때문)
$ npx serve
index.js
이 코드를 웹팩을 이용해서 하나의 파일로 번들링 할 것임
$ npm install webpack webpack-cli react react-dom
webpack
webpack-cli
react
,react-dom
리액트 패키지다음은 웹팩을 실행
$ npx webpack
dist
폴더가 생성되고 그안에 번들링된 파일이 생긴다.
웹팩
을 사용하게 되면서 즉모듈시스템
을 사용할 수 있게 되면서 npm으로 인스톨해서 사용할 수 있게 된 것이다.- 그렇기때문에 index.js에서 가져다 쓰는 파일이 모두 하나의 파일로 압축되어 사용할 수 있다.
create-react-app
은 리액트의 개발환경을 구축해주는 도구예를들어,
- 빌드시스템을 구축하기 위해서 웹팩이나 바벨을 사용해야함
- 테스트환경 - jest 테스트 프레임워크 사용
- 코딩 컨벤션을 정하고 자동으로 체크하기 위해서 eslint를 사용
- 오래된 브라우저 지원하기 위해 polyfill
- HMR 코드릉 수정하면 바로바로 화면에 적요이 되는 기능 -> 개발 생산성 높아짐
- css 후처리 , scss 를 사용하면 css 컴파일 과정이 필요
이러한 환경들을
create-react-app
에서 자동으로 구축해서 제공해준다.
next.js
와 cra
차이cra
서버사이드 렌더링을 지원xnext.js
cra
단점은 빌드시스템이나 eslint
거의 변경할 수 없음Next.js
바벨 웹팩 estlint 등의 설정을 변경할 수 있음cra
로 개발하는게 좋음, 쉽고 빠르다는 장점cra
설치$ npx create-react-app cra-test
react-scripts
package.json
파일에서 react-scripts
라는 명령어를 이용하고 있는데 create-react-app이 버전업이 될때는 대부분 react-scripts
의 버전이 올라가는 것이다.react-scripts
패키지의 버전만 올려주면 된다.cra
의 설정을 변경할 수 없다고 했는데, 굳이 변경하고 싶다면 react-scripts
를 fork
한 뒤 수정해서 사용하면 된다."browserslist": {
"production": [
">0.2%",
],
browserslist
브라우저 지원범위에 관한 내용polyfill
이 어떻게 적용될지 여부를 나타냄0.2%
이상의 점유율을 가진 브라우저를 대상으로 하겠다는 뜻이다.$ npm start
HMR
기능 덕분에 코드를 수정하면 바로바로 적용됨
- Hot Module Replacement
은 응용프로그램 실행 중에 추가 또는 제거된 모듈들을 페이지 리로드 없이 교체하는 기능
이렇게 HMR
이 동작하는 것은 npm start
로 실행했을때 로컬에 서버를 띄워서 브라우저와 통신을 하기 때문에 가능한 것
npm start
배포시 x 개발모드만, 개발모드기때문에 성능 최적화가 안되어있음
배포할 때는 반드시 빌드명령어
StrictMode
- 개발환경에서 동작하는데 리액트에서 잘못사용한 것들에 대해서 빨리 잡아내기 위해 사용한다.
css나 이미지를 자바스크립트에서 import
하는 방식으로 사용하는게 좋다.
- 이미지 경로에 해시값이 들어가기 때문에 브라우저 캐싱을 효율적으로 사용할 수 있다.
이미지뿐만아니라 데이터를 다룰때 json
파일을 사용할 수 있다.
// data.json
{
"name":"lee",
"age":12
}
// app.js
import data from './data.json'
function App() {
console.log({data});
}
import
해와서 사용시 장점은 데이터를 필요할때만 불러서 사용해야하는 동작에 유용하게 작용한다는 장점이 있다.개발할때 https
로 실행하고 싶으면?
$ HTTPS=true npm start
window에서
$ (set HTTPS=true && npm start)
빌드명령어
$ npm run build
$ npx serve -s build
$ npm test
App.test.js
를 App.spec.js
로 바꾸더라도 test가 인식함__tests__
이름의 폴더를 만들면 폴더 아래의 파일은 모두 test파일이 된다. react-scripts
를 사용하지 않고 모든 설정 파일을 추출하는 명령어cra
기반으로 개발환경을 직접 구축하고 싶을때 사용cra
의 기능이 추가됐을때 단순히 react-scripts
버전만 올리면 되는데 추출을하면 수동으로 설정파일을 수정해야함padStart()
를 사용하고 싶을때 -> 오래된 브라우저는 지원xcanIuse
페이지에서 브라우저별로 지원여부 확인 가능IE
는 지원되지 않음, IE에서 사용하고 싶다면 폴리필 추가해야한다.core-js
를 많이 사용한다.core-js(-pure)/es|stable|features/string/pad-start
import 'core-js/features/string/pad-start'
이렇게 설치해서 코드에서 마음놓고 사용하면 된다.npm run core-js
설치해서 사용하지만cra
에서는 기본적으로 core-js
가 내장되어 있기 때문에 필요한 부분만import
해주면 된다.개발, 테스트, 배포 환경별로 다른 값을 적용할때 유용하다.
전달된 환경변수는 코드에서 process.env.{변수이름}
과 같이 사용한다.
cra
에서는 기본적으로 process.env.NODE_ENV
라는 환경변수를 갖고 있다.
process.env.NODE_ENV
는
- npm start
-> development
- npm test
-> test
- npm run build
로 실행하면 production 이라는 값을 가지고 있는다.
환경변수를 커맨드라인에서도 실행할 수 있다.
REACT_APP_*
으로 항상 시작
process.env.REACT_APP_API_URL
// 윈도우버전
$ set "REACT_APP_API_URL=api.myapp.com" && npm start
console.log('process.env.REACT_APP_API_URL',process.env.REACT_APP_API_URL);
콘솔로 출력해본 결과 변수의 값이 출력된다. (but 난 undefined
가 찍힌다..?)
.env
파일로 관리하는게 좋다.// 개발용
REACT_APP_API_URL=api.myapp.com
REACT_APP_TEMP1=temp_dev1
// 배포용
REACT_APP_API_URL=api-prod.myapp.com
REACT_APP_TEMP1=temp_prod1
console.log('process.env.REACT_APP_temp1',process.env.REACT_APP_API_TEMP1);
서버로 연결해서 확인시 배포했을때 배포용 정의가 출력되고 개발모드시에는 개발했을때의 결과가 출력된다. (이부분도 undefined 출력..)
1. process.env.NODE_ENV production
2. process.env.REACT_APP_API_URL api-prod.myapp.com
3. process.env.REACT_APP_temp1 undefined
Box.module.css
처럼 css 파일의 모듈이름을 .module.css
으로 작성해주고
import할떄는 import style from './Button.module.css
로 받아서
객체 형식이기 떄문에 Style.big
,Style.small
클래스명을 그 속성 이름으로 해주면 된다.
콘솔에 출력해보면 객체 형태로 출력된다.
객체 속성의 값을 보면 해시값이 붙어있다.
- 이 해시값 덕분에 각 클래스명은 고유한 이름을 부여받을 수 있다.
- 때문에 이름 충돌 문제가 없다.
클래스네임 입력하는게 다소 번거로울 경우 간편하게 하는 방법
classNames
패키지 이용
$ npm install classNames
사용법 classnames
를 import
import cn from 'classnames'
<button className={cn(style.Button,style.big)}>큰 버튼</button>
cra
에서 Sass를 사용을 위한 패키지 설치 - node-sass
$ npm install node-sass
scss
styled-components
라이브러리 사용$ npm install styled-components
import styled from 'styled-components';
const BoxCommon = styled.div`
height : 50px;
background-color:#ffff;
`;
const Big = styled(boxCommon) `
width:200px;
`;
const BoxCommon = styled.button`
width:${props => {props.isBig ? 100 : 50 }}px;
`;
위처럼 동적으로 코드를 작성할 수도 있다.
pushState
, replaceState
함수popState
이벤트popState
이벤트를 통해 알려준다.import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
function App() {
useEffect(()=>{
window.onpopstate = function (event) {
console.log(`location: ${document.location},state: ${event.state}`);
}
},[]);
onpopstate
이벤트 핸들러를 등록하고 있다.useEffect
는 이벤트 핸들러를 등록하거나 api를 호출하는 등의 부수효과를 발생시킬때 사용하는 리액트 함수pushState
함수는 브라우저에게 알려주는 것return (
<div>
<button onClick={() => window.history.pushState('v1','','/page1')}>
page1
</button>
<button onClick={() => window.history.pushState('v2','','/page2')}>
page2
</button>
</div>
)
}
pushState
의 첫번째 매개변수 v1
은 데이터를 의미onpopstate
에서 event.state
가 있는데 onpopstate
에서 넘겨주는 그 값이 첫번째 매개변수의 값이다.react-router-dom
은 여러가지 편의 기능을 제공해준다.import {Route, Link} from 'react-router-dom';
export default function Rooms({match}) {
return (
<div>
<h2>여기는 방을 소개하는 페이지입니다.</h2>
<Link to={`${match.url}/blueRoom`}>파란방입니다.</Link>
// ...생략
</div>
)
}
function Room({match}) {
return <h2>{`${match.params.roomId} 방을 선택하셨습니다.`}</h2>
}
match
란?match
라는 속성값을 넣어준다.match
에는 url이라는 속성이 있다. 의미는 Rooms컴포넌트가 렌더링 됐을 당시에 match
됐던 그 url의 일부분🔍 강의 출처
실전 리액트 프로그래밍🧷 관련 링크