react chapter3. 컴포넌트 (리액트를 다루는 기술)

클래스형 컴포넌트, 함수형 컴포넌트, props, state, state를 사용할때 주의사항 등을 알아본다.

3.1 클래스형 컴포넌트

함수형 컴포넌트는 다음과 같은 구조로 이루어져 있다.

App.js 함수형 컴포넌트
1
2
3
4
5
6
7
8
9
import React from 'react';

//함수형 컴포넌트
function App(){
const name ="리액트";
return <div className="react">{name}</div>
}

export default App;

컴포넌트는 함수형 컴포넌트클래스형 컴포넌트로 선언할 수 있다.

클래스형 컴포넌트는 다음과 같은 구조이다.

App.js 클래스형 컴포넌트
1
2
3
4
5
6
7
8
9
import React, { Component } from 'react';

//클래스형 컴포넌트
class App extend Component{
const name ="리액트";
return <div className="react">{name}</div>
}

export default App;

클래스형 컴포넌트로 바뀌었지만 둘의 기능은 똑같다.
이 둘의 차이점은 클래스형 컴포넌트state 기능 및 라이프사이클(life cycle)을 사용가능하다는 것과 임의 메서드를 정의할 수 있다는 것이다.
클래스형 컴포넌트에서는 render 함수가 꼭 있어야하고, 그 안에 보여줄 JSX를 반환해야한다.

리액트 메뉴얼에서는 함수형 컴포넌트와 hooks를 사용하기를 권장하지만 함수형 컴포넌트도 장단점이 존재한다.

함수형 컴포넌트 장점 : 선언하기가 편하다, 메모리 자원을 덜 사용한다, 프로젝트 빌드 후 배포할때 파일 크기가 더 작다.
함수형 컴포넌트 단점 : state와 라이프사이클 API 사용이 불가능하다(이 점은 리액트 v16.8 업데이트 후 Hooks 라는 기능이 도입되면서 해결)

3.2 첫 컴포넌트 생성

파일만들기 -> 코드 작성 -> 모듈내보내기 및 불러오기

MyComponent.js
1
2
3
4
5
6
7
8
9
10
11
//hello-react\src\MyComponent.js
//모듈 불러오기(import)
import React from 'react';

const MyComponent = () => {
return (<div> 나의 새롭고 멋진 컴포넌트</div>
);
};

//모듈 내보내기
export default MyComponent;

function 키워드 사용대신 () => {} 를 사용함 이는 ES6에서 도입된 화살표 함수 문법임

노트 ES6 화살표 함수

화살표 함수는 ES6 문법에서 함수를 표현하는 새로운 방식이다. 이 문법은 주로 함수를 파라미터로 전달할 때 유용하다.
이 문법이 기존 function을 대체할 수 없는 이유는 서로 가리키는 this의 값이 다르기 때문이다.

function vs 화살표
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function BlackDog() {
this.name = "흰둥이";
return {
name: "검둥이",
bark: function () {
console.log(this.name + "명멍");
}
}
}

const blackDog = new BlackDog();
blackDog.dark(); //검둥이 멍멍

function WhiteDog() {
this.name = "흰둥이";
return {
name: "검둥이",
bark: () => {
console.log(this.name + "명멍");
}
}
}

const whiteDog = new WhiteDog();
whiteDog.dark();//흰둥이 멍멍

일반 함수는 자신이 종속된 객체를 this로 가리키며, 화살표 함수는 자신이 종속된 인스턴스를 가리킨다.

노트 ReactJS Code Snippet 사용

vs code 에서 ReactJS Code Snippet 확장팩 설치후 에디터에 rsc를 입력하면
알아서 react 컴포넌트 코드가 생성됨
rsc -> 함수형 컴포넌트 생성
rcc - > 클래스형 컴포넌트 생성

3.2.2 모듈 불러오기(import)

App.js
1
2
3
4
5
6
7
8
import React from 'react';
import MyComponent from './MyComponent';

const App = () => {
return <MyComponent />;
};

export default App;

3.3 props

props는 컴포넌트 속성을 설정할때 사용하는 요소이다.
props 값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트 (현 상황에서는 App.js가 부모 컴포넌트임)에서 설정 가능하다.

3.3.1 JSX 내부에서 props 랜더링

MyComponent 수정해서 해당 컴포넌트에서 name이라는 props를 랜더링하도록 설정한다.

