React Component 만들기

 

JSX 문법

React Component 만드는 법

props 와 state

DOM 이벤트 다루기

컴포넌트 라이프사이클

Lead Software Engineer @ProtoPie

Microsoft MVP

TypeScript Korea User Group Organizer

Marktube (Youtube)

Mark Lee

이 웅재

JSX

JSX

JSX 문법으로 작성된 코드는 순수한 JavaScript 로 컴파일 하여 사용한다.

누가 해주나요?? => babel

JSX

JSX 문법 => React.createElement

<!-- ex6.html : React.createElement => JSX -->
<!DOCTYPE html>
<html lang="en">
  <head>...</head>
  <body>
    <div id="root"></div>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
      //   React.createElement(
      //     type, // 태그 이름 문자열 | React 컴포넌트 | React.Fragment
      //     [props], // 리액트 컴포넌트에 넣어주는 데이터 객체
      //     [...children] // 자식으로 넣어주는 요소들
      //   );

      // 1. 태그 이름 문자열 type
      //   ReactDOM.render(
      //     React.createElement('h1', null, `type 이 "태그 이름 문자열" 입니다.`),
      //     document.querySelector('#root'),
      //   );

      //   ReactDOM.render(
      //     <h1>type 이 "태그 이름 문자열" 입니다.</h1>,
      //     document.querySelector('#root'),
      //   );

      // 2. React 컴포넌트 type
      //   const Component = props => {
      //     return React.createElement('p', null, `나는 컴포넌트입니다.`);
      //   };

      //   ReactDOM.render(
      //     React.createElement(
      //       Component,
      //       null,
      //       `type 이 "React 컴포넌트" 입니다.`
      //     ),
      //     document.querySelector("#root")
      //   );

      //   const Component = props => {
      //     return <p>type 이 "React 컴포넌트" 입니다.</p>;
      //   };

      //   ReactDOM.render(<Component />, document.querySelector('#root'));

      // 3. React Fragment type
      //   ReactDOM.render(
      //     React.createElement(
      //       React.Fragment,
      //       null,
      //       `type 이 "React Fragment" 입니다.`
      //     ),
      //     document.querySelector("#root")
      //   );

      //   ReactDOM.render(
      //     <>`type 이 "React Fragment" 입니다.</>,
      //     document.querySelector('#root'),
      //   );

      // 4. props 를 통해 데이터를 주입
      //   const Component = props => {
      //     return React.createElement(
      //       'p',
      //       null,
      //       `message 는 "${props.message}" 입니다.`,
      //     );
      //   };

      //   ReactDOM.render(
      //     React.createElement(
      //       Component,
      //       { message: '이것은 메세지 입니다.' },
      //       null,
      //     ),
      //     document.querySelector('#root'),
      //   );

      //   const Component = props => {
      //     return <p>message 는 "{props.message}" 입니다.</p>;
      //   };

      //   ReactDOM.render(
      //     <Component message="이것은 메세지 입니다." />,
      //     document.querySelector('#root'),
      //   );

      // 5. props 에 들어가는 children
      //   const Component = props => {
      //     return React.createElement(
      //       'p',
      //       null,
      //       `message 는 "${props.message}" 입니다.`,
      //       `props.children 은 "${props.children}" 입니다.`,
      //     );
      //   };

      //   ReactDOM.render(
      //     React.createElement(
      //       Component,
      //       { message: '이것은 메세지 입니다.' },
      //       '이것은 children 입니다.',
      //     ),
      //     document.querySelector('#root'),
      //   );

      //   const Component = props => {
      //     return (
      //       <p>
      //         message 는 "{props.message}" 입니다. props.children 은 "
      //         {props.children}" 입니다.
      //       </p>
      //     );
      //   };

      //   ReactDOM.render(
      //     <Component message="이것은 메세지 입니다.">
      //       이것은 children 입니다.
      //     </Component>,
      //     document.querySelector('#root'),
      //   );

      // 6. 리액트 엘리먼트에 style 추가
      //   ReactDOM.render(
      //     React.createElement(
      //       'h1',
      //       { style: { color: 'red' } },
      //       `type 이 "태그 이름 문자열" 입니다.`,
      //     ),
      //     document.querySelector('#root'),
      //   );

      //   ReactDOM.render(
      //     <h1 style={{ color: 'red' }}>type 이 "태그 이름 문자열" 입니다.</h1>,
      //     document.querySelector('#root'),
      //   );

      // 7. 복잡한 컴포넌트
      //   ReactDOM.render(
      //     React.createElement(
      //       'div',
      //       { style: { backgroundColor: 'red', width: 100, height: 100 } },
      //       React.createElement(
      //         'div',
      //         { style: { backgroundColor: 'green', width: 50, height: 50 } },
      //         null,
      //       ),
      //       React.createElement(
      //         'div',
      //         { style: { backgroundColor: 'yellow', width: 50, height: 50 } },
      //         null,
      //       ),
      //     ),
      //     document.querySelector('#root'),
      //   );

      //   ReactDOM.render(
      //     <div style={{ backgroundColor: 'red', width: 100, height: 100 }}>
      //       <div style={{ backgroundColor: 'green', width: 50, height: 50 }} />
      //       <div style={{ backgroundColor: 'yellow', width: 50, height: 50 }} />
      //     </div>,
      //     document.querySelector('#root'),
      //   );
    </script>
  </body>
