Javascript/React

JSX & Props & State

leeeee.yeon 2021. 7. 22. 16:33
Component

 

index.js에서부터 시작해보자.

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

여기서 <App />은 HTML이 아니다. 이런 것은 기본적으로 component라고 불린다.

리액트는 component와 함께 동작하며, 모든 것이 component이다. 앞으로 우리는 component를 만들게 될 것이고, component를 보기 좋게 수정해 나갈 것이다.

 

그래서 도대체 component가 뭘까?!

component는 HTML을 반환하는 함수이다.

import React from "react";

function App() {
  return <div>Hello, world!</div>
}

export default App;

 

다시 정리하면 App.js에서 HTML을 반환하는 App()이라는 함수는 component이고, index.js의 <App />은 component를 사용하는 형태이다.

그리고 <App/>과 같은 js와 HTML 사이의 조합을 jsx라고 부른다.

 

이번에는 component를 직접 만들어보자!

 

src 폴더 안에 Potato.js 파일을 만든다.

import React from "react";

function Potato(){
    return (
        <h3>I am talking potato ... ~~</h3>
    );
}

export default Potato;
  • component를 작성할 때마다 리액트를 import해준다. 그렇지 않으면 리액트는 jsx가 있는 component를 사용하는 것을 이해하지 못한다.
  • 함수를 만들고 HTML을 return 해준다.
  • 그리고 이것을 외부에서 사용할 수 있도록 export 해준다.

 

Potato라는 component를 만들었으니 이것을 이용해봐야겠지?!

 

리액트는 하나의 컴포넌트(component라고 일일이 쓰기 귀찮아짐 ㅎㅎ)만 렌더링했지만...! (강의 기준)

이제는 여러 개의 컴포넌트의 렌더링이 가능하다!!

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import Potato from './Potato';

ReactDOM.render(
  <React.StrictMode>
    <App />
    <Potato/>
  </React.StrictMode>,
  document.getElementById('root')
);

이렇게 index.js를 수정해주면 Potato 컴포넌트를 사용하게 되는 것이다.

 

잘 된 것을 확인할 수 있다 ^~^

 

 

Component의 재사용성

 

컴포넌트의 개념과 생성을 Potato🥔로 잘 실습하였다. 하지만 이제 실습이 끝났으니 삭제하자. 감자야 안뇽 ... ~~

그리고 수업에서는 일단 모든 컴포넌트를 App.js에서 작성한다고 했다. 나중에 리팩토링을 수업을 통해(수업에서 하지 않으면 개인적으로) 해야겠다.

 

우리가 jsx에서 두 번째로 이해해야 하는 것은, 컴포넌트에 정보를 보낼 수 있다는 점이다.

재사용이 가능한 컴포넌트를 만들면 우리는 이 컴포넌트를 반복해서 사용할 수 있다.

컴포넌트에서 children 컴포넌트로 정보를 보내는 방법에 대해 배워보자.

 

import React from "react";

function Food(props){
  console.log(props);
  return (
    <h3>I like Potato</h3>
  );
}

function App() {
  return <div>
    Hello, world!
    <Food fav="kimchi" bool={true} list={[1, 2, 3]}/>
    </div>
}

export default App;

여기서 fav는 property, kimchi는 value이다. 그리고 이를 합쳐서 props라고 한다.

props로 문자열, boolean, 배열 등 여러가지를 사용할 수 있다.

컴포넌트로 정보를 보내려고 할 때, props(속성)를 가져와 argument(인자)로 사용한다.

 

콘솔 출력을 통해 컴포넌트에 속성이 어떻게 전달되는지 보자.

 

prpps는 객체로 전달된 것을 볼 수 있다.

 

import React from "react";

function Food({ fav }){
  return (
    <h3>I like {fav}</h3>
  );
}

function App() {
  return <div>
    Hello, world!
    <Food fav="kimchi"/>
    </div>
}

export default App;

props.fav와 { fav }는 똑같은 기능을 한다. ( es6의 기능 )