MyComponent.js
1
2
3
4
5
6
7
8
9
10
11
//hello-react\src\MyComponent.js
//모듈 불러오기(import)
import React from 'react';

const MyComponent = props => {
return (<div>안녕하세요, 제 이름은 {props.name} 입니다.</div>
);
};

//모듈 내보내기
export default MyComponent;

App.js 컴포넌트에서 MyComponent의 props의 값을 지정한다.

App.js
1
2
3
4
5
6
7
8
import React from 'react';
import MyComponent from './MyComponent';

const App = () => {
return <MyComponent name="soo"/>;
};

export default App;

3.3.3 props 기본값 설정 : defaultProps

name 값을 부모 컴포넌트에서 지정해주지 않으면 name 값이 안나타나므로
“안녕하세요, 제 이름은 입니다.” 로 나온다.
지금처럼 props 값을 지정하지 않을때 보여줄 기본값을 설정하는 defaultProps는 다음과 같다.

defaultProps
1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react';

const MyComponent = props => {
return (<div>안녕하세요, 제 이름은 {props.name} 입니다.</div>
);
};

//기본설정하기
MyComponent.defaultProps ={
name : "기본이름"
}

export default MyComponent;

3.3.4 태그 사이의 내용을 보여주는 children

리액트 컴포넌트를 사용할 떄, 컴포넌트 태그 사이에 내용을 보여주는 props가 있는데 children 이라고 한다.

App.js
1
2
3
4
5
6
7
8
import React from 'react';
import MyComponent from './MyComponent';

const App = () => {
return <MyComponent>자식</MyComponent>;
};

export default App;

위에서 MyComponent 사이에 작성한 ‘자식’이란 문자열을 MyComponent 내부에서 보여주려면 props.children을 사용한다.

MyComponent.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from 'react';

const MyComponent = props => {
return (<div>안녕하세요, 제 이름은 {props.name} 입니다.
children의 값은 {props.children} 입니다.
</div>

);
};
/*
안녕하세요, 제 이름은 기본이름 입니다.
children의 값은 자식입니다.
*/

(...)

export default MyComponent;

3.3.5 비구조화 할당 문법을 통해 내부 props 값 추출하기

MyComponent 에서 props 값을 조회할 때마다 props.name 과 같이 props.를 붙여주고 있는데, 이러한 작업을 더 편하게 하기 위해 ES6의 비구조화 할당 문법을 사용하여 내부 값을 추출할 수 있다.

MyComponent.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//MyComponent.js 수정
import React from 'react';

const MyComponent = props => {
//비구조화 할당
const {name , children} = props;

return (<div>안녕하세요, 제 이름은 {name} 입니다.
children의 값은 {children} 입니다.
</div>

);
};

(...)
export default MyComponent;

다음과 같이도 수정 가능하다.

MyComponent.js
1
2
3
4
5
6
7
8
9
10
11
12
//MyComponent.js 수정
import React from 'react';

const MyComponent = ({name , children}) => {
return (<div>안녕하세요, 제 이름은 {name} 입니다.
children의 값은 {children} 입니다.
</div>
);
};

(...)
export default MyComponent;

3.3.6 propTypes를 통한 props 검증

props에 타입을 지정할때는 propTypes를 사용한다.
propTypes를 사용하기 위해서는 import 구문을 사용하여 불러와야 한다.

MyComponent.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from 'react';
//propsType 불러오기
import PropTypes from 'prop-types';

const MyComponent = ({name , children}) => {
return (...)
};

//props타입 지정
MyComponent.propTypes = {
name : PropTypes.string //문자열으로 타입을 지정, name의 값은 무조건 문자열로 전달해야한다.
}

export default MyComponent;

name에 값을 문자열이 아닌 다른값(ex)숫자)로 전달하면 값은 나오지만, 개발자 도구 console탭에서 오류를 알려준다.

3.3.6.1 isRequired를 사용하여 필수 propTypes 설정

propTypes를 지정하지 않았을 때 경고 메시지 등을 띄어주기 위해 isRequired를사용한다.
필수 propTypes를 지정할 때 사용

MyComponent.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from 'react';
//propsType 불러오기
import PropTypes from 'prop-types';

const MyComponent = ({name ,favoriteNumber, children}) => {
return (<div>안녕하세요, 제 이름은 {name} 입니다.
children의 값은 {children} 입니다.

<br/>
제가 좋아하는 숫자는 {favoriteNumber}입니다.
</div>
);
};

