+ 알아야 할것

composition과 inheritance의 차이점

composition 하는 3가지 방법

class를 동적으로 추가하는 방법

css frame 애니메이션

+ 실습

composition 기법 중 children props 를 이용해서 가장 높은 점수의 player의 오른쪽에 왕관을 css frame 애니메이션을 사용해서 추가한다

로직없이 composition 구조 만들기

기존 Player 컴포넌트를 composition 기법을 통해서 확장한다. 기존 Player 컴포넌트에 왕관 기능을 추가하여 CustomPlayer 컴포넌트를 만든다. 

컴포넌트 구조는 App – CustomPlayer – Player 가 된다.

components/CustomPlayer.jsx

export const CustomPlayer = (props) => {
  // is-high-score 클래스는 동적으로 넣어야한다 => 클래스 바인딩
  let dynamicClass = 'svg';

  return (
    <Player {...props}>
      <svg viewBox="0 0 44 35" className={dynamicClass}>
        <path d="M26.7616 10.6207L21.8192 0L16.9973 10.5603C15.3699 14.1207 10.9096 15.2672 7.77534 12.9741L0 7.24138L6.56986 28.8448H37.0685L43.5781 7.72414L35.7425 13.0948C32.6685 15.2672 28.3288 14.0603 26.7616 10.6207Z" transform="translate(0 0.301727)"/>
        <rect width="30.4986" height="3.07759" transform="translate(6.56987 31.5603)"/>
      </svg>
    </Player>
  )
}

App.jsx render 함수내 Player를 CustomPlayer로 바꾼다(L3)

App.jsx

        {/*Players List*/}
        { this.state.players.map(item =>
          <CustomPlayer key={item.id}
                  name={item.name} score={item.score} id={item.id}
                  isHighScore={item.score === this.getHighScore()}
                  removePlayer={this.handleRemovePlayer}
                  changeScore={this.handleChangeScore} />) }

화면을 확인하면 아직 기존이랑 변경된게 없다. 왜냐하면 Player 컴포넌트에서 children 속성을 추가하지 않았기 때문이다. Player 컴포넌트에서 children 속성을 name 왼쪽에 추가한다(L9)

Player.jsx

export const Player = (props) => (
  <div className="container">
    <div className='player row align-items-center'>
      <div className="col-1">
        <button className="btn btn-danger"
                onClick={() => props.removePlayer(props.id)}>x</button>
      </div>
      <div className="col-8">
        {props.children}
        <span>{props.name}</span>
      </div>
      <div className="col-3 counter">
        <Counter score={props.score} id={props.id} changeScore={props.changeScore} />
      </div>
    </div>
  </div>
);

이제 화면을 확인하면 모든 Player name 왼쪽에 회색 왕관 표시가 있을 것이다. 

highscore 로직 추가

점수가 가장 높은 사용자일 경우에 왼쪽 왕관을 노란색으로 만들되 애니메이션을 사용하여 추가한다. 애니메이션을 추가하기 위해서 css 애니메이션을 넣기 위해서 is-high-score라는 클래스를 미리 추가해놓았기 때무에 high score일 경우 이 클래스만 추가하면 된다.

로직을 계산할 수 있는 컴포넌트는 최상위 컴포넌트이므로 먼저 App.jsx에서 high score가 누구인지를 계산한 다음에(L1 ~ L6) Player 컴포넌트에 속성으로 넘겨준다(L17). isHighScore라는 속성은 boolean 속성이다.

App.jsx

  getHighScore() {
    const maxObject = _.maxBy(this.state.players, 'score');
    const highScore = maxObject.score;
    // 0은 디폴트이므로  0보다 클 경우만 highScore로 지정한다.
    return highScore > 0 ? highScore : null;
  }

  render() {
    return (
      <div className="container p-3">
        <Header title="My scoreboard" players={this.state.players} />

        {/*Players List*/}
        { this.state.players.map(item =>
          <CustomPlayer key={item.id}
                  name={item.name} score={item.score} id={item.id}
                  isHighScore={item.score === this.getHighScore()}
                  removePlayer={this.handleRemovePlayer}
                  changeScore={this.handleChangeScore} />) }

        <AddPlayerForm addPlayer={this.handleAddPlayer}></AddPlayerForm>
      </div>
    );
  }

