상세 컨텐츠

본문 제목

<리액트를 다루는 기술> 3장: 컴포넌트

21-22/21-22 리액트 스타터 -2

by Kimpeep 2021. 11. 8. 18:49

본문

728x90

INDEX

3장 컴포넌트

1. 클래스형 컴포넌트

● 컴포넌트를 선언하는 두 가지 방식

2. 첫 컴포넌트 생성

2.1 src 디렉터리에 MyComponent.js 파일 생성

2.2 코드 작성하기

2.3 모듈 내보내기 및 불러오기

3. props

3.1 JSX 내부에서 props 렌더링

3.2 컴포넌트 사용 시 props 값 지정하기

3.3 props 기본값 설정: defaultProps

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

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

3.6 propTypes를 통한 props 검증

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

4. state

4.1 클래스형 컴포넌트의 state

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

5. state를 사용할 때 주의 사항

● 주의 사항

6. Question 개념 정리 및 코드 문제

● 개념 복습 문제

● 코드 문제


컴포넌트

애플리케이션의 사용자 인터페이스를 구성하는 요소 단위. 단순한 템플릿 이상의 기능을 갖추고 있다.

  • 데이터가 주어지면 UI를 구성
  • 라이프사이클 API 이용: 컴포넌트가 화면에서 나타났을 때, 사라질 때, 변화가 일어날 때에 작업 수행 지시 가능
  • 임의 메서드 작성 가능

* 라이프사이클: 컴포넌트의 수명 주기. 페이지에 렌더링되기 전 준비 과정부터 페이지에서 사라질 때까지를 이른다. (7장에서 다룸)

1. 클래스형 컴포넌트

●  컴포넌트를 선언하는 두 가지 방식

함수 컴포넌트

  • 클래스형 컴포넌트에 비해 선언하기 편리
  • 메모리 자원 적게 사용, 빌드 후 결과물의 파일 용량이 적음 (클래스형 컴포넌트와 큰 차이 없음)
  • state 및 라이프사이클 API 사용 불가능 (리액트 v16.8에서 Hooks 기능 도입되며 해결)
import '/App.css';

function App() {
	const name = '리액트';
    return <div className="react">{name}</div>;
    }
    
export default App;

클래스형 컴포넌트

  • state 및 라이프사이클 API 사용 가능
  • 임의 메서드 정의 가능
  • render 함수 반드시 필요(JSX 반환)
import { Component } from 'react';

class App extends Component {
	render() {
    	const name='react';
        return <div className="react">{name}</div>;
       }
   }    
export default App;

2. 첫 컴포넌트 생성

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


2.1  src 디렉터리에 MyComponent.js 파일 생성


2.2  코드 작성하기

MyComponent.js 파일을 열고 아래 코드를 작성한다.

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

export default MyComponent;

자바스크립트 ES6에서 도입된 화살표 함수 선언 방식을 이용했다.

이렇게 선언한 컴포넌트도 함수형 컴포넌트가 된다.

* 화살표 함수 문법 ▶ https://corner-ds.tistory.com/15#ArrowFunc

 


2.3  모듈 내보내기 및 불러오기

모듈 내보내기 (export)

방금 작성한 코드의 맨 아래 줄이 다른 파일에서 이 파일을 import할 때 불러올 수 있는 함수를 지정하는 코드이다.

export default func1;

모듈 불러오기 (import)

App.js에서 MyComponent를 불러와서 사용하기 위해 수정한 코드.

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

export default App;

3. props (속성)

propsproperties의 줄임말로, 컴포넌트 속성을 설정할 때 사용하는 요소

props 값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트에서 설정할 수 있다.


3.1 JSX 내부에서 props 렌더링

 MyComponent 컴포넌트를 수정하여 해당 컴포넌트에서 name이라는 props를 렌더링하도록 설정해보자.

props 값은 컴포넌트 함수매개변수로 받아 와서 사용할 수 있다.

props를 렌더링할 때 에는 2장에서 다뤘던 대로 JSX 내부에서 { } 기호로 감싸 주면 된다.

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

export default MyComponent;

3.2 컴포넌트 사용 시 props 값 지정하기

App 컴포넌트에서 name이라는 이름의 props 값을 지정하면 {props.name} 위치에 렌더링된다.

// App.js
const App = () => {
  return <MyComponent name="React" />;
};

export default App;


3.3 props 기본값 설정: defaultProps

defaultProps으로 props 값을 지정하지 않았을 때 보여줄 기본값을 설정할 수 있다.

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

MyComponent.defaultProps = {
  name: '기본 이름'
};
// App.js
const App = () => {
  return <MyComponent />;
};