//props타입 지정
MyComponent.propTypes = {
name : PropTypes.string //문자열으로 타입을 지정, name의 값은 무조건 문자열로 전달해야한다.
favoriteNumber : propTypes.number.isRequired
}

export default MyComponent;

favoriteNumber의 값을 설정하지 않았으므로 개발자 도구에 오류가 발생하게된다.

App.js
1
2
3
4
5
6
7
8
9
10
import React from 'react';
import MyComponent from './MyComponent';

const App = () => {

//App.js에서 favoriteNumber의 값을 불러옴
return <MyComponent favoriteNumber={1}>자식</MyComponent>;
};

export default App;

더 많은 PropTypes의 종류들

다음과 같은 것들이 PropTypes의 종류가 될 수 있다.

  • array : 배열
  • arrayOf : 특정 propType으로 이루어진 배열
  • bool : true or false 값
  • func : 함수
  • object : 객체
  • number : 숫자
  • string : 문자
  • symbol : ES6의 Symbol
  • node : 랜더링 할 수 있는 모든 것 (숫자, 문자열, JSX 등등)
  • instanceOf(클래스) : 특정 클래스의 인스턴스
  • oneOf([‘dog’,’cat’]) : 주어진 배열 요소 값 중 하나
  • oneOfType([React.PropTypes.string, PropTypes.number]) : 주어진 배열 안의 종류 중 하나
  • objectOf(React.PropTypes.number): 객체의 모든 키 값이 인자로 주어진 propType인 객체
  • shape({name : PropTypes.string, num : PropTypes.number}) : 주어진 스키마를 가진 객체
  • any : 아무 종류

더 자세한 정보는 link: https://github.com/facebook/prop-types 에서 확인 가능

3.3.7 클래스형 컴포넌트에서 props 사용하기

클래스형 컴포넌트에서 props를 사용할땐, render 함수에서 this.props를 조회하면 된다.

MyComponent.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React, { Component } from 'react';
import propTypes from 'prop-types';

class MyCompoenet extends Component {

render() {
const { name, favoriteNumber, children } = this.props; //비구조화 할당
return (
<div>
제 이름은 {name}입니다.
<br />
제가 좋아하는 색은 {favoriteNumber} 이구요
<br />
저의 차일드는 {children} 입니다.
</div>
);
}
}

//defaultProps 지정
MyComponent.defaultProps ={
name : "기본이름";
}

//propTypes 지정
MyComponent.propTypes = {
name : PropTypes.string
}

export default MyCompoenet;

class 내부에서 지정하는 방법도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import React, { Component } from 'react';
import propTypes from 'prop-types';

class MyCompoenet extends Component {

//내부에서 지정
static defaultProps = {
name: '기본이름',
};
static propTypes = {
name: propTypes.string,
};

render() {
const { name, favoriteNumber, children } = this.props;
return (
<div>
제 이름은 {name}입니다.
<br />
제가 좋아하는 색은 {favoriteNumber} 이구요
<br />
저의 차일드는 {children} 입니다.
</div>
);
}
}

export default MyCompoenet;

3.4 state

props가 부모 컴포넌트가 설정하는 값이며, 컴포넌트 자신은 해당 props를 읽는 용도로만 사용할 수 있고, props를 바꾸려먼 부모 컴포넌트에서 바꾸어야 하는 것이라면
state는 컴포넌트 내부에서 바뀔 수 있는 값을 말한다.
리액트에서는 두 가지 state가 있다. 하나는 클래스형 컴포넌트가 지닌 state이고, 다른 하나는 함수형 컴포넌트에서 useState를 통해 사용하는 state 이다.

클래스형 컴포넌트의 state

새로운 Counter.js라는 컴포넌트를 src 폴더에 생성한다.

Conuter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { Component } from 'react';

class Counter extends Component {
constructor(props) {
super(props);
//state의 초기값 설정
this.state = {
number: 0,
};
}

render() {
const { number } = this.state; //state를 조회할 때는 this.state로 조회한다.
return (
<div>
<h1>{number}</h1>
<button //onClick을 통해 버튼이 클릭되었을때 호출할 함수를 지정
onClick={() => {
//this.setState를 사용하여 state에 새로운 값을 넣는다.
this.setState({ number: number + 1 });
}}
>
+1
</button>
</div>
);
}
}
export default Counter;