</html>

왜 JSX 을 쓰나요??

  • React.createElement VS JSX
    • 가독성 완승
  • babel 과 같은 컴파일 과정에서 문법적 오류를 인지하기 쉬움

JSX 문법

  • 최상위 요소가 하나여야 합니다.
  • 최상위 요소 리턴하는 경우, ( ) 로 감싸야 합니다.
  • 자식들을 바로 랜더링하고 싶으면, <>자식들</> 를 사용합니다. => Fragment
  • 자바스크립트 표현식을 사용하려면, {표현식} 를 이용합니다.
  • if 문은 사용할 수 없습니다.
    • 삼항 연산자 혹은 && 를 사용합니다.
  • style 을 이용해 인라인 스타일링이 가능합니다.
  • class 대신 className 을 사용해 class 를 적용할 수 있습니다.
  • 자식요소가 있으면, 꼭 닫아야 하고, 자식요소가 없으면 열면서 닫아야 합니다.
    • <p>어쩌구</p>
    • <br />

JSX 문법

<!-- ex7.html : JSX 문법 -->
<!DOCTYPE html>
<html lang="en">
  <head>...</head>
  <body>
    <div id="root"></div>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
      // 1. 최상위 요소가 하나여야 합니다.
      // 2. 최상위 요소 리턴하는 경우, ( ) 로 감싸야 합니다.
      //   const Comp1 = props => {
      //       return (
      //         <h1>제목</h1>
      //         <h2>부제목</h2>
      //       );
      //   }
      //   const Comp2 = props => {
      //     return (
      //       <div>
      //         <h1>제목</h1>
      //         <h2>부제목</h2>
      //       </div>
      //     );
      //   };

      // 3. 자식들을 바로 랜더링하고 싶으면, <>자식들</> 를 사용합니다. => Fragment
      //   const Comp3 = props => {
      //     return (
      //       <>
      //         <h1>제목</h1>
      //         <h2>부제목</h2>
      //       </>
      //     );
      //   };

      // 4. 자바스크립트 표현식을 사용하려면, {표현식} 를 이용합니다.
      //   const Comp4 = props => {
      //     return (
      //       <div>
      //         <h1>제목</h1>
      //         <h2>{props.children}</h2>
      //       </div>
      //     );
      //   };

      // 5. if 문은 사용할 수 없습니다.
      // 삼항 연산자 혹은 && 를 사용합니다.
      //   const Comp5 = props => {
      //     return (
      //       <div>
      //         <h1>제목</h1>
      //         <h2>{props.children}</h2>
      //         {props.isShow ? '있다' : '없다.'}
      //         {props.isShow && '있을 때만 나온다'}
      //       </div>
      //     );
      //   };

      // 6. style 을 이용해 인라인 스타일링이 가능합니다.
      //   const Comp6 = props => {
      //     return (
      //       <div>
      //         <h1
      //           style={{
      //             color: 'red',
      //           }}
      //         >
      //           제목
      //         </h1>
      //         <h2>부제목</h2>
      //       </div>
      //     );
      //   };

      // 7. class 대신 className 을 사용해 class 를 적용할 수 있습니다.
      //   const Comp7 = props => {
      //     return (
      //       <div>
      //         <h1 className="title">제목</h1>
      //         <h2>부제목</h2>
      //       </div>
      //     );
      //   };

      // 8. 자식요소가 있으면, 꼭 닫아야 하고, 자식요소가 없으면 열면서 닫아야 합니다.
      // <p>어쩌구</p>
      // <br />
      //   const Comp7 = props => {
      //     return (
      //       <div>
      //         <h1 className="title">제목</h1>
      //         <br />
      //         <h2>부제목</h2>
      //       </div>
      //     );
      //   };
    </script>
  </body>