fav 값이 kimchi이므로, Potato가 kimchi로 변한 것을 확인할 수 있을 것이다.

 

예상과 일치하는 것을 확인할 수 있다.

 

아래 코드처럼 컴포넌트를 여러 개 추가하면,

import React from "react";

function Food({ fav }){
  return (
    <h3>I like {fav}</h3>
  );
}

function App() {
  return <div>
    Hello, world!
    <Food fav="kimchi"/>
    <Food fav="chicken"/>
    <Food fav="pizza"/>
    <Food fav="pasta"/>
    <Food fav="ice cream"/>
    </div>
}

export default App;

각 컴포넌트의 props 값이 잘 전달되는 것을 확인할 수 있다.

 

 

웹사이트에 동적 데이터를 추가하는 방법

 

컴포넌트를 생성할 때마다 복사 붙여넣기를 하는 것은 비효율적이다.

그러므로 웹사이트에 동적 데이터를 추가하는 방법을 배워보자.

 

import React from "react";

function Food({ name, image }){
  return (
    <div>
      <h3>I like {name}</h3>
      <img src={image} width="200px"></img>
    </div>
  );
}

const myFood = [
  {
    name: "kimchi",
    image: "https://img.koreatimes.co.kr/upload/newsV2/images/202106/dd467848c7e349e9b456fea85ff36c1f.jpg/dims/resize/740/optimize"
  },
  {
    name: "chicken",
    image: "https://www.seriouseats.com/thmb/LJQ1jFVrlJbSb23MmK5iwHUr_EY=/1500x1125/filters:fill(auto,1)/__opt__aboutcom__coeus__resources__content_migration__serious_eats__seriouseats.com__2015__07__20210324-SouthernFriedChicken-Andrew-Janjigian-21-cea1fe39234844638018b15259cabdc2.jpg"
  },
  {
    name: "pizza",
    image: "https://www.recipetineats.com/wp-content/uploads/2020/05/Pizza-Crust-without-yeast_5-SQ.jpg"
  },{
    name: "pasta",
    image: "https://www.seriouseats.com/thmb/GSqpVkulyUZu-D6sPijmbFV_f4s=/1500x1125/filters:fill(auto,1)/__opt__aboutcom__coeus__resources__content_migration__serious_eats__seriouseats.com__2020__03__20200224-carretteira-pasta-vicky-wasik-21-ffe68515b25f4b348cbde845a59d6a62.jpg"
  },{
    name: "ice cream",
    image: "https://www.thatskinnychickcanbake.com/wp-content/uploads/2014/08/Oreo-Ice-Cream-1-480x360.jpg"
  }];

function App() {
  return (
    <div>
      {myFood.map(food => <Food name={food.name} image={food.image}/>)}
    </div>
  );
}

export default App;
  • 음식들의 정보를 myFood라는 배열에 객체로 저장해주었다.
  • map 함수를 이용해서 배열의 각 요소마다 함수를 실행해주도록 했다. ( 파이썬의 map 함수를 떠올리면 쉽다 )

그리고 map 함수 안에 쓰인 함수는 arrow function으로, 함수를 다른 형태로 표현한 것이다. 아래 함수와 동일한 함수이다.