컴포넌트의 state를 설정할 때는 다음과 같이 constructor 메서드를 작성하여 설정한다. 클래스형에서 constructor을 작성할때는 반드시 super(props)를 호출해 주어야 한다. 그 다음에, constructor 내부에 this.state값에 초깃값을 설정해준다. 컴포넌트의 state는 객체 형태 ({}) 여야 한다.

render 함수에서 현재 state를 조회할 때는 this.state를 조회하면 된다. 그리고 button의 onClick 이벤트 안에 this.setState라는 함수를 사용해 state 값을 변경한다.
후에 App.js에서 컴포넌트를 불러와 랜더링을 하면 버튼 클릭시 숫자가 1씩 올라가게 된다.

3.4.1.1 state 객체 안에 여러 값이 있을 때

state 객체 안에 여러 값이 있을때는 다음과 같이 작성한다.

Conuter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { Component } from 'react';

class Counter extends Component {
constructor(props) {
super(props);
//state의 초기값 여러개 설정
this.state = {
number: 0,
fixedNumber : 0
};
}

render() {
const { number, fixedNumber } = this.state; //state를 조회할 때는 this.state로 조회한다.
return (
<h1>{number}</h1>
<h1>변경 불가 값 : {fixedNumber}</h1>
...
);
}
}
export default Counter;

3.4.1.1 state를 constructor에서 꺼내기

state 초기값을 지정하기 위해 constructor 메서드를 선언해주었는데, 또 다른 방식으로도 state 초깃값을 지정할 수 있다.

Counter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { Component } from 'react';

class Counter extends Component {
//다른 방식으로 state 초깃값 설정하기
state = {
number:0,
fixedNumber :0;
};

render() {
const { number, fixedNumber } = this.state; //state를 조회할 때는 this.state로 조회한다.
return (
...
);
}
}
export default Counter;

3.4.1.3 this.setState에 객체 대신 함수 인자 전달하기

this.setState를 사용하여 state 값을 업데이트 할 땐 상태가 비동기적으로 업데이트 된다. 만약 onClick 함수 내부에서 this.setState를 두 번 호출하면 클릭시 +2가 될까?

Counter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { Component } from 'react';

class Counter extends Component {
(...)
render() {
const { number } = this.state; //state를 조회할 때는 this.state로 조회한다.
return (
<div>
(...)
onClick={() => {
//this.setState를 사용하여 state에 새로운 값을 넣는다.
this.setState({ number: number + 1 });
this.setState({ number: this.state.number + 1 });
}}
>
+1
</button>
</div>
);
}
}
export default Counter;

정답은 “NO”이다. this.setState를 사용한다고 해서 state 값이 바로 바뀌진 않기 때문이다.

이에 대한 해결을 하기 위해서는 this.setState를 사용할 때, 객체 대신 함수를 인자로 넣어주면 된다.

this.setState 객체 대신 함수를 인자로 넣어주기
1
2
3
4
5
6
//this.setState를 사용해 바로 값이 변경되게 하려면/?
this.setState((prevState,props) => {
return(
//업데이트 할 내용
)
});

여기서 prevState는 기존 상태이고, props는 현재 지니고 있는 props를 가리키는데 , 만약 props 값이 업데이트 과정에서 필요없다면 생략 가능하다.

Counter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//클릭시 +2를 하게끔 this.setState를 두번 호출해보자!
import React, { Component } from 'react';

class Counter extends Component {
(...)
render() {
const { number } = this.state; //state를 조회할 때는 this.state로 조회한다.
return (
<div>
(...)
<button onClick={
() => {

//this.setState 바로바로 값 바꾸게 하기
this.setState(prevState => {
return {
number: prevState.number + 1,
};
});

//한 번 더
this.setState(prevState => {
return {
number: prevState.number + 1,
};
});
}}
> +2
</button>
</div>
);
}
}
export default Counter;

this.setState가 끝난 뒤에 특정 작업 실행하기

setState를 사용해 값을 업데이트 하고 난 다음에 특정작업을 실행하기 위해서는 setState의 두번째 파라미터에 callback 함수를 등록해 작업을 처리하면 된다.