</html>

React Component 만드는 법

Hooks 이전

  • 컴포넌트 내부에 상태가 있다면 ?

    • class

  • 컴포넌트 내부에 상태가 없다면 ?

    • ​라이프사이클을 사용해야 한다면 ?

      • class

    • ​라이프사이클에 관계 없다면 ?

      • ​function

Hooks 이후

  • class

  • function

Class 컴포넌트

import React from 'react';

class ClassComponent extends React.Component {
  render() {
    return (<div>Hello</div>);
  }
}

// 사용
<ClassComponent />

Functional 컴포넌트

import React from 'react';

function FunctionalComponent() {  
  return <div>Hello</div>;
}

const FunctionalComponent = () => <div>Hello</div>;

// 사용
<FunctionalComponent />

Props 와 State

Props 와 State

Props 는 컴포넌트 외부에서 컴포넌트에게 주는 데이터입니다.

State 는 컴포넌트 내부에서 변경할 수 있는 데이터입니다.

둘 다 변경이 발생하면, 랜더가 다시 일어날 수 있습니다.

Component

Props

State

Render 함수

Props 와 State는 를 바탕으로 컴포넌트를 그립니다.

그리고 Props 와 State 가 변경되면, 컴포넌트를 다시 그립니다.

컴포넌트를 그리는 방법을 기술하는 함수가 랜더 함수 입니다.

Component

Props

State

Component

Props

State

Props

State

function 컴포넌트(props) { retutn JSX; }

<!-- ex8-1.html : 함수로 리액트 컴포넌트 만들기 -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <div id="root"></div>
    <script
      crossorigin
      src="https://unpkg.com/react@16/umd/react.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"
    ></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

    <script type="text/babel">
      function Component(props) {
        return (
          <div>
            <h1>{props.message} 이것은 함수로 만든 컴포넌트 입니다.</h1>
          </div>
        );
      }

      ReactDOM.render(
        <Component message="안녕하세요!!!" />,
        document.querySelector('#root'),
      );
    </script>
  </body>
</html>

<Component p="프롭스" />

props 설정

function Component(props) {
  return (
    <div>
      <h1>{props.message} 이것은 함수로 만든 컴포넌트 입니다.</h1>
    </div>
  );
}

props.p 로 접근

props 사용

class 컴포넌트 extends React.Component

<!-- ex8-2.html : 클래스로 리액트 컴포넌트 만들기 -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <div id="root"></div>
    <script
      crossorigin
      src="https://unpkg.com/react@16/umd/react.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"
    ></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

    <script type="text/babel">
      class Component extends React.Component {
        render() {
          return (
            <div>
              <h1>
                {this.props.message} 이것은 클래스를 상속하여 만든 컴포넌트
                입니다.
              </h1>
            </div>
          );
        }
      }

      ReactDOM.render(
        <Component message="안녕하세요!!!" />,
        document.querySelector('#root'),
      );
    </script>
  </body>
</html>

<Component p="프롭스" />

props 설정

class Component extends React.Component {
  render() {
    return (
      <div>{this.props.p}</div>
    );
  }
}

this.props.p 로 접근

props 사용

defaultProps

<!-- ex9.html : defaultProps 설정 -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <div id="root"></div>
    <script
      crossorigin
      src="https://unpkg.com/react@16/umd/react.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"
    ></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

    <script type="text/babel">
      class Component extends React.Component {
        static defaultProps = {
          message: '안녕하세요!!!',
        };
        render() {
          return (
            <div>
              {this.props.message} 이것은 클래스를 상속하여 만든 컴포넌트
              입니다.
            </div>
          );
        }
      }

      //   Component.defaultProps = {
      //     message: '안녕하세요!!!',
      //   };

      ReactDOM.render(<Component />, document.querySelector('#root'));
    </script>
  </body>
</html>

state = {}; constructor

state 초기값 설정

class Component extends React.Component {
  state = {
    s: '스테이트'
  };
  render() {
    return (
      <div>{this.state.s}</div>
    );
  }
}

this.state.s 로 접근

state 사용

class Component extends React.Component {
  constructor(props) {
    super(props);
    this.state = {s: '스테이트'};
  }
  render() {
    return (
      <div>{this.state.s}</div>
    );
  }
}

