* 리액트 설치하기
npx create-react-app syleemomo-react 또는 npx create-react-app@latest syleemomo-react
cd syleemomo-react
npm start
Node 14.0.0 이상 버전 및 npm 5.6 이상 버전이 필요하다. 위 명령어로 잘 설치가 되지 않으면 아래와 같이 create-react-app 모듈을 먼저 설치한 다음에 CMD 창에서 create-react-app 명령어로 폴더를 생성하자!
npm install -g create-react-app
create-react-app [폴더명]
npm start 를 실행하면 위와 같이 자동으로 브라우저가 열리면서 http://localhost:3000/ 주소에서 실행된다.
* 리액트 포트 변경
package.json 파일에는 해당 프로젝트에 대한 메타 정보가 기록된다. dependencies 에는 프로젝트에서 설치한 라이브러리들이 기록된다. npm start 명령어를 실행하면 react-scripts start 명령어를 실행한다.
react-scripts 모듈은 위와 같이 node_modules 폴더 하위에 존재한다. 결국 npm start 를 실행하면 node_modules > react-scripts > scripts > start.js 파일이 실행된다. 위 캡쳐화면에서 리액트의 기본 포트는 3000 번임을 확인할 수 있다.
set PORT=3002 && npm start
기본 포트를 변경하려면 CMD 창에서 위와 같이 리액트 프로젝트를 실행하면 된다. 이렇게 하면 node_modules > react-scripts > scripts > start.js 파일이 환경변수의 PORT 번호(process.env.PORT)를 조회한 다음 기본 포트로 설정한다. 이때 주의할점은 비주얼 스튜디오 편집기의 명령창 종류를 Command Prompt 로 작성해야 한다는 점이다.
set 명령어로 환경변수 목록을 조회하면 설정한 PORT 변수가 등록되어 있음을 확인할 수 있다.
"scripts": {
"start": "set PORT=3002 && react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
하지만 첫번째 방법은 일회성으로 동작한다. 프롬프트 창(CMD)창을 껏다가 다시 켜면 환경변수 PORT 설정은 사라진다. 따라서 항상 설정한 포트로 동작하기 위해서는 package.json 파일의 scripts > start 명령어를 위와 같이 변경하면 npm start 명령어를 실행할때마다 기본 포트 3002 번으로 실행된다. 단, 프롬프트(CMD) 창에는 환경변수 PORT 가 나타나지 않는다.
// Tools like Cloud9 rely on this.
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3002;
const HOST = process.env.HOST || '0.0.0.0';
node_modules > react-scripts > scripts > start.js 파일에서 직접 기본 포트 3000번을 원하는 포트 번호로 변경해도 된다.
PORT=3002
프로젝트 루트 폴더(syleemomo-react) 하위에 .env 파일을 생성하고 위와 같이 설정한 다음 npm start 로 실행해도 포트가 변경된다. 해당 방법이 동작하지 않는다면 아래와 같이 환경변수가 이미 설정되어 있어서 그렇다.
CMD 창에서 set 명령어로 환경변수 목록을 확인해보면 아까 전에 설정해둔 PORT 가 존재한다. 이를 제거하기 위해서는 아래와 같은 명령어를 입력한다. 그런 다음 CMD 창을 닫았다가 다시 열고 set 명령어로 환경변수 목록을 재확인하면 PORT 변수가 사라진 것을 확인할 수 있다.
reg delete HKEY_CURRENT_USER\Environment /v PORT /f
그런 다음 .env 파일에 PORT 를 설정한 다음 npm start 로 프로젝트를 다시 실행해보자!
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
import logo from './logo.svg';
import './App.css';
function App() {
console.log("초기 렌더링")
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
App.js 파일에서 콘솔을 출력해보자!
* 클래스형 컴포넌트의 개념
자바스크립트의 최신문법인 class 키워드를 사용하여 컴포넌트를 정의한다. 리액트 초기에 상태(state) 관리가 필요한 컴포넌트는 클래스형 컴포넌트로 정의하였다. 상태(state)는 컴포넌트에서 초기화되고 업데이트되는 데이터이다.
* 클래스형 컴포넌트의 기본구조
간단한 형태의 클래스형 컴포넌트는 아래와 같다. class 키워드를 사용하며 기본적으로 React.Component 를 상속한다. 자바스크립트에서 상속은 extends 키워드를 사용한다. Person 은 컴포넌트 이름이며, 일반적으로 컴포넌트 이름의 첫글자는 대문자로 선언한다. render 메서드는 클래스형 컴포넌트에서 반드시 존재해야 한다.
리액트에서 모듈을 가져올때는 import 키워드를 사용하고 내보낼때는 export 키워드를 사용한다. 아래 코드에서는 react 라이브러리로부터 React 과 Component 모듈을 가져온다. Component 는 React 객체의 프로퍼티이므로 구조분해(destructuring assignment)를 이용하여 가져올 수 있다. 자바스크립트 모듈은 재사용 가능한 파일이나 객체를 의미한다.
import React, { Component } from 'react';
class Person extends Component {
render() {
const name = 'syleemomo'
const age = 3
return (
<React.Fragment>
<h3>{name}</h3>
<h4>{age}</h4>
</React.Fragment>
)
}
}
export default Person;
src 폴더 하위에 Person.js 파일을 생성하고 위와 같이 작성하자!
Component 는 React 모듈의 프로퍼티이므로 아래와 같이 작성해도 동일하게 동작한다.
import React from 'react';
class Person extends React.Component {
render() {
const name = 'syleemomo'
const age = 3
return (
<React.Fragment>
<h3>{name}</h3>
<h4>{age}</h4>
</React.Fragment>
)
}
}
export default Person;
리액트에서는 하나의 최상위 요소만 렌더링할 수 있다. 그렇지 않으면 에러가 발생한다. React.Fragment 는 HTML 템플릿 안의 다양한 요소들(h3, h4 태그)을 하나의 그룹으로 묶어주는 최상위 요소의 역할을 하지만 실제 웹화면에는 보이지 않는다. 즉, 렌더링할 HTML 요소가 두개 이상인 경우에는 반드시 위와 같이 React.Fragment 나 div 로 묶어줘야 한다.
import React from 'react';
class Person extends React.Component {
render() {
const name = 'syleemomo'
const age = 3
return (
<>
<h3>{name}</h3>
<h4>{age}</h4>
</>
)
}
}
export default Person;
React.Fragment 컴포넌트는 위와 같이 축약형으로 사용할 수도 있다.
* 클래스형 컴포넌트 사용 및 주의할 점
import React, { Component } from 'react';
import Person from './Person';
class App extends Component {
render() {
return <Person country="korea"></Person>
}
}
export default App;
App 컴포넌트에서 Person 컴포넌트를 가져온 다음 render 메서드에서 HTML 요소처럼 사용하면 된다. HTML 요소와의 차이점은 리액트에서는 반드시 슬래쉬(/)로 컴포넌트의 끝을 알려줘야 한다. 그냥 <Person country="korea"> 처럼 사용하면 아래와 같은 에러가 발생한다.
render 메서드에서 HTML 요소를 생성하는 코드는 JSX 문법이라고 한다. 이는 추후 수업에서 자세하게 다룬다. 아무튼 위와 같이 종료태그를 작성하지 않았다는 에러가 발생한다.
import React from 'react';
class Person extends React.Component {
render() {
const name = 'syleemomo'
const age = 3
return (
<>
<h3>{name}</h3>
<h4>{age}</h4>
</>
)
}
}
// export default Person;
만약 Person 컴포넌트를 export 키워드로 내보내주지 않으면 아래와 같은 에러가 발생한다. 즉, 해당 컴포넌트를 외부에서 사용하지 못한다.
import React from 'react';
class person extends React.Component {
render() {
const name = 'syleemomo'
const age = 3
return (
<>
<h3>{name}</h3>
<h4>{age}</h4>
</>
)
}
}
export default person;
해당 코드와 같이 컴포넌트 이름의 첫글자를 소문자로 작성하고 파일명의 첫글자도 person.js 와 같이 소문자로 변경해보자!
import logo from './logo.svg';
import './App.css';
import person from './person';
function App() {
console.log("초기 렌더링")
return <person country="korea"></person>
}
export default App;
App.js 파일을 위와 같이 수정해보자!
컴포넌트 이름과 파일명을 소문자로 작성하고 사용하면 위와 같이 브라우저는 person 이라는 태그가 없다고 판단한다. 그러므로 HTML 태그와 컴포넌트를 구분하기 위해서는 반드시 컴포넌트 이름과 파일명의 첫글자는 대문자로 작성해야 한다.
* 클래스형 컴포넌트 활용
할일(TODO)에 대한 컴포넌트를 만들어보자!
import React, { Component } from 'react';
class Todo extends Component {
render() {
// 데이터 선언
const todo = {
name: "cleaning",
done: false,
description: "cleaning my room on weekends"
}
// HTML 템플릿
return (
<>
<h3>name: {todo.name} </h3>
<h4>done: {todo.done? "finished": "ongoing"}</h4>
<p>description: {todo.description}</p>
</>
)
}
}
export default Todo;
초기 데이터(state) 선언과 이벤트핸들러 함수는 상황에 따라 생략 가능하다. render 메서드만 존재하는 클래스형 컴포넌트의 형태는 위와 같다. render 메서드 안에서 todo 객체(데이터)를 선언하고, HTML 템플릿에 데이터를 삽입한 다음 웹화면에 렌더링한다.
import React, { Component } from 'react';
import Todo from './Todo';
class App extends Component {
render() {
return <Todo></Todo>
}
}
export default App;
App 컴포넌트에서 Todo 컴포넌트를 가져온 다음 render 메서드 안에서 렌더링해보자! 결과는 아래와 같다.
import React, { Component } from 'react';
class Todo extends Component {
constructor(props){
super(props) // props 상속
this.state = { // state 선언
name: "cleaning",
done: false,
description: "cleaning my room on weekends"
}
}
// 이벤트 핸들러 함수
changeTodoName = () => {
this.setState({name: "learning react"})
}
// HTML 템플릿
render() {
const { name, done, description } = this.state
const { user } = this.props
return (
<>
<h2>user: {user}</h2>
<h3>name: {name}</h3>
<h4>done: {done? "finished": "ongoing"}</h4>
<p>description: {description}</p>
<button type="button"
onClick={this.changeTodoName}
>change todo name</button>
</>
)
}
}
export default Todo;
초기 데이터(state) 선언, 이벤트핸들러 함수, render 함수가 모두 갖춰진 클래스형 컴포넌트의 구조는 위와 같다.
생성자(constructor)를 사용하여 부모 컴포넌트로부터 props 를 전달받는다. 생성자 안에서 웹화면에 렌더링할 데이터인 state를 초기화한다. 생성자 다음에는 사용자의 이벤트를 처리하는 이벤트 핸들러 함수를 정의한다. 마지막으로 HTML 템플릿을 렌더링하는 render 메서드를 정의한다. button 요소에는 인라인 방식으로 클릭 이벤트가 설정되어 있다.
첫번째 예제의 Todo 컴포넌트와 비교하면 render 메서드 내부에 선언한 데이터가 state 객체로 변경되었다. this 키워드는 Todo 클래스를 가리킨다. 아래와 같이 App 컴포넌트에서 Todo 컴포넌트를 사용해보자!
import React, { Component } from 'react';
import Todo from './Todo';
class App extends Component {
render() {
return <Todo user="syleemomo"></Todo>
}
}
export default App;
App 컴포넌트에서 Todo 컴포넌트를 가져온 다음 render 메서드에서 사용해보자! 이번에는 Todo 컴포넌트에 user 라는 속성을 추가로 설정하였다. 리액트에서는 이러한 속성을 props 라고 한다. props 는 Todo 컴포넌트 내부로 전달되며, Todo 컴포넌트 내부에서 this.props.user 로 접근하면 "syleemomo" 라는 사용자 정보를 조회할 수 있다.
import React, { Component } from 'react';
class Todo extends Component {
state = { // state 선언
name: "cleaning",
done: false,
description: "cleaning my room on weekends"
}
// 이벤트 핸들러 함수
changeTodoName = () => {
this.setState({name: "learning react"})
}
// HTML 템플릿
render() {
const { name, done, description } = this.state
const { user } = this.props
return (
<>
<h2>user: {user}</h2>
<h3>name: {name}</h3>
<h4>done: {done? "finished": "ongoing"}</h4>
<p> description: {description}</p>
<button type="button"
onClick={this.changeTodoName}
>change todo name</button>
</>
)
}
}
export default Todo;
생성자(constructor)를 사용하지 않고 state 를 위와 같이 선언하기도 한다.
아래 예제는 동물에 대한 정보를 화면에 출력하는 Animal 컴포넌트이다.
import React from 'react';
class Animal extends React.Component {
state = {
type: 'cat',
name: 'meyow',
size: 'small',
sound: 'low',
appearence: 'cute'
}
render() {
const { type, name, size, sound, appearence } = this.state
return (
<>
<h1>동물 정보</h1>
<h3>종류 - {type}</h3>
<h3>이름 - {name}</h3>
<h3>크기 - {size}</h3>
<h3>소리 - {sound}</h3>
<h3>생김새 - {appearence}</h3>
</>
)
}
}
export default Animal;
state 는 동물에 대한 정보를 담고 있는 초기 데이터이다. render 메서드에서 state 를 조회할때는 this 로 접근한다. this 는 Animal 클래스를 의미한다. 구조분해(distructuring assignment)를 이용하여 state 객체의 프로퍼티를 각각의 변수에 따로 저장하였다. render 메서드는 동물에 대한 정보를 DOM 객체로 생성한 다음 반환한다.
import logo from './logo.svg';
import './App.css';
import Animal from './Animal'
function App() {
return (
<div className="App">
<Animal/>
</div>
);
}
export default App;
App 컴포넌트에서 Animal 컴포넌트를 가져와서 렌더링한다.
* 함수형 컴포넌트의 개념
function 키워드나 화살표 함수를 사용하여 컴포넌트를 정의한다. 예전에는 props(렌더링할 데이터)를 전달받아서 화면에 렌더링만 하는 경우 사용하였다. 최근에 리액트 훅(react hook)이 등장하면서 함수형 컴포넌트에서도 상태(state)관리를 할 수 있게 되었다. 즉, 이제는 함수형 컴포넌트에서도 state(데이터)를 변경할 수 있다.
* 함수형 컴포넌트의 기본구조
위에서 제시한 Person 컴포넌트를 함수형으로 만들면 아래와 같다. 일반적인 함수 선언과 동일하다. function 키워드를 사용하고, 컴포넌트 이름(함수명)의 첫글자는 대문자로 작성하였다. HTML 템플릿을 리턴한다. 클래스형 컴포넌트의 render 함수와 유사하다. export 키워드를 사용하여 외부에서 Person 컴포넌트를 사용할 수 있도록 한다.
import React from 'react';
function Person() {
const name = 'syleemomo'
const age = 3
return (
<>
<h3>{name}</h3>
<h4>{age}</h4>
</>
)
}
export default Person;
props 를 상속받는 경우는 아래와 같이 작성하면 된다. 함수의 파라미터로 props 객체가 전달된다. 함수형 컴포넌트에서 props 에 접근하는 경우 this 키워드는 필요 없다.
import React from 'react';
function Person(props) {
return (
<>
<h3>{props.name}</h3>
<h4>{props.age}</h4>
</>
)
}
export default Person;
화살표 함수로 함수형 컴포넌트를 선언할 수도 있다. function 키워드를 사용한 것과 완전히 동일하지는 않다. 이유는 두 경우 this 의 의미가 달라지기 때문이다.
import React from 'react';
const Person = (props) => {
return (
<>
<h3>{props.name}</h3>
<h4>{props.age}</h4>
</>
)
}
export default Person;
props 파라미터를 사용하는 대신 비구조화 할당(destructuring) 문법을 사용해서 곧바로 props 값을 조회할 수도 있다.
import React from 'react';
function Person({name, age}) {
return (
<>
<h3>{name}</h3>
<h4>{age}</h4>
</>
)
}
export default Person;
* 함수형 컴포넌트 사용
import React, { Component } from 'react';
import Person from './Person';
class App extends Component {
render() {
return <Person name="syleemomo" age="3"></Person>
}
}
export default App;
사용방법은 클래스형 컴포넌트와 동일하다. App 컴포넌트에서 Person 컴포넌트를 가져온 다음 render 함수에서 렌더링하면 된다. props 로 name 과 age 속성을 Person 컴포넌트 내부로 전달한다. 컴포넌트 내부에서 props 데이터를 조회할 수 있다.
* 함수형 컴포넌트 실습예제
import React, { Component } from 'react';
function Todo({ user, name, done, description }){
return (
<>
<h2>user: {user}</h2>
<h3>name: {name}</h3>
<h4>done: {done? "finished": "ongoing"}</h4>
<p> description: {description}</p>
</>
)
}
export default Todo;
props 로 전달받은 사용자정보(user)와 할일 정보(name, done, description)를 이용하여 웹 화면에 렌더링한다.
import React, { Component } from 'react';
import Todo from './Todo';
class App extends Component {
render() {
return <Todo user="syleemomo" name="cleaning" done="false" description="cleaning my room on weekends"></Todo>
}
}
export default App;
App 컴포넌트에서 Todo 컴포넌트를 가져온 다음 render 함수에서 렌더링해보자! Todo 컴포넌트 내부로 user, name, done, description 속성을 props 로 전달한다.
* 클래스 컴포넌트와 함수 컴포넌트의 차이
예전에는 state 변경과 라이프사이클을 사용하는 경우 클래스 컴포넌트만 사용가능했다. 그러나 리액트 훅이 추가되면서 함수 컴포넌트에서도 state 변경과 라이프사이클을 사용할 수 있게 되었기 때문에 현재는 큰 차이가 없다.
* 클래스형 컴포넌트에서 super(props)의 의미
https://developer-talk.tistory.com/136
super(props) 는 부모 클래스의 생성자를 호출한다. 부모 클래스의 생성자를 호출함으로써 자신에게 없는 프로퍼티나 메서드를 초기화할 수 있다. 자바스크립트에서는 super(props) 호출전에 this 를 사용하지 못하도록 강제하고 있다. 또한, 리액트에서는 super(props)와 constructor() 호출이 없더라도 this.props 로 부모 컴포넌트로부터 전달된 데이터를 사용할 수 있지만 생성자(contstructor) 내부에서 props 에 접근하기 위해서는 반드시 super(props)가 필요하다.
* 연습과제 1
아래는 나의 SNS 친구목록이다. 친구에 대한 정보를 화면에 렌더링하기 위하여 Friend 컴포넌트를 정의해보자. Friend 컴포넌트는 클래스형 컴포넌트이며, 부모로부터 name, age, city 를 props 로 전달받아서 화면에 해당 데이터를 렌더링한다. 그런 다음 App 컴포넌트에서 Friend 컴포넌트를 가져와서 사용한다. 당연히 아래 친구목록을 화면에 보여주기 위해서는 Friend 컴포넌트를 friends 배열의 갯수만큼 재사용해야 한다.
const friends = [
{name: 'victoria', age: 13, city: 'seoul'},
{name: 'sun', age: 34, city: 'busan'},
{name: 'johseb', age: 25, city: 'busan'},
{name: 'syleemomo', age: 9, city: 'seoul'},
{name: 'hannah', age: 41, city: 'daegu'},
{name: 'shara', age: 37, city: 'seoul'},
{name: 'martin', age: 28, city: 'daegu'},
{name: 'gorgia', age: 39, city: 'seoul'},
{name: 'nana', age: 24, city: 'busan'},
{name: 'dannel', age: 19, city: 'seoul'},
]
아래와 같이 화면에 친구목록을 보여주자!
* 연습과제 2
아래는 Person 컴포넌트를 클래스형 컴포넌트로 정의한 것이다. 버튼을 클릭하면 결과 화면과 같이 신상정보가 보여지도록 displayInfo 메서드를 구현해보자!
import React from 'react';
class Person extends React.Component {
state = {
name: "sunrise",
age: 23,
friends: [
"victoria",
"daniel",
"hanna"
]
}
render() {
return (
<>
<button onClick={this.displayInfo}>신상정보 확인하기</button>
</>
)
}
}
export default Person;
당연히 화면에 Person 컴포넌트를 보여주려면 아래와 같이 App 컴포넌트에서 가져와서 렌더링해줘야 한다.
import logo from './logo.svg';
import './App.css';
import Person from './Person'
function App() {
return (
<div className="App">
<Person/>
</div>
);
}
export default App;
* 연습과제 3
회사에서 온라인 도서 서비스를 런칭한다고 하자. 그래서 아래 테이블처럼 데이터 모델을 설계하였다.
필드명 | 설명 | 데이터 타입 |
title | 도서 제목 | 문자열(String) |
author | 도서 저자 | 문자열(String) |
summary | 도서 내용 요약 | 문자열(String) |
genre | 도서 장르 | 문자열(String) |
release | 도서 발매일 | 문자열(String) |
ISBN | 국제표준도서정보 | 숫자(Number) |
위 모델을 이용하여 Book 컴포넌트를 클래스형 컴포넌트로 정의하고, 화면에 도서 정보를 출력해보자!
{
title: '해리포터',
author: '조앤K롤링',
summary: '해리포터가 마법사로 성장해나가는 과정을 그린 이야기',
genre: '판타지 소설',
release: '2003년 9월 4일',
ISBN: '1234567890'
}
출력할 정보는 위와 같다. 결과는 아래 화면과 같다.
* 연습과제 4
아래는 클래스형 컴포넌트 수업에 사용된 예제이다. 아래 클래스형 컴포넌트를 함수형 컴포넌트로 변경해보자! state 에 정의한 데이터는 부모 컴포넌트에서 props 로 전달받도록 한다.
import React from 'react';
class Animal extends React.Component {
state = {
type: 'cat',
name: 'meyow',
size: 'small',
sound: 'low',
appearence: 'cute'
}
render() {
const { type, name, size, sound, appearence } = this.state
return (
<>
<h1>동물 정보</h1>
<h3>종류 - {type}</h3>
<h3>이름 - {name}</h3>
<h3>크기 - {size}</h3>
<h3>소리 - {sound}</h3>
<h3>생김새 - {appearence}</h3>
</>
)
}
}
export default Animal;
'프론트엔드 > React' 카테고리의 다른 글
리액트 기초이론 5 - 컴포넌트 스타일링 (0) | 2021.10.22 |
---|---|
리액트 기초이론 4 - 컴포넌트의 생명주기 (Life cycle) (0) | 2021.10.22 |
리액트 기초이론 3 - JSX 문법 (0) | 2021.10.22 |
리액트 기초이론 2 - state & props (0) | 2021.10.22 |
리액트 기초이론 0 - 컴포넌트의 개념과 기본적인 동작방식 (0) | 2021.10.22 |