Counter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(...)
<button
onClick={() => {
this.setState(
{
number: number + 1,
},
//콜백 함수로 업데이트 후 작업 처리
() => {
console.log('작업 끝');
},
);
}}
>
+1
</button>

3.4.2 함수형 컴포넌트에서 useState 사용하기

리액트 v16.8 이전에는 함수형 컴포넌트에서 state를 사용할 수 없었지만, 16.8 이후로부터는 useState라는 함수를 통해 함수형 컴포넌트에서도 state를 사용할 수 있게 되었다.
이 과정에서 Hooks를 사용하게 된다.Hooks의 종류는 다양한데, 지금은 useState만을 사용해 보도록한다.

3.4.2.1 배열 비구조화 할당

배열 비구조화 할당은 배열 안에 들어 있는 값을 쉽게 추출할 수 있게 한다.

배열 비구조화 할당 예제
1
2
3
4
5
6
7
8
9

const array = [1,2];

//배열 비구조화 전
const one = array[0]; //1
const one = array[1]; //2

//배열 비구조화 후
const [one, two] = array;

3.4.2.2 useState 사용

새로운 Say.js 컴포넌트를 src 폴더에 생성한다.

Say.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { useState } from 'react';

const Say = () => {
const [message, setMessage] = useState('');
const onClickEnter = () => setMessage('안녕하세요!');
const onClickLeave = () => setMessage('안녕히 가세요!');

const [color, setColor] = useState('black');
return (
<div>
<button onClick={onClickEnter}>입장</button>
<button onClick={onClickLeave}>퇴장</button>
<h1 style={{ color }}>{message}</h1>
</div>
);
};
export default Say;

useState 함수의 인자에 상태 초기값을 넣어준다. 클래스형 컴포넌트에서 state 초깃값은 항상 객체 형태로 넣어주어야 하는 것과 달리, useState 에서는 반드시 객체가 아니여도 크게 상관없다. 문자열, 숫자, 객체, 배열 모두 가능하다.

함수를 호출하면 배열이 반환되는데, 배열의 첫번째 요소 헌재상태이고,
두번째는 상태를 바꿔주는 setter 함수이다. 위의 Say.js 의 경우 message가 현재상태이고, setMessage가 message의 상태값으 바꿔주는 setter 함수가 된다.

App.js에서 Say.js를 랜더링을 하면
입장 버튼 클릭시 ‘안녕하세요’라는 문자열이,
퇴장 버튼 클릭시 ‘안녕히 가세요!’라는 문자열이 나오게된다.

3.4.2.3 한 컴포넌트에서 useState 여러번 사용하기

useState는 여러번 사용해도 상관없다. 또 다른 상태를 useState로 관리하면 된다.

Say.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { useState } from 'react';

const Say = () => {
const [message, setMessage] = useState('');
const onClickEnter = () => setMessage('안녕하세요!');
const onClickLeave = () => setMessage('안녕히 가세요!');

const [color, setColor] = useState('black');
return (
<div>
<button onClick={onClickEnter}>입장</button>
<button onClick={onClickLeave}>퇴장</button>
<h1 style={{ color }}>{message}</h1>

<button style={{ color: 'red' }} onClick={() => setColor('red')}>
빨간색
</button>

<button style={{ color: 'green' }} onClick={() => setColor('green')}>
초록색
</button>

<button style={{ color: 'blue' }} onClick={() => setColor('blue')}>
파란색
</button>
</div>
);
};
export default Say;

클릭시 텍스트 색상이 바뀌게 된다.

state를 사용할 때 주의 사항

state 값을 바꾸어야 할때는 setState 혹은 전달받은 setter함수를 이용해야 한다.
배열이나 객체를 업데이트 할때는 사본을 만들고 사본의 값을 업데이트 한 후, 그 사본의 상태값을 변경해줘야한다.
객체에 대한 사본을 만들 때는 spread 연산자라 불리는 …을 사용하여 처리한다.
배열에 대한 사본을 만들 때는 배열 내장 함수를 이용한다(filter, concat 등))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//사본을 만들어 업데이트 하는 예시

//객체 다루기
const object = {a:1, b:2, c:3};
const nextObject = {...object, b:2}; //사본 생성

//배열
const array = [
{id :1, value :true},
{id :2, value :false}
];

let nextArry = array.concat({id :4}); //새 항목 추가
nextArry.filter(item => item.id !=2); //id가 2인 항목 제거

Comentarios

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×