this.setState({s: '새 스테이트'});

state 값 업데이트

class Component extends React.Component {
  state = {
    s: '스테이트'
  };
  render() {
    return (
      <div onClick={() => {
        this.setState({s: '새 스테이트'});
      }}>{this.state.s}</div>
    );
  }
}

Event Handling

Event Handling

  • HTML DOM 에 클릭하면 이벤트가 발생하고, 발생하면 그에 맞는 변경이 일어나도록 해야합니다.
  • JSX 에 이벤트를 설정할 수 있습니다.
class Comp extends React.Component {
  render() {
    return (
      <div>
        <button onClick={() => {
          console.log('clicked');
        }}>클릭</button>
      </div>
    );
  }
}

Event Handling

  • camelCase 로만 사용할 수 있습니다.
    • onClick, onMouseEnter
  • 이벤트에 연결된 자바스크립트 코드는 함수입니다.
    • 이벤트={함수} 와 같이 씁니다.
  • 실제 DOM 요소들에만 사용 가능합니다.
    • 리액트 컴포넌트에 사용하면, 그냥 props 로 전달합니다.
<!-- ex10.html : 이벤트를 이용하여 state 바꾸기 -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        border: 0;
      }
    </style>
  </head>
  <body>
    <div id="root"></div>

    <script
      crossorigin
      src="https://unpkg.com/react@16/umd/react.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"
    ></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

    <script type="text/babel">
      class Component extends React.Component {
        state = { message: 'init', count: 0 };
        render() {
          return (
            <>
              <p
                style={{
                  color: 'white',
                  fontSize: 20,
                  backgroundColor: 'green',
                  textAlign: 'center',
                  width: 200,
                }}
              >
                {this.state.message} {this.state.count}
              </p>
              <button
                style={{
                  backgroundColor: 'red',
                  border: '2px solid #000000',
                  fontSize: 15,
                  width: 200,
                }}
                onClick={() => {
                  this.setState({
                    message: 'update',
                    count: this.state.count + 1,
                  });
                }}
              >
                +
              </button>
            </>
          );
        }
      }

      ReactDOM.render(<Component />, document.querySelector('#root'));
    </script>
  </body>
</html>

Component Lifecycle

리액트 컴포넌트는 탄생부터 죽음까지

여러지점에서 개발자가 작업이 가능하도록

메서드를 오버라이딩 할 수 있게 해준다.

Declarative 디클레러티브

Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes.

Component 생성 및 마운트 (< v16.3)

constructor

componentWillMount

render (최초 랜더)

componentDidMount

class App extends React.Component {
  _interval;

  constructor(props) {
    console.log('App constructor');
    super(props);
    this.state = {
      age: 37,
    };
  }

  componentWillMount() {
    console.log('App componentWillMount');
  }

  componentDidMount() {
    console.log('App componentDidMount');
    this._interval = window.setInterval(() => {
      this.setState({
        age: this.state.age + 1,
      });
    }, 1000);
  }

  componentWillUnmount() {
    console.log('App componentWillUnmount');
    clearInterval(this._interval);
  }

  render() {
    console.log('App render');
    return (
      <div>
        <h2>
          Hello {this.props.name} - {this.state.age}
        </h2>
      </div>
    );
  }
}

constructor

componentWillMount

render

componentDidMount

Component props, state 변경 (< v16.3)

componentWillReceiveProps

shouldComponentUpdate

componentWillUpdate
render

componentDidUpdate

  componentWillReceiveProps(nextProps) {
    console.log(
      `App componentWillReceiveProps : ${JSON.stringify(
        this.props
      )} => ${JSON.stringify(nextProps)}`
    );
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log(
      `App shouldComponentUpdate : ${JSON.stringify(
        this.props
      )} => ${JSON.stringify(nextProps)}, ${JSON.stringify(
        this.state
      )} => ${JSON.stringify(nextState)}`
    );
    return true;
  }

  componentWillUpdate(nextProps, nextState) {
    console.log(
      `App componentWillUpdate : ${JSON.stringify(
        this.props
      )} => ${JSON.stringify(nextProps)}, ${JSON.stringify(
        this.state
      )} => ${JSON.stringify(nextState)}`
    );
  }

  componentDidUpdate(prevProps, prevState) {
    console.log(
      `App componentDidUpdate : ${JSON.stringify(
        prevProps
      )} => ${JSON.stringify(this.props)}, ${JSON.stringify(
        prevState
      )} => ${JSON.stringify(this.state)}`
    );
  }

