리액트는 자바스크립트 라이브러리로 사용자 인터페이스를 만드는데 사용한다.
구조가 MVC, MVW 등인 프레임워크와 달리, 오직 V(View)만을 신경 쓰는 라이브러리이다.
Node.js가 설치 되었다는 전제로 시작한다.
저자는npm
을 통하여 설치를 진행한다.
npx create-react-app <프로젝트 이름>
npm create-react-app <프로젝트 이름>
두개의 명령어가 있는데 무엇이 다를까?
npm | npx | |
---|---|---|
의미 | Node.js 패키지 매니저로 사용 | 패키지를 실행하지만, 설치하지 않음 |
사용 목적 | 패키지 설치 및 전역 명령 실행 | 일회성 패키지 실행 및 설치 (전역 설치 X) |
실행 방법 | npm install -g 패키지명 | npx 패키지명 |
패키지 버전 | 패키지의 전역 설치 버전 사용 | 항상 최신 버전을 사용 |
특징 | 패키지 버전 관리가 가능, 여러 프로젝트에서 재사용 가능 | 글로벌 설치 없이 필요한 패키지를 즉시 실행 |
설치가 완료 되었으면, 해당 폴더로 이동 cd 프로젝트 이름
-> npm start
오케이 시작이 반이라고 했으니 반정도 왔다.
가장 먼저 package.json 파일에 대해 알아보자
Node.js 프로젝트의 구성 및 의존성 정보를 저장하는 .json 파일
name
, version
, private
, ....start
, build
, test
, ....스크립트 부분에서 start
부분이 react-scripts start
인 것을 보아
왜 npm start
를 했을 때 리액트 프로젝트가 실행이 되는 것을 알 수 있다.
import React from 'react';
CSS에서 어떠한 파일을 불러와 사용하는 문법이랑 똑같이 생겼다.
위 코드는 내가 생각한 것 처럼 리앤트를 불러와 사용할 수 있게 해주는데
프로젝트 생성 과정에서 node_modules 폴더에 react 모듈이 설치 되고, 이를 통해
import
문을 통하여 리액트를 불러와 사용할 수 있는 것이다.
import logo from './logo.svg';
import './App.css';
import
문을 사용하여 다른 파일에서 정의한 내용을 현재 파일로 불러와 사용할 수 있으며, 이를 통해 코드를 모듈화하고 다른 파일에서 정의한 내용을 현재 파일에서 재사용할 수 있다.
function App() {
return (
<div className="App">
...
</div>
)
};
App
이라는 컴포넌트를 function
키워드를 사용하여 컴포넌트를 만들게 되는데
이를 함수형 컴포넌트라 부른다. 이러한 코드를 JSX
라고 부르는데 JSX
가 무엇인지 알아보자
JSX
(JavaScript XML) 자바스크립트의 확장 문법으로, JS코드 내에서 HTML과 유사한 구문을 사용하여 UI 컴포넌트를 만들고 렌더링하는 데 도움을 준다.
function App() {
return(
<div>
Hello! <b>React</b>
</div>
)
};
이러한 코드는 다음과 같이 변환이 된다.
function App() {
return React.createElement("div", null, "Hello!", React.createElement("b", null, "React"));
}
JSX
문법을 사용하는 것이 아니라 항상 createElement
를 쓰게 되면 불편할 것이다
그렇기에 JSX
를 사용하면 편하게 렌더링을 할 수 있다.
JSX
를 올바르게 사용하기 위해 규칙에 대하여 알아보자
import React from 'react';
function App () {
return (
<h1>리액트의 JSX 문법 공부</h1>
<p>1. 감싸인 요소를 꼭 포함한다.</p>
)
}
export default App;
이렇게 되게 되면 제대로 작동하지 않을 것이다.
"JSX 식에는 부모 요소가 하나 있어야 합니다" 라고 에러가 발생하는 것을 볼 수 있다.
위의 식을 에러 없이 제대로 작동하게끔 코드를 수정해보자
div
태그 사용하기import React from 'react';
function App () {
return (
<div>
<h1>리액트의 JSX 문법 공부</h1>
<p>1. div 태그로 감싸기</p>
</div>
)
}
export default App;
Fragment
태그 사용하기import React from 'react';
function App () {
return (
<React.Fragment>
<h1>리액트의 JSX 문법 공부</h1>
<p>2. 최상위 요소를 두기 싫을 때 Fragment 사용하기</p>
</React.Fragment>
)
}
export default App;
import React from 'react';
function App () {
return (
<>
<h1>리액트의 JSX 문법 공부</h1>
<p>3. 빈 태그 사용하기</p>
</>
)
}
export default App;
jsx
내부에서 js 표현식 사용하기
import React from 'react';
function App () {
const name = "리액트";
return (
<div>
<h1>{name}의 JSX 문법 공부</h1>
</div>
)
}
export default App;
{}
를 사용하여 jsx
내부에서 렌더링 하기
JSX 내부의 자바스크립트 표현식에서 if문을 사용 할 수 없다.
import React from 'react';
function App() {
const name = '리액트';
return <div>{name === '리액트' ? <h1>리액트 입니다.</h1> : <h1>리액트가 아닙니다</h1>}</div>;
}
export default App;
JSX
내부에선 삼항 연산자를 사용하자
아무것도 렌더링을 하고 싶지 않을 때 AND연산자를 사용하자
위의 3번에서 배운 조건부 연산자를 사용하여 null
값을 사용하여 아무것도 렌더링 하지 않을 수 있지만
AND 연산자(&&)를 사용하면 더 간결하게 사용할 수 있다.
import React from 'react';
function App() {
const name = '리액트';
return <div>{name === '리액트' && <h1>리액트 입니다.</h1>}</div>;
}
export default App;
name
이 "리액트"일 경우 화면에 표시가 되지만 아닐 경우 아무것도 표시가 되지 않는걸 볼 수 있다.
카멜표기법으로 작성하자
import React from 'react';
function App() {
const name = '리액트';
const style = {
backgroundColor: 'black',
fontSize: '50px',
textAlign: 'center',
color: 'aqua',
};
return <div style={style}>{name}</div>;
}
export default App;
import React from 'react';
function App() {
return (
<div>
<h2>리액트를 배워보자</h2>
<input>
</div>
);
}
export default App;
해당 코드는 Parsing error : Unterminated JSX contents
즉 모든 태그는 닫는 태그를 사용해야한다.
<input />
, <br />
....
JS나 HTML에서 사용하는 주석을 사용하게 되면 화면에 표시되게 된다
그렇기에 {/* ... */}
의 형태로 사용하자!
import React from 'react';
function App() {
return (
<>
<h1>리액트의 JSX 문법 공부</h1>
{/* 주석은 이렇게 사용합니다 */}
// 이렇게 사용하면 화면에 보여지게 됩니다.
/* 이렇게 사용해도 화면에 보여지게 됩니다.*/
</>
);
}
export default App;
컴포넌트 내부에서 바뀔 수 있는 값
React에서 사용하는 State를 알아보기 위해 기본적으로 버튼을 눌렀을 때
1씩 증가, 1씩 감소 그 값을 화면에 렌더링 하는것을 구현해보자
State
를 사용하기 위해서는 React
를 import 하고useState
를 추가적으로 적어야 한다.import React, { useState } from 'react';
Returns a stateful value, and a function to update it
-> "현재 상태 값을 나타내는 변수 + 상태 값을 업데이트하는 함수를 반환한다" 라고 알려준다.
useState
를 사용하여 상태 값을 초기화, 업데이트 함수를 선언const [count, setCount] = useState(0);
배열의 비구조화 할당을 이용하여 다음과 같은 값을 가지게 한다.
count
- 상태 값
setCount
- 상태 값을 업데이트하는 함수
const onIncrease = () => {
setCount(count + 1);
};
const onDecrease = () => {
setCount(count - 1);
};
증가,감소 함수를 사용하여 현재 상태 값인
count
를 +1, -1씩 하는 함수를 만들어 봤다.
import React, { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
const onIncrease = () => {
setCount(count + 1);
};
const onDecrease = () => {
setCount(count - 1);
};
return (
<div>
<h2>{count}</h2>
<button onClick={onIncrease}>증가 버튼</button>
<button onClick={onDecrease}>감소 버튼</button>
</div>
);
};
처음 보는 문법이라 아직 익숙하진 않은데 확실한건 js로만 했을 때보다는 더 간결하다는 점
useState(0)
- 초기 값 0 그렇기에 count가 0으로 출력 되는 걸 볼 수 있다.컴포넌트에 데이터 전달하기
부모 컴포넌트인 App.js
파일이 있고 자식 컴포넌트인 Counter.js
가 있다
위에서 버튼을 눌렀을 때 증감이 되는 함수가 포함된 파일 즉 자식 컴포넌트가 Counter.js
import React from 'react';
import Counter from './Counter';
function App() {
return(
<>
<Counter initialNumber={5}/>
</>
)
}
import React, { useState } from 'react';
const Counter = (props) => {
console.log(props);
const [count, setCount] = useState(0);
const onIncrease = () => {
setCount(count + 1);
};
const onDecrease = () => {
setCount(count - 1);
};
return (
<div>
<h2>{count}</h2>
<button onClick={onIncrease}>증가 버튼</button>
<button onClick={onDecrease}>감소 버튼</button>
</div>
);
};
export default Counter;
부모 컴포넌트에서 데이터를 적기만 한다고 자식 컴포넌트에 전달이 되는것이 아닌
데이터를 받고자 하는 자식 컴포넌트에서 props를 매개변수로 받는다.
이를 console.log
에 찍어보면 object
즉 객체로 받는것을 볼 수 있다.
편의를 위해 코드를 생략하고 작성하겠다
// App.js
function App() {
return (
<>
<Counter initialNumber={5} />
</>
);
}
// Counter.js
const Counter = (props) => {
const [count, setCount] = useState(props.initialNumber); // 화면에 초기 값이 5로 경 된 것을 볼 수 있다.
};
props
가 console 창에서 객체로 나오기에 그 값에 접근 하기 위해서 props.키값
으로 접근을 하였고 화면에 받은 데이터
// App.js
function App() {
const counterProps = {
a: 1,
b: 2,
c: 3,
d: 4,
f: 5,
};
return (
<>
<Counter {...counterProps} />
</>
);
}
넘겨주고자 하는 데이터가 많을 경우 태그 안에 나열 보다는 새로운 객체를 생성해서
스프레드 연산자로 받는것을 지향하자
// App.js
import React from 'react';
import Counter from './Counter';
function App() {
const counterProps = {
a: 1,
b: 2,
c: 3,
d: 4,
f: 5,
};
return (
<>
<Counter {...counterProps} />
</>
);
}
export default App;
// Counter.js
import React, { useState } from 'react';
const Counter = (props) => {
const [count, setCount] = useState(props.initialNumber);
const onIncrease = () => {
setCount(count + 1);
};
const onDecrease = () => {
setCount(count - 1);
};
return (
<div>
<h2>{count}</h2>
<button onClick={onIncrease}>증가 버튼</button>
<button onClick={onDecrease}>감소 버튼</button>
</div>
);
};
export default Counter;
이렇게 되면 initialNumber
이 undefined
가 되기에 여기에 연산을 하게 되면 NaN
이 렌더링 된다.
이는 오류이기에 해당 코드를 초기 설정값을 통해 이러한 문제를 해결해보자
Counter.defaultProps = {
initialNumber: 0,
};
App.js
에 해당 코드를 넣음으로써 NaN
이 렌더링 되는것을 방지할 수 있다.
또한 신기한 사실은 부모 컴포넌트의 State의 변경에 따른 자식 요소 컴포넌트도 리렌더링 된다.