export default App;


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

children은 컴포넌트 태그 사이의 내용을 보여주는 props이다.

// App.js

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

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

};

MyComponent.defaultProps = {
  name: '기본 이름'
};

export default MyComponent;


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

MyComponent에서 props 값을 조회할 때마다 props 키워드를 붙여 사용하고 있다.

예) props.name, props.children

ES6의 비구조화 문법을 사용하면 객체에서 값을 추출할 수 있어 다음과 같이 간략하게 작성할 수 있다.

* 비구조화(구조 분해) 문법 ▶ https://corner-ds.tistory.com/24#Destructed_Assignment

 

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

};

MyComponent.defaultProps = {
  name: '기본 이름'
};

export default MyComponent;

비구조화 문법은 구조 분해 문법이라고도 불리며, 함수의 매개변수 부분에서도 사용할 수 있다.

함수의 매개변수가 객체라면 그 값을 바로 비구조화해서 사용하는 것이다.

다음과 같이 수정할 수 있다.

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

MyComponent.defaultProps = {
  name: '기본 이름'
};

export default MyComponent;

3.6 propTypes 를 통한 props 검증

컴포넌트의 필수 props를 지정하거나 props의 타입(자료형)을 지정할 때는 propTypes를 사용한다.

우선 코드 상단에 import 구문을 사용하여 불러와야 한다.

// MyComponent.js
import PropTypes from 'prop-types';

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

Mycomponent.defaultProps = {
  name: '기본 이름'
};

MyComponent.propTypes = {
  name: PropTypes.string  // name 값은 무조건 문자열(string) 형태로 전달
};

export default MyComponent;

만약 컴포넌트에 설정한 props가 propTypes에서 지정한 형태와 일치하지 않는다면,

웹페이지에 값이 출력되기는 하지만 브라우저 개발자 도구의 Console 탭에 다음과 같은 경고가 나타난다.

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

propType를 지정하지 않았을 때 경고 메시지를 띄우려면, 뒤에 .isRequired를 붙여 주면 된다.

favoriteNumber라는 숫자를 필수 props로 지정해보자.

// MyComponent.js
const MyComponent = ({ name, favoriteNumber, children }) => {
	return (
    <div>
      안녕하세요, 제 이름은 {name}입니다. <br />
      children 값은 {children}
      입니다.
      <br />
      제가 좋아하는 숫자는 {favoriteNumber}입니다.
    </div>
  );
};

MyComponent.defaultProps = {
  name: '기본 이름'
};

MyComponent.propTypes = {
  name: PropTypes.string,
  favoriteNumber: PropTypes.number.isRequired
};

export default MyComponent;
// App.js
const import_Component = () => {
  return <MyComponent name={'React'} favoriteNumber={'안녕'}>
    리액트
  </MyComponent>;
};

export default import_Component;

이때 favoriteNumber의 값을 지정하지 않으면 경고가 나타난다.

* 더 많은 PropTypes 종류

• array: 배열

• arrayOf(다른 PropType): 특정 PropType으로 이루어진 배열을 의미한다. 예를 들어 arrayOf(PropTypes.number)는 숫자로 이루어진 배열이다.

• bool: true 혹은 false 값

• func: 함수

• number: 숫자

• object: 객체

• string: 문자열

• symbol: ES6의 Symbol

• node: 렌더링할 수 있는 모든 것(숫자, 문자열, 혹은 JSX 코드. children도 node PropType이다.)

• instanceOf(클래스): 특정 클래스의 인스턴스(예: instanceOf(MyClass))

• oneOf(['dog', 'cat']): 주어진 배열 요소 중 값 하나

• oneOfType([React.PropTypes.string, PropTypes.number]): 주어진 배열 안의 종류 중 하나

• objectOf(React.PropTypes.number): 객체의 모든 키 값이 인자로 주어진 PropType인 객체

• shape({ name: PropTypes.string, num: PropTypes.number }): 주어진 스키마를 가진 객체

• any: 아무 종류

 

더 자세한 정보는 https://github.com/facebook/prop-types에서 확인할 수 있다.


4. state(상태)

  • state(상태): 컴포넌트 내부에서 바뀔 수 있는 값
  • props(속성): 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값으로, 부모 컴포넌트에서 변경 가능
  • 컴포넌트 자신은 해당 props를 읽기 전용으로만 사용할 수 있음

리액트의 두 가지 종류의 state

  • 클래스형 컴포넌트가 지니고 있는 state
  • 함수 컴포넌트에서 useState라는 함수를 통해 사용하는 state

4.1 클래스형 컴포넌트의 state