componentWillReceiveProps

shouldComponentUpdate

componentWillUpdate

render

componentDidUpdate

componentWillReceiveProps

  • props 를 새로 지정했을 때 바로 호출됩니다.

  • 여기는 state 의 변경에 반응하지 않습니다.

    • 여기서 props 의 값에 따라 state 를 변경해야 한다면,

      • setState 를 이용해 state 를 변경합니다.

      • 그러면 다음 이벤트로 각각 가는것이 아니라 한번에 변경됩니다.

shouldComponentUpdate

  • props 만 변경되어도

  • state 만 변경되어도

  • props & state 둘다 변경되어도

  • newProps 와 new State 를 인자로 해서 호출

  • return type 이 boolean 입니다.

    • true 면 render

    • false 면 render 가 호출되지 않습니다.

    • 이 함수를 구현하지 않으면, 디폴트는 true

componentWillUpdate

  • 컴포넌트가 재 랜더링 되기 직전에 불립니다.

  • 여기선 setState 같은 것을 쓰면 아니됩니다.

componentDidUpdate

  • 컴포넌트가 재 랜더링을 마치면 불립니다.

Component 언마운트 (< v16.3)

componentWillUnmpunt

class App extends React.Component {
  _interval;

  constructor(props) {
    console.log('App constructor');
    super(props);
    this.state = {
      age: 37,
    };
  }

  componentDidMount() {
    console.log('App componentDidMount');
    this._interval = window.setInterval(() => {
      this.setState({
        age: this.state.age + 1,
      });
    }, 1000);
  }
  
  componentWillUnmount() {
    console.log('App componentWillUnmount');
    clearInterval(this._interval);
  }

  render() {
    console.log('App render');
    return (
      <div>{this.state.age < 50 && <Button />}</div>
    );
  }
}

componentWillUnmount

class Button extends React.Component {
  componentWillUnmount() {
    console.log('Button componentWillUnmount');
  }

  render() {
    return <>hello</>;
  }
}

Component 라이프사이클 변경 (v16.3)

constructor

componentWillMount => getDerivedStateFromProps

render

componentDidMount

 

componentWillReceiveProps => getDerivedStateFromProps

shouldComponentUpdate

render

componentWillUpdate => getSnapshotBeforeUpdate

(dom 에  적용)

componentDidUpdate

 

componentWillUnmount

Component 생성 및 마운트 (v16.3)

constructor

static getDerivedStateFromProps

render (최초 랜더)

componentDidMount

import React from 'react';

class App extends React.Component {
  state = {
    age: 0,
  };

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log(nextProps, prevState);
    if (prevState.age !== nextProps.age) {
      return { age: nextProps.age };
    }

    return null;
  }

  render() {
    console.log('App render');
    return <div>{this.state.age}</div>;
  }
}

export default App;

getDerivedStateFromProps

Component props, state 변경 (v16.3)

static getDerivedStateFromProps​ (props 변경)

shouldComponentUpdate (state 변경)
render

getSnapshotBeforeUpdate

(dom 에 적용)

componentDidUpdate

import React from "react";
import "./App.css";

let i = 0;

export default class App extends React.Component {
  state = { list: [] };

  getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevState.list.length === this.state.list.length) return null;
    const list = document.querySelector("#list");
    return list.scrollHeight - list.scrollTop;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot === null) return;
    const list = document.querySelector("#list");
    list.scrollTop = list.scrollHeight - snapshot;
  }

  componentDidMount() {
    setInterval(() => {
      this.setState({
        list: [...this.state.list, i++],
      });
    }, 1000);
  }

  render() {
    return (
      <div id="list" style={{ height: 100, overflow: "scroll" }}>
        {this.state.list.map((i) => (
          <div>{i}</div>
        ))}
      </div>
    );
  }
}

getSnapshotBeforeUpdate

Component 언마운트 (v16.3)

componentWillUnmount

Component 에러 캐치

componentDidCatch

import React from 'react';

class Button extends React.Component {
  render() {
    test();
    return <div>hello</div>;
  }
}

class App extends React.Component {
  state = {
    hasError: false,
  };

  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // You can also log the error to an error reporting service
    // logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      return <div>에러 화면</div>;
    }
    return (
      <div>
        <Button />
      </div>
    );
  }
}

export default App;

componentDidCatch

2. React Component 만들기

By Woongjae Lee

2. React Component 만들기

Fast Campus Frontend Developer School 17th

  • 1,284