안녕하세요! delay100입니다. 이번 포스팅에서는 Component에 대해 공부해봅시다.
대부분의 설명은 주석으로 달아놓았으니 코드에 대한 설명은 주석을 확인해주세요!
책 리액트를 다루는 기술, 개정판의 3장 내용을 다루고 있습니다.
이번 포스팅의 Github 링크
https://github.com/delay-100/study-react/tree/main/ch3/hello-react
1. Component 기본
Component(컴포넌트)란?
- 데이터가 주어졌을 때 UI를 만들어줌
- 라이프사이클 API를 이용해 컴포넌트가 화면에서 나타날때 등 변화가 일어날 때 주어진 작업을 처리할 수 있음
- 임의 메서드를 만들어 특별한 기능을 붙여줄 수 있음
함수 컴포넌트 VS 클래스형 컴포넌트?
=> 리액트 공식 메뉴얼에서는 컴포넌트를 새로 작성 시, 함수 컴포넌트와 Hooks를 사용하도록 권장하고 있음
1. 함수 컴포넌트 특징
- 클래스형 컴포넌트보다 선언하기 편함
- 메모리 자원을 (클래스형 컴포넌트보다) 덜 사용함
- 프로젝트를 완성하여 빌드 시 결과물의 파일 크기가 (클래스형 컴포넌트보다) 더 작음
=> 사실, 클래스형 컴포넌트와 별 차이가 없으므로 이 특징은 중요하진X
- state와 라이프사이클 API의 사용이 불가능함
=> 리액트 v16.8 이후 Hooks라는 기능이 도입되면서 가능하게 됨
- 함수 컴포넌트 예시 - src/App.js 中 함수형 컴포넌트
// 함수 컴포넌트 import './App.css'; class App() { const name = "리액트"; return <div className="react">{name}</div>; } }
2. 클래스형 컴포넌트 특징
- render 함수가 꼭 있어야 함
- render 함수 내에 보일 JSX를 반환해야 함
- state 기능 및 라이프 사이클 기능을 사용할 수 있음
- 임의 메서드를 정의할 수 있음
- 클래스형 컴포넌트 예시 - src/App.js 中 클래스형 컴포넌트
// 클래스형 컴포넌트 import { Component } from "react"; class App extends Component { render() { const name = "react"; return <div className="react">{name}</div>; } } export default App;
초반부에서는 클래스형 컴포넌트로 감을 익히고, 8장 이후에 함수 컴포넌트와 Hooks를 해보겠습니다.
책이 그렇게 하기 때문에 저도 한번 따라가 볼게요
2. Component 생성
- 컴포넌트 생성 예시(모듈 내보내기, export) - src/MyComponent.js
const MyComponent = () => {
return <div>나의 새롭고 멋진 컴포넌트</div>;
};
export default MyComponent; // 모듈 내보내기, 다른 파일에서 이 파일을 import할 때, 위에서 선언한 MyComponent 클래스를 불러오도록 설정함
* () => {} 는 화살표 함수인데, 이전 포스팅에서 다뤘습니다. (여기 클릭)
+ 화살표 함수 추가 설명
- 주로 함수(function)를 파라미터로 전달할 때 유용함
- 기존 function을 대체할 수 없는데, 용도가 다르기 때문임
=> 일반 함수(function)는 자신이 종속된 객체를 this로 가리키며, 화살표 함수(() => {})는 자신이 종속된 인스턴스를 가리킴
- 컴포넌트 생성 예시(모듈 불러오기, import) - src/App.js
import MyComponent from "./MyComponent"; // 모듈 불러오기
const App = () => {
return <MyComponent />;
};
export default App;
3. props
props란?
- properties를 줄인 표현
- 컴포넌트 속성을 설정할 때 사용하는 요소
- 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값
- 컴포넌트 자신은 해당 props를 읽기 전용으로만 사용할 수 있음(props를 바꾸려면 부모 컴포넌트에서 바꿔야 함)
- props 값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트(2. Component 생성에서는 App 컴포넌트가 부모 컴포넌트임)에서 설정 가능
3-1. props 기본
- props로 Component 생성 예시 - src/App.js
// props 사용한 Component 생성
import MyComponent from "./MyComponent";
const App = () => {
// return <MyComponent name="React" />; // src/MyComponent.js의 props.name 값
return <MyComponent>리액트</MyComponent>; // src/MyComponent.js의 props.children 값
};
export default App;
- props로 Component 생성 예시 - src/MyComponent.js
const MyComponent = (props) => {
return (
<div>
안녕하세요. 제 이름은 {props.name}입니다. <br />
children 값은 {props.children}
입니다.
</div>
);
};
/* src/App.js에 MyComponent에 name이 없는 경우 props.name에 아무것도 출력되지 않는데,
이 때 기본값을 주고 싶은 경우 defaultProps를 사용함 */
MyComponent.defaultProps = {
name: "기본 이름",
};
export default MyComponent;
3-2. 비구조화 할당
위의 기본 props는 항상 props.name, props.children 처럼 꽤나 길게 적어야합니다. 이 작업을 보다 더 편하게 하기 위해 비구조화 할당을 이용합니다.
그럼, 비구조화 할당 문법을 통해 props 내부의 값을 추출해봅시다.
- 비구조화 할당으로 props를 추출해 Component 생성 예시 - src/MyComponent.js
const MyComponent = ({ name, children }) => {
return (
<div>
안녕하세요, 제 이름은 {name}입니다. <br />
children 값은 {children}
입니다.
</div>
);
};
MyComponent.defaultProps = {
name: "기본 이름",
};
export default MyComponent;
- 비구조화 할당으로 props를 추출해 Component 생성 예시 - src/App.js
// // pros 사용한 Component 생성
import MyComponent from "./MyComponent";
const App = () => {
// return <MyComponent name="React" />; // src/MyComponent.js의 props.name 값
// return <MyComponent>리액트</MyComponent>; // src/MyComponent.js의 props.children 값
return <MyComponent name="React">리액트</MyComponent>;
};
export default App;
결과를 보면 3-1. props 기본과 같다는 것을 확인할 수 있습니다.
3-3. PropTypes
컴포넌트의 props의 타입을 지정, 필수 props 지정할 때 propTypes를 사용합니다.
- 비구조화 할당 & PropTypes 이용해 Component 생성 예시 - src/MyComponent.js
import PropTypes from "prop-types";
const MyComponent = ({ name, favoriteNumber, children }) => {
return (
<div>
안녕하세요, 제 이름은 {name}입니다. <br />
children 값은 {children}
입니다.
<br />
제가 좋아하는 숫자는 {favoriteNumber}입니다.
</div>
);
};
MyComponent.defaultProps = {
name: "기본 이름",
};
MyComponent.propTypes = {
name: PropTypes.string, // props의 타입 지정, 무조건 name 값이 문자열(string) 형태로 전달되어야 함
favoriteNumber: PropTypes.number.isRequired, // 필수 props 지정
};
export default MyComponent;
- 비구조화 할당 & PropTypes 이용해 Component 생성 예시 - src/App.js
// props 사용한 Component 생성
import MyComponent from "./MyComponent";
const App = () => {
// return <MyComponent name="React" />; // src/MyComponent.js의 props.name 값
// return <MyComponent>리액트</MyComponent>; // src/MyComponent.js의 props.children 값
// return <MyComponent name="React" >리액트</MyComponent>;
return (
<MyComponent name="React" favoriteNumber={1}>
리액트
</MyComponent>
);
};
export default App;
+더 많은 PropTypes 종류
- array: 배열
- arrayOf(다른 PropType): 특정 PropType으로 이루어진 배열. ex) arrayOf(PropTypes.number) - 숫자로 이루어진 배열
- bool: true 혹은 false 값
- func: 함수
- number: 숫자
- object: 객체
- string: 문자열
- symbol: ES6의 Symbol
- node: 렌더링할 수 있는 모든 것(숫자, 문자열, 혹은 JSX코드, children도 nodePropType임
- instanceOf(클래스): 특정 클래스의 인스턴스. ex) instanceOf(MyClass))
- oneOf(['dog', 'cat']): 주어진 배열 요소 중 값 하나
- oneOfType([React.PropTypes.string, PropTypes.number]): 주어진 배열 안의 종류 중 하나
- objectOf(React.PropTypes.number): 객체의 모든 키 값이 인자로 주어진 PropType인 객체
- shape({ name: PropTypes.string, num: PropTypes.number }): 주어진 스키마를 가진 객체
- any: 아무 종류
+proptypes에 대한 자세한 정보는 여기서 확인할 수 있습니다.
3-4. 클래스형 컴포넌트
클래스형 컴포넌트에서 props를 사용하는 경우에는 render 함수에서 this.props를 조회하면 됩니다.
defaultProps와 propTypes는 똑같은 방식으로 설정할수도 있고, class 내부에서 지정할 수도 있습니다.
- 클래스형 컴포넌트로 Component 생성 예시 - src/MyComponent.js
import { Component } from "react";
import PropTypes from "prop-types";
// 클래스형 컴포넌트
class MyComponent extends Component {
// 클래스형 컴포넌트에서는 class 내부에서 defaultProps와 propTypes 설정 가능
static defaultProps = {
name: '기본 이름'
};
static propTypes = {
name: PropTypes.string,
favoriteNumber: PropTypes.number.isRequired
};
render() {
const { name, favoriteNumber, children } = this.props; // 비구조화 할당
return (
<div>
안녕하세요, 제 이름은 {name}입니다. <br />
children 값은 {children}
입니다.
<br />
제가 좋아하는 숫자는 {favoriteNumber}입니다.
</div>
);
}
}
// // 함수, 클래스형 컴포넌트 사용 시 아래의 형태로 defaultProps,propTypes 사용 가능
// MyComponent.defaultProps = {
// name: "기본 이름",
// };
// MyComponent.propTypes = {
// name: PropTypes.string, // props의 타입 지정, 무조건 name 값이 문자열(string) 형태로 전달되어야 함
// favoriteNumber: PropTypes.number.isRequired, // 필수 props 지정
// };
export default MyComponent;
- 클래스형 컴포넌트로 Component 생성 예시 - src/App.js
// props 사용한 Component 생성
import MyComponent from "./MyComponent";
const App = () => {
// return <MyComponent name="React" />; // src/MyComponent.js의 props.name 값
// return <MyComponent>리액트</MyComponent>; // src/MyComponent.js의 props.children 값
// return <MyComponent name="React" >리액트</MyComponent>;
return (
<MyComponent name="React" favoriteNumber={1}>
리액트
</MyComponent>
);
};
export default App;
4. state
리액트에서 state는 컴포넌트 자체적으로 지닌 값으로, 컴포넌트 내부에서 바뀔수 있는 값을 의미합니다.
리액트에는 두 가지 종류의 state가 존재합니다.
- 클래스형 컴포넌트가 지니고 있는 state
- 함수 컴포넌트에서 useState라는 함수를 통해 사용하는 state
4-1. 클래스형 컴포넌트
- state를 사용한 클래스형 컴포넌트 예시1 - src/Counter.js
// 예시1 - constructor 과 this.setState 사용하는 Counter (값 + 1)
import { Component } from "react";
class Counter extends Component {
// 컴포넌트에 state를 설정할 때는 다음과 같이 constructor 메서드를 작성하여 설정함
// 컴포넌트 생성자 메서드
constructor(props) {
super(props); // 클래스형 컴포넌트에서 constructor를 작성할 때는 반드시 이 문장을 호출해야 함
// 함수 호출 시 현재 클래스형 컴포넌트가 상속받고 있는 리액트의 Component 클래스가 지닌 생성자 함수를 호출해줌
// this.state에 (state의) 초기값 설정하기, component의 state는 객체 형식이어야 함
this.state = {
number: 0,
fixedNumber: 0,
};
}
render() {
const { number, fixedNumber } = this.state; // state를 조회할 때는 this.state로 조회
return (
<div>
<h1>{number}</h1>
<h2>바뀌지 않는 값: {fixedNumber}</h2>
<button
// onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정합니다.
// button안에 onClick이라는 값을 props로 넣어줌 => 이벤트 설정
onClick={() => {
// this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
this.setState({ number: number + 1 }); // number 변수의 값만 1을 증가시켜줌
}}
>
+1
</button>
</div>
);
}
}
export default Counter;
- state를 사용한 클래스형 컴포넌트 예시1 - src/App.js
// state를 사용한 클래스형 컴포넌트
import Counter from "./Counter";
const App = () => {
return <Counter />;
};
export default App;
- state를 사용한 클래스형 컴포넌트 예시2 - src/Counter.js
// 예시2 - Counter (number + 1)
import { Component } from 'react';
class Counter extends Component {
// constructor 메서드를 선언하지 않고 state 초기값을 설정하는 방법
state = {
number: 0,
fixedNumber: 0,
};
render() {
const { number, fixedNumber } = this.state; // state를 조회할 때는 this.state로 조회
return (
<div>
<h1>{number}</h1>
<h2>바뀌지 않는 값: {fixedNumber}</h2>
<button
// onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정합니다.
// button안에 onClick이라는 값을 props로 넣어줌 => 이벤트 설정
onClick={() => {
// this.setState((prevState) => {
// // prevState: 기존 상태, props: 현재 지니고 있는 props(생략 가능)
// return {
// // 업데이트 하고 싶은 내용
// number: prevState.number + 1,
// };
// });
// // 위 코드와 아래 코드는 완전히 똑같은 기능을 합니다.
// // 아래 코드는 함수에서 바로 "객체"를 반환한다는 의미입니다.
// this.setState((prevState) => ({
// number: prevState.number + 1,
// }));
// setState를 사용해 값을 업데이트 한 후 특정 작업을 하고 싶은 경우 setState의 두 번째 파라미터로 콜백(callback) 함수를 등록하여 작업을 처리할 수 있음
this.setState(
{
number: number + 1,
},
() => {
console.log('방금 setState가 호출되었습니다.');
console.log(this.state);
}
);
// 화살표함수에서 값을 바로 반환하고 싶다면 {}를 생략하면 됨
// ex) const sum = (a, b) => a + b;
}}
>
+1
</button>
</div>
);
}
}
export default Counter;
- state를 사용한 클래스형 컴포넌트 예시2 - src/App.js
// state를 사용한 클래스형 컴포넌트
import Counter from './Counter';
const App = () => {
return <Counter />;
};
export default App;
4-2. 함수 컴포넌트
리액트 16.8 이전 버전에서는 함수 컴포넌트에서 state를 사용할 수 없었습니다.
하지만 리액트 16.8 버전 이후부터는 useState라는 함수를 사용해 함수 컴포넌트에서도 state를 사용할 수 있게 되었습니다.
이 과정에서 위에서 잠깐 언급한 Hooks를 사용하게 되는데, 여기서는 useState만 사용해보겠습니다.
Hooks를 사용하기 전에, 비구조화 할당(배열 안에 들어 있는 값을 쉽게 추출할 수 있도록 해주는 문법)을 잠깐 알아봅시다.
- 비구조화 할당 예시
// 일반적으로 배열 값을 사용하는 방법
const array1 = [1, 2];
const one = array1[0];
const two = array1[1];
// 배열 비구조화 할당
const array2 = [1, 2];
const [one, two] = array2;
- useState를 사용한 함수 컴포넌트 예시 - src/Say.js
import { useState } from 'react';
const Say = () => {
const [message, setMessage] = useState(''); // useState 함수의 인자에 상태의 초기값을 넣어줌
// 클래스형 컴포넌트에서 state 초기값은 객체 형태를 넣어주어야 하지만, useState에서는 반드시 객체가 아니어도 상관 없음. 값의 형태는 자유임(숫자, 문자, 객체, 배열 등..)
// 함수 호출 시 배열이 반환되는데, 배열의 첫 번째 원소: 현재 상태, 두 번째 원소: 상태를 바꾸어 주는 함수(세터(setter) 함수라고 함)
// 배열의 비구조화 할당을 통해 이름을 자유롭게 정해 줄 수 있음(현재는 message, setMessage로 이름을 지음)
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;
- useState를 사용한 함수 컴포넌트 예시 - src/App.js
// useState를 사용한 함수 컴포넌트
import Say from './Say';
const App = () => {
return <Say />;
};
export default App;
4-3. 주의 사항
state를 사용할 때는 주의해야할 사항이 있습니다. state 값을 바꿔야 할 때는 setState 또는 useState를 통해 전달받은 세터 함수를 사용해야 합니다.
아래의 코드는 잘못된 state 사용 예입니다.
// 클래스형 컴포넌트에서..
this.state.number = this.state.number + 1;
this.state.array = this.array.push(2);
this.state.object.value = 5;
// 함수 컴포넌트에서..
const [object, setObject] == useState({ a:1, b: 1});
object.b = 2;
배열이나 객체를 업데이트 할때는 아래의 방법처럼 해야합니다.
배열이나 객체 사본을 만들고, 그 사본에 값을 업데이트한 후,
그 사본의 상태를 setState 혹은 세터 함수를 통해 업데이트를 합니다.
// 객체 다루기
const object = { a:1, b: 2, c:3 };
const nextObject = { ...object, b: 2}; // 사본을 만들어서 b 값만 덮어 쓰기
// 객체의 사본을 만들 때는 spread 연산자라 불리는 ...을 사용하여 처리
// 배열의 사본을 만들 때는 배열의 내장 함수들을 활용함
// 배열 다루기
const array = [
{ id: 1, value: true },
{ id: 2, value: true },
{ id: 3, value: false}
];
let nextArray = array.concat({ id: 4}); // 새 항목 추가
nextArray.filter(item => item.id !== 2); // id가 2인 항목 제거
nextArray.map(item => (item.id === 1 ? { ...item, value: false} : item)); // id가 1인 항목의 value를 false로 설정
이렇게 오늘은 Component에 대해 다뤄보았습니다.
읽어주셔서 감사합니다. 잘못된 정보는 댓글로 알려주세요!
'Study > React' 카테고리의 다른 글
6. map (0) | 2022.07.13 |
---|---|
5. ref (0) | 2022.07.12 |
4. 이벤트 핸들링 (0) | 2022.07.11 |
2. JSX (0) | 2022.07.05 |
1. React 시작하기(yarn 설치 및 실행) (0) | 2022.07.04 |