src 디렉터리에 Counter.js 파일을 생성하여 다음 코드를 작성해보자.

// Counter.js
import { 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;

위 파일에서 각 코드의 역할

  • constructor 메서드(생성자)를 작성해 컴포넌트에 state를 설정
    super(인자 리스트); 가 호출되면 현재 클래스형 컴포넌트(Counter)의
    부모 클래스(react의 Component 클래스)가 지닌 생성자 함수를 호출함
  • this.state 값에 초기값 설정
    컴포넌트의 state는 객체 형식이어야 함
  • render 함수
    현재 state를 조회할 때는 this.state로 조회
    onclick을 props로 넣어주어, 버튼이 클릭될 때 호출시킬 함수 설정 (='이벤트를 설정한다'라고 하며, 4장에서 다룸)
// App.js
import Counter from './Counter';

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

export default App;

4.1.① state 객체 안에 여러 값이 있을 때

state 안에 새 값을 설정했어도 this.setState 함수의 인자로 전달하지 않으면  함수 호출 시에도 값이 바뀌지 않는다.

// Counter.js
(...)
  constructor(props){
    super(props);
    // state의 초기값 설정
    this.state ={
      number: 0,
      fixedNumber: 0 // fixedNumber 추가
    };
  }
  render() {
    const { number, fixedNumber } = this.state; // fixedNumber 추가
(...)
        <h2>바뀌지 않는 값: { fixedNumber } </h2>
        <button
        // onClick을 통해 버튼이 클릭되었을 때 호출할 함수 지정
        onClick={ () => {
          // this.setState를 사용하여 state에 새로운 값을 넣을 수 있음
          // 이곳에 fixedNumber를 수정할 만한 명령이 없음
          this.setState({ number: number + 1});
(...)

4.1.② state를 constructor에서 꺼내기

constructor 메서드(생성자)를 선언하는 방법 외에 state의 초기값을 지정하는 또 다른 방법

// Counter.js
(...)
class Counter extends Component {
  state = {  // 이것을 추가한다
    number: 0,
    fixedNumber: 0
  };
  render() {
    const{ number, fixedNumber } = this.state;
    return (...);
  }
}

export default Counter;

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

this.setState를 사용하여 state에 새로운 값을 업데이트할 때는 상태가 비동기적으로 업데이트된다.

- 다음과 같이 onClick에 설정한 함수 내부에서 this.setState를 두 번 호출해도 값은 1만 더해진다.

- this.setState를 사용한다고 해서 state 값이 바로 바뀌지는 않기 때문이다.

// Counter.js - button onclick

onClick={() => {
          // this.setState를 사용하여 state에 새로운 값을 넣을 수 있음
          this.setState({ number: number+1 });
          this.setState({ number: this.state.number +1 });
        }}

해결책: this.setState를 사용할 때 객체 대신 함수를 인자로 넣기

※ 화살표 함수에서 값을 바로 반환하고 싶다면 코드 블록 { } 과 그에 포함된 return문을 생략

// Counter.js - button onclick

onClick={() => {
          // this.setState를 사용하여 state에 새로운 값을 넣을 수 있음
          this.setState(prevState => {
            return {
              number: prevState.number +1
            };
          });
          // 위 코드와 아래 코드는 완전히 똑같은 기능
          //아래 코드는 함수에서 바로 객체를 반환한다는 의미
          this.setState(prevState => ({
            number: prevState.number + 1
          }));
        }}

4.1.④ this.setState가 끝난 후 특정 작업 실행하기

setState의 두 번째 매개변수콜백 함수를 등록하여 작업 처리

// Counter.js - button onclick

onClick={() => {
            this.setState(
              { number: number + 1 },
              () => {
                console.log('방금 setState가 호출되었습니다.');
                console.log(this.state);
              }
            );
          }}

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

리액트 16.8 버전부터는 Hooks 기능 중 useState라는 함수를 사용하면, 함수 컴포넌트에서도 state를 이용할 수 있게 되었다.

4.2.① 배열 비구조화 할당

- 배열 비구조화 할당: 객체 비구조화 할당과 비슷하게, 배열 안의 값을 추출하는 문법

아래 코드는 array 안의 값을 one과 two에 각각 담는다.

const array = [1,2];
const one = array[0];
const two = array[1];
// 위 코드를 배열 비구조화 할당을 사용하면 아래와 같이 표현 가능
const array = [1, 2];
const [one, two] = array;

4.2.② useState 사용하기

src 디렉터리에 Say.js 파일을 새로 생성해 다음 코드를 작성

// Say.js
import { useState } from 'react';

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

  return (
    <div>
      <button onClick={onClickEnter}>입장</button>
      <button onClick={onClickLeave}>퇴장</button>
      <h1>{message}</h1>
    </div>
  );
};

export default Say;
사용 형태
const [현재 상태, 세터 함수] = useState(현재 상태의 초기값);
세터 함수(현재 상태에 설정할 값);

여기서 현재 상태가 state의 역할을 하는 셈이다.

 

● useState 함수의 인자에는 상태의 초기값을 기술

- 값의 형태는 자유 (숫자, 문자열, 객체, 배열, 객체 등 모두 가능)

- cf. 클래스형 컴포넌트에서는 state의 초기값을 반드시 객체 형태로 지정

 

● useState 함수를 호출하면 배열 반환

- 첫 번째 원소 : 현재 상태

- 두 번째 원소 : 상태를 바꾸어 주는 함수(=세터 함수)

 

// App.js
import Say from './Say';

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

export default App;

4.2.③ 한 컴포넌트에서 useState 여러 번 사용하기

여러 가지 상태(state)를 useState로 관리하는 코드를 작성해보자.

 

import { useState } from 'react';

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

  const [color, setColor] = useState('black');  // 새로 추가된 상태(state)

  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;

 


5. state를 사용할 때 주의 사항

● 주의 사항

    • 값을 변경하려면 상태(state)에 직접 접근해서는 안 되고,
      setState 혹은 useState를 통해 전달받은 세터 함수를 사용해야 한다.
      다음 코드는 잘못된 코드이다.
      // 클래스형 컴포넌트에서...
      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 혹은 세터 함수를 통해 업데이트해야 한다.
    - 객체에 대한 사본을 만들 때: spread 연산자라 불리는 ...을 사용하여 처리

    - 배열에 대한 사본을 만들 때: 배열의 내장 함수들을 활용
    (자세한 내용은 추후 살펴볼 예정)
// 객체 다루기
const object = { a: 1, b: 2, c: 3 };
const nextObject = { ...object, b: 2 ); // 사본을 만들어 b 값만 덮어 쓰기

// 배열 다루기
const array = [
  { id: 1, value: true },
  { id: 2, value: true },
  { id: 3, value: false }
};
let nextArray = array.concat({ id: 2}); // 새 항목 추가
nextArray.filter(item -> item.id !==2); // id가 2인 항목 제거
nextArray.map(item => (item.id === 1 ? { ...item, value: false } : item)); // id가 1인 항목의 value를 false로 설정

 


6. Question 개념 정리 및 코드 문제

● 개념 복습 문제

 

1. 클래스형 컴포넌트에서는 state 기능 및 라이프사이클 기능을 사용할 수 있고, (JSX)를 반환하는 (render) 함수를 반드시 필요로 한다.

2. props는 컴포넌트가 사용되는 과정에서 (컴포넌트 자신/부모 컴퍼넌트)가 설정하는 값이다.

3. 리액트의 state는 (클래스형) 컴포넌트가 지니고 있는 state, (    함수    ) 컴포넌트에서 (useState)라는 함수를 통해 사용하는 state로 두 가지 종류가 있다.

4. 클래스형 컴포넌트에서 constructor 메서드는 (생성자)의 역할을 한다.

5. useState 함수 사용하는 방법

const [현재 상태, 세터 함수] = useState(현재 상태의 초기값);

세터 함수(현재 상태에 설정할 값);

6. state의 값을 변경하려면 직접 접근해서는 안 되고 (setState) 혹은 (useState)를 통해 전달받은 (세터 함수)를 사용해야 한다.

7. 객체에 대한 사본을 만들 때에는 (spread) 연산자라 불리는 (...)을 사용하여 처리하고,
배열에 대한 사본을 만들 때에는 배열의 내장 함수들을 활용한다.

 

● 코드 문제

1. (1) 함수 매개변수를 비구조화하고 * Hint: 3.5 

(2) num 속성을 필수로 입력받게 하되 * Hint: 3.6 

(3) 숫자 자료형이 입력되게 하기 * Hint: 3.6 

// MyComponent.js
const MyComponent = props => {
  const { num, children } = props;
	return (
    <div>
      안녕하세요, 오늘 배울 내용은 {num}장입니다. <br />
      부모 태그 안의 내용은 {children} 입니다.
    </div>
  );

};

export default MyComponent;

2. constructor 메서드를 선언하는 방법을 사용하지 않고 state의 초기값을 지정하도록 코드 변경하기 * Hint: 4.1.② 

// Counter.js
import { Component } from 'react';

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

 


Corner React Starter #2

Editor 유즈

728x90

관련글 더보기