{myFood.map(function(food){
	<Food name={food.name} />
})

공식 문서 참고: https://developer.mozilla.org/ko/docs/orphaned/Web/JavaScript/Reference/Functions/Arrow_functions

 

화살표 함수 | MDN

화살표 함수 표현(arrow function expression)은 function 표현에 비해 구문이 짧고  자신의 this, arguments, super 또는 new.target을 바인딩 하지 않습니다. 화살표 함수는 항상 익명입니다. 이  함수 표현은 메

developer.mozilla.org

 

결과

 

이번에는 map 함수도 별도의 함수로 만들어 코드를 리팩토링해보자.

function renderFood(food){
  return (
    <Food name={food.name} image={food.image}/>
  );
}

function App() {
  return (
    <div>
      {myFood.map(renderFood)}
    </div>
  );
}

 

근데 콘솔을 출력하면 이런 경고를 볼 수 있다.  각 객체에 id를 추가해주어 유일성을 부여해주자.

function renderFood(food){
  console.log(food);
  return (
    <Food key={food.id} name={food.name} image={food.image}/>
  );
}

그리고 컴포넌트에 key로 id를 전달해주면 경고를 없앨 수 있다. (실제 웹사이트에서 id는 전달, 사용하진 않음)

 

이런 경고창도 떴는데 img 태그 안에 alt 속성( 그림이 렌더링되지 못했을 때 나오는 문자열 )을 추가하여 해결할 수 있다.

 

props가 전달되었는지 확인

 

npm install -s prop-types

npm을 이용하여 prop-types를 설치하자. prop-types는 전달받은 props가 내가 원하는 props인지 확인해준다.

package.json에서 잘 설치된 것을 확인할 수 있다.

 

Food.propTypes = {
  name: propTypes.string.isRequired,
  picture: propTypes.string.isRequired,
  rating: propTypes.string.isRequired
};

이 코드를 App.js에 추가하고 reload를 해보자. ( rating은 string이 아닌데 일부러 틀린 값을 준 것 )

웹사이트는 정상적으로 작동하지만, 콘솔을 보면 경고창이 뜬 것을 볼 수 있다.

 

Food.propTypes = {
  name: propTypes.string.isRequired,
  image: propTypes.string.isRequired,
  rating: propTypes.number.isRequired
};

rating을 number로 바꿔주면 콘솔에 뜬 경고창이 사라진 것을 볼 수 있다.

string과 number 외에도 array, boolean 등을 사용할 수 있다. 또, 반드시 isRequired 메서드를 사용해야 하는 것이 아니다.

예를 들어 propTypes.number이라고 한다면 number 또는 undefined를 허용한다는 뜻이다.

또, 중요한 것이 있다! propTypes는 변수 이름이 항상 propTypes여야 한다!! ( 처음에 Proptypes 하니까 에러 나더라 ㅠ)

 

 

Class 컴포넌트와 State

 

props 학습을 마쳤으니 이제 Food 예제도 보내주자 안뇽 ... ~~

 

그리고 우리는 이제까지 App 컴포넌트를 function 컴포넌트로 사용했다. 이것을 class 컴포넌트로 바꾸자.

class 컴포넌트의 기본 틀은 아래와 같이 생겼다.

class App extends React.Component{

}

 

또, class 컴포넌트는 return을 가지고 있지 않는다는 특징이 있다. 그 대신 render 메서드를 가지고 있다.

 

한번 예시를 들어보자.

import React from "react";
import propTypes from "prop-types";

class App extends React.Component{
  render(){
    return <h1>I am class component</h1>
  }
}

export default App;

 

class 컴포넌트는 이런 식으로 작동한다!

 

function 컴포넌트와 비교하면, function 컴포넌트는 function이므로 무언가를 return하고 그 무언가가 화면에 표시된다.

class component는 extends 구문에서 볼 수 있듯이 react component로부터 확장되고, react가 render 메서드를 자동으로 작동시켜 내부의 내용을 화면에 표시된다.

 

그럼 왜 class 컴포넌트를 사용하는 것일까? 그것은 state 때문이다.

state는 객체이고, 안에 컴포넌트의 데이터를 넣을 공간이 있으며, 그 데이터는 변하는, 동적인 데이터이다.

 

react는 render function을 refresh하지 않는다. 즉, state를 바꾸고 싶으면 render 함수를 refresh해주어야 한다.

그러므로 this.state.count와 같이 state에 직접 접근하는 것이 아니라, setState라는 함수를 만들어야 한다.

 

아래 코드는 Add 버튼을 누르면 숫자가 올라가고, Minus 버튼을 누르면 숫자가 내려가는 counter을 만든 코드이다.

import React from "react";
import propTypes from "prop-types";

class App extends React.Component{
  state = {
    count: 0
  };
  
  add = () => {
    this.setState({count: this.state.count + 1});
  };
  minus = () => {
    this.setState({count: this.state.count - 1});
  };

  render(){
    return (
    <div>
      <h1>The number is {this.state.count}</h1>
      <button onClick={this.add}>Add</button>
      <button onClick={this.minus}>Minus</button>
    </div>
      );
  }
}

export default App;

 

하지만 this.state.count에 접근하는 것은 state에 의존하는 것이기 때문에 좋은 코드는 아니다. 그러므로 아래와 같이 this.state.count 대신 current를 사용하자.

이렇게 하면, state를 set할 때 외부의 상태에 의존하지 않을 수 있다.

add = () => {
  this.setState(current => ({count: current.count + 1}));
};
minus = () => {
  this.setState(current => ({count: current.count - 1}));
};

 

자!! 다시 정리하자!

setState를 호출하면 리액트는 새로운 state와 함께 render 함수를 호출한다 !

this.state.count로 접근하는 거 NoNo!

 

 

Component Life Cycle

 

life cycle method - 기본적으로 리액트가 컴포넌트를 생성, 제거하는 방법이다. ( Mounting, Updating, Unmounting )

 

컴포넌트가 render 되기 전에 호출되는 함수, 호출된 후 호출되는 함수, 업데이트될 때 호출되는 함수가 있다.

 

몇 가지 함수들을 살펴보며 Life Cycle에 대해 알아보자.

( 더 많은 함수는 https://ko.reactjs.org/docs/react-component.html 참고 )

 

Mounting - 컴포넌트가 생성되는 것

 

constructor() - 생성자

import React from "react";
import propTypes from "prop-types";

class App extends React.Component{
  constructor(props){
    super(props);
    console.log("constructor");
  }

  render(){
    console.log("render");
    return (
    <div>
    </div>
      );
  }
}

export default App;

콘솔 출력으로 비교했을 때, constructor() 메서드가 실행되고 나서 render 메서드가 실행된다.

 

컴포넌트가 생성될 때(= 화면에 표시될 때 = 컴포넌트가 웹사이트로 갈 때) 아래의 순서로 함수가 호출된다.

( 두번째 함수는 잘 쓰이지 않으므로 설명 skip )

 

constructor()과 render()은 앞에서 배웠고, 마지막 함수 componentDidMount()는 우리에게 '야! 이 컴포넌트 처음 render됐어!'하고 알려주는 메서드이다.

 

이제 Mount 관련 함수들의 호출 순서를 직접 확인해보자.

class App extends React.Component{
  constructor(props){
    super(props);
    console.log("constructor");
  }

  componentDidMount(){
    console.log("component rendered");
  }

  render(){
    console.log("render");
    return (
    <div>
      <h1>Life Cycle test - Check console</h1>
    </div>
      );
  }
}

확인 완료

 

Updating - 우리의 동작으로 인해 컴포넌트(state 등)가 업데이트되는 것이다.

( 첫번째, 두번째, 네번째 함수는 강의에서 설명하지 않고 skip한다. )

 

componentDidUpdate() 함수에 대해 실습해보자. App.js에 아래 코드를 추가하자. ( + counter 코드 안 쓸줄 알고 지웠는데 그것도 다시 작성하기 ^ㅅ^;; )

componentDidUpdate(){
	console.log("just updated");
}

Add 버튼을 눌렀을 때 state가 업데이트되고 render() 다음에 componentDidUpdate() 함수가 호출되는 것을 확인할 수 있다.

 

Unmounting - 컴포넌트가 죽는 것, 다른 페이지로 갈 때, state를 이용하여 컴포넌트 교체 등 컴포넌트가 죽는 것에는 다양한 방법이 있다.

Unmount될 때 호출되는 함수는 componentWillUnmount()만 호출된다. 실습 긔긔 아래 코드 추가추가

componentWillUnmount(){
	console.log("good bye component...T^T");
}

 

아래는 결과결과...를 보여주려고 했지만 refresh를 할 때 콘솔에 출력되는 것을 확인하지 못한다 ㅜ.ㅜ 그래도 니꼬를 믿자..!

 

끗 ~