isHighScore 속성이 true인 경우 미리 만들어 놓은 is-high-score 클래스를 추가한다(L4 ~ L6)

components/CustomPlayer.jsx

export const CustomPlayer = (props) => {
  // is-high-score 클래스는 동적으로 넣어야한다 => 클래스 바인딩
  let dynamicClass = 'svg';
  if (props.isHighScore) {
    dynamicClass += ' ' + 'is-high-score';
  }

  return (
    <Player {...props}>
      <svg viewBox="0 0 44 35" className={dynamicClass}>
        <path d="M26.7616 10.6207L21.8192 0L16.9973 10.5603C15.3699 14.1207 10.9096 15.2672 7.77534 12.9741L0 7.24138L6.56986 28.8448H37.0685L43.5781 7.72414L35.7425 13.0948C32.6685 15.2672 28.3288 14.0603 26.7616 10.6207Z" transform="translate(0 0.301727)"/>
        <rect width="30.4986" height="3.07759" transform="translate(6.56987 31.5603)"/>
      </svg>
    </Player>
  )
}

카운트를 클릭하여 점수가 가장 높은 player 왼쪽에 왕관이 애니메이션되면서 표시되는지 확인한다.

동적 클래스 라이브러리 classnames 활용

화면을 동적으로 만들기 위해서 특정 클래스를 추가 및 삭제하는 경우가 많다. jquery 같은 경우도 addClass, removeClass 와 같이 별도로 만들어져 있고, vue, angular를 보더라도 별도의 템플릿이 만들어져 있는 만큼 웹 개발시에 아주 많이 사용되고 있다.

하지만 리액트에는 별도의 문법이 없는데, 3rd party 라이브러리중 classnames가 이것을 해주고 있으며 react 공식 문서에서도 이 라이브러리를 추천하고 있으니 이것을 이용해서 동적클래스를 쉽게 구현해보자. 먼저 라이브러리를 추가한다.

yarn add classnames

npmjs.com 에 가서 이 라이브러리를 검색해보면 다운로드 수가 리액트와 거의 맞먹는 수준으로 개발시 거의 필수 라이브러리라는 것을 알 수 있다. 몇가지 간단한 사용법을 살펴보자.

사용법은 import 후에 함수안에 넣어야 할 클래스명을 정적으로 혹은 동적으로 넣을수 있다.

  • classNames(‘svg’) => svg 클래스명이 추가. 스트링 형태로 사용 가능
  • classNames({‘svg’: true}) => json 형태로 사용 가능하다. true이면 svg 클래스명이 추가
  • classNames({‘svg’: false}) => false이므로 svg 클래스명이 추가되지 않는다.
  • classNames({‘is-high-score’: props.isHighScore}) => props 값에 true이면 is-highScore 가 추가되고 false 이면 추가되지 않는다. 이것을 동적 클래스 바인딩이라고 한다.
  • classNames({‘svg’, true, ‘is-high-score’: props.isHighScore}) => json에 여러개를 넣을 수 있다.
  • classNames(‘svg’, {‘is-high-score’: props.isHighScore}) => ,로 분리해서 여러개로도 사용가능하다.

if 문을 삭제하고 classnames를 사용하여 클래스를 동적으로 추가한다.

components/CustomPlayer.jsx

import {Player} from "./Player";
import classNames from "classnames";

export const CustomPlayer = (props) => {
  // is-high-score 클래스는 동적으로 넣어야한다 => 클래스 바인딩

  return (
    <Player {...props}>
      <svg viewBox="0 0 44 35" className={classNames('svg', {'is-high-score': props.isHighScore})}>
        <path d="M26.7616 10.6207L21.8192 0L16.9973 10.5603C15.3699 14.1207 10.9096 15.2672 7.77534 12.9741L0 7.24138L6.56986 28.8448H37.0685L43.5781 7.72414L35.7425 13.0948C32.6685 15.2672 28.3288 14.0603 26.7616 10.6207Z" transform="translate(0 0.301727)"/>
        <rect width="30.4986" height="3.07759" transform="translate(6.56987 31.5603)"/>
      </svg>
    </Player>
  )
}

quiz