메인화면

 


 

도타2 오토체스를 즐기는 사람이라면 모바일로 오토체스가 개발 중이라는 것을 들어봤을 것이다. 아직 개발이 완료되지는 않았지만 지금도 게임을 할 수 있어서 해보고 후기를 남기려고 한다. 아직 정식 버전은 아니지만 최근 플레이 스토어Auto Chess(미출시)- Dragonest Game 로 앱이 올라왔다. 링크 참조. 게임 버전은 스팀에 비해 꽤나 옛날 버전이고, 한글화는 되어 있지만 번역기를 돌린 수준이다. 한글화는 사실 스팀에서도 별 차이 없어서 뭐 정식 오픈 때는 잘 반영을 해줄지 의문이긴하다. 유닛들을 가져올 때, 도타 영웅들을 그대로 가져온 게 아니라서 이름의 변화가 조금 있어서 게임 전 미리 도감을 확인해 보는 것을 추천한다. 전체적인 게임 진행이나 UI 등은 기존과 거의 흡사하기 때문에 별 무리 없이 재밌게 즐길 수 있을 것이다.

 


 

게임은 스탠다드/랭킹전을 선택해서 즐길 수 있다. 전부 사람들과 할 수 있는데, 스탠다드는 그냥 노멀 게임이라고 보면 되고, 랭킹은 병사-기사 등 이렇게 등급전을 하게 된다. 무슨 게임을 하던지 짐꾼을 뽑을 때 쓰는 사탕을 받을 수 있으니 모아서 짐꾼을 뽑아보자.

 


 

게임 내 플레이에서는 예전 나가 암살자, 야수 어쌔신(Sand King) 이 있고, 엘프 사냥꾼(Mirana), 종족 캐릭터들이 없는 상태라 6 어쌔신을 하기에 좋은 것 같다. 물론, 기사 시너지가 2, 4, 6 단위로 효과가 발생되는 버전이라 기사를 이용한 트롤나이트 같은 조합도 좋아보인다. 6 헌터나 6 메이지의 경우에는 갖추면 물론 강력하지만 해당 종류를 하나도 빠짐없이 다 모아야하기 때문에 지금 버전에서는 조금 불리하다고 할 수 있겠다. 여기서도 벌목기는 매우 강력하기 때문에 리롤 고블린도 괜찮은 선택 같다.

 


 

출퇴근하면서 해봤는데, 지하철에서 딱히 끊기지도 않고 쾌적한 플레이를 할 수 있었다. 대신 플레이 타임이 스팀에서처럼 40분 정도 가기 때문에 나중에 정신없이 리롤하는 상황이 생기지 않으려면 여유롭게 시간을 가지는 게 필요할 듯 하다. 걸어가면서 리롤하고 합치는 플레이를 하기에는 창고에서 자동합치기 이런 기능은 없기 때문에 정신없을 것 같다.

'게임 > 기타' 카테고리의 다른 글

모바일 오토체스를 해보자  (0) 2019.05.27
POE/Path Of Exile/패스오브액자일  (0) 2019.03.14

웹 개발자는 크게 프론트엔드, 백엔드로 나뉘어진다. 풀스택 개발자라면 두 개 뿐 아니라 데브옵스까지 참고하면 좋을 듯 하여 가져와봤다.


인트로

 

우선, 형상관리는 Git을 안 쓸 이유가 없는 것 같다. 물론, 아직까지 국내에선 Spring Framework 기반 개발을 하면서 이클립스에서 SVN을 이용하여 관리하는 곳도 많을 것으로 생각된다. 하지만, Git은 branch를 나누고 merge 하는 방식 등 SVN에 비해 형상 관리가 훨씬 강력하다고 생각한다. 거기에 무료이니 안 쓸 이유가 없지 않을까? 그리고 다양한 오픈 소스를 공유하고 받을 수 있는 Github와의 연동을 생각하면 이젠 필수가 아닐까 한다. 이 외에는 인트로에서 특별히 언급할 것은 없는 듯 하다.

 


프론트엔드

전체적으로 PHP, Spring Framework 같은 무겁고 느린 느낌에서 React.js, Node.js 처럼 가볍고 빠른 형태로 바뀌어야 하는 상황이라고 생각한다. Spring도 Spring Boot 처럼 쉽게 쓸 수 있도록 개선을 하고 있지만, MVC(Model, View, Controller)패턴에서 벗어나기 쉽지 않아 보인다. 물론, 개발하는 입장에서는 하던 게 편하기 때문에 리액트보다 스프링 개발이 더 쉬울 수 있겠지만, 모바일 웹과 같이 반응형 화면을 만들기 위해서는 React.js 나 Vue.js 와 같은 자바스크립트 기반 라이브러리로 개발하는 것이 좋아보인다. 동일한 기능을 자바로 한 번, 안드로이드 네이티브로 한 번, 스위프트로 한 번 하는 것은 너무 비효율적이 되어버렸다.

 


 

백엔드

 

백엔드는 전문 분야가 아니지만 개인적인 의견을 써보려고 한다. 빅데이터, 인공지능, 머신러닝과 관련이 있는 사람이라면 어디에서든 핵심 인재가 될 수 있을 것 같다. 캐글처럼 초심자도 이런 것에 대해 쉽게 접할 수 있는 공간이 늘어가고는 있지만, 아직 이런 것을 어떻게 자신의 서비스와 연결시켜서 더 발전시킬 건지에 대해서는 쉽지 않아보인다. 챗봇처럼 이제 주위에서 서비스하는 것이 하나 둘 생겨가기 때문에 자연어 관련 전문가가 되는 것도 좋을 듯 하다. 이것과 관계없는 개발자라면 서버에서 클라이언트로 제공하는 API를 개발하거나 DB 관리 등을 할텐데 아직은 완벽히 넘어갈만한 것이 보이진 않는다. 사실 DB는 어떤 걸 써도 쿼리가 거의 비슷하기 때문에 회사에 따라 선택하는 것 같다. API는 RESTful API에서 GraphQL 로 넘어가는 추세 같지만 아직 주위에서는 RESTful API를 유지하고 있는 상황으로 보인다. 언어 자체는 실리콘밸리에서는 파이썬에서 이미 고랭으로 다 넘어갔다고 들었지만, 나에게 와닿은 것은 없다. 그냥 본인/회사랑 맞는 것을 선택하면 될 듯 하다.

 


데브옵스

 

데브옵스(DevOps)는 소프트웨어의 개발(Development)과 운영(Operations)의 합성어로서, 소프트웨어 개발자와 정보기술 전문가 간의 소통, 협업 및 통합을 강조하는 개발 환경이나 문화를 말한다. 프로젝트 관리를 어떻게 할 건지, 개발 형상 관리를 통해 빌드 배포를 어떻게 할 건지, 테스트 및 릴리즈, 모니터링은 어떻게 할 건지 정하는 거라고 보면 되겠다. 이것은 프로젝트마다 다 다르기 때문에 뭐가 좋은지 말하기가 어렵다. 내가 써본 걸 명시해보자면 프로젝트 관리는 트렐로, 빌드 배포는 젠킨스 정도가 있겠다.

개발이라는 게 분야가 넓기 때문에 국소적으로 보안 쪽을 담당할 수도 있고, 풀스택으로 혼자 하나의 프로젝트를 완성해야 할 수도 있다. 그래서 막상 이렇게 본 것을 어떻게 더 공부해야 할 지 감이 안 올 수도 있을 것 같다. (사실 나도 그렇다) 같은 언어도 패치가 되면서 자주 내용이 바뀌는데, 언어 자체를 바꿔서 공부해야 할 경우도 생긴다. 그렇기에 뭘 하려고 하든 하나의 프로젝트를 완성해 나가는 것이 중요하다고 생각한다. 완성이란게 풀스택을 의미하는 것은 아니고, 자신이 정한 분야에서 온전히 독립적으로 기능을 할 수 있으면 되지 않을까한다. 그렇게 완성을 한, 두 개 늘려가다보면 자신의 커리어가 그 만큼 늘 것이라고 생각한다.

'개발 > My Dev Story' 카테고리의 다른 글

2019 웹 개발자는 뭘 해야할까  (0) 2019.05.22

투더문(To the Moon) 이라는 스팀 게임 엔딩을 본 후에 피아노 곡을 찾아 들어봐야겠다고 생각이 들었다.

이 게임의 OST는 따로 구매를 해서 들어야했기 때문에 멜론으로 들을만한 것들을 찾아보다 딱 맘에 들어서 소개해본다.

잔잔하면서 서정적인 곡이라 제목이랑 딱 잘 어울리는 것 같다.

 

 

'음악' 카테고리의 다른 글

로맨틱브리즈(Romantic Breeze) - 별의 바다  (0) 2019.05.16
ADOY - Young  (0) 2019.04.03
Keane - Bend & Break  (0) 2019.03.29
Keane - Somewhere Only We Know  (0) 2019.03.13
Keane - Everybody's Changing  (0) 2019.03.12

최근에 내가 정말 좋아하게 된 인디밴드 아도이.

 

처음에는 영어가사만 들리길래 외국 밴드인 줄 알았다. 듣다보니 아도이만의 느낌에 빠지게 되었다.

 

ADOY(아도이) 라는 이름은 키우고 있는 고양이 이름 YODA를 거꾸로 한거라고 한다.

 

알고나니 뭔가 신비감은 조금 떨어졌지만 친숙하게 느껴지는 것 같다.

 

'음악' 카테고리의 다른 글

로맨틱브리즈(Romantic Breeze) - 별의 바다  (0) 2019.05.16
ADOY - Young  (0) 2019.04.03
Keane - Bend & Break  (0) 2019.03.29
Keane - Somewhere Only We Know  (0) 2019.03.13
Keane - Everybody's Changing  (0) 2019.03.12

시간여행 추가 부분을 추가하려고 하니, 문서 길이가 너무 길어서 그런지 오류가 발생한다.
그래서 어쩔 수 없이 2로 새로 글로 작성하게 되었다.

시간여행 추가

이제 마지막으로, 게임 플레이 중 이전 턴으로 돌아갈 수 있도록 해보자.

턴 저장하기

우리가 squares 배열을 .slice()를 통해서 새로운 복사본을 만들면서 사용하기 때문에 우리는 각 턴마다의 상태를 저장할 수 있다. 우리는 history라는 새로운 배열에 이전 턴의 squares 배열을 저장할 것이다. history 배열은 처음부터 마지막 턴까지의 게임 보드판의 모습을 다 저장하게 될 것이다. 코드로 보자면 아래와 같은 형태가 될 것이다:

history = [
  // Before first move
  {
    squares: [
      null, null, null,
      null, null, null,
      null, null, null,
    ]
  },
  // After first move
  {
    squares: [
      null, null, null,
      null, 'X', null,
      null, null, null,
    ]
  },
  // After second move
  {
    squares: [
      null, null, null,
      null, 'X', null,
      null, null, 'O',
    ]
  },
  // ...
]

우리는 history 의 상태를 어떤 컴포넌트에서 관리해야 할 지 정해야 한다.

한 번 더 상태 전달하기

앞서 말한 history의 상태를 제일 최상단 컴포넌트인 Game 컴포넌트에서 관리하려고 한다.
그래서 state를 부모(parent) 컴포넌트에게 전달하기 에서
Square 컴포넌트의 상태를 Boardprops를 통해 전달한 것처럼, Board의 상태를 Game으로 props를 통해 전달할 것이다.
( 쉽게 설명하자면, 하위 컴포넌트의 state 로 쓰던 것-> 상위 컴포넌트에서 관리 및 props 를 통해 하위 컴포넌트로 전달)

먼저, 우리는 Game 컴포넌트에 생성자를 추가하자:

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [{
        squares: Array(9).fill(null),
      }],
      xIsNext: true,
    };
  }

  render() {
    return (
      <div className="game">
        <div className="game-board">
          <Board />
        </div>
        <div className="game-info">
          <div>{/* status */}</div>
          <ol>{/* TODO */}</ol>
        </div>
      </div>
    );
  }
}

다음으로, Board 컴포넌트가 Game 컴포넌트로부터 squaresonClickprops로 받게 하자.
아래의 단계를 통해 Board 컴포넌트를 수정하자:

  • Board 생성자 삭제
  • BoardrenderSquare 함수에서 this.state.squares[i]this.props.squares[i] 로 교체
  • BoardrenderSquare 함수에서 this.handleClick(i)this.props.onClick(i) 로 교체

그러면 Board 컴포넌트는 이렇게 될 것이다:

class Board extends React.Component {
  handleClick(i) {
    const squares = this.state.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      squares: squares,
      xIsNext: !this.state.xIsNext,
    });
  }

  renderSquare(i) {
    return (
      <Square
        value={this.props.squares[i]}
        onClick={() => this.props.onClick(i)}
      />
    );
  }

  render() {
    const winner = calculateWinner(this.state.squares);
    let status;
    if (winner) {
      status = 'Winner: ' + winner;
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
    }

    return (
      <div>
        <div className="status">{status}</div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
          {this.renderSquare(7)}
          {this.renderSquare(8)}
        </div>
      </div>
    );
  }
}

그리고 게임 진행 상태와 승자 파악하는 부분을 history의 최근 상태를 이용하도록 수정하자.
Game 컴포넌트의 render 함수를 아래처럼 바꾸자:

render() {
    const history = this.state.history;
    const current = history[history.length - 1];
    const winner = calculateWinner(current.squares);

    let status;
    if (winner) {
      status = 'Winner: ' + winner;
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
    }

    return (
      <div className="game">
        <div className="game-board">
          <Board
            squares={current.squares}
            onClick={(i) => this.handleClick(i)}
          />

        </div>
        <div className="game-info">
          <div>{status}</div>
          <ol>{/* TODO */}</ol>
        </div>
      </div>
    );
  }

이제 Game 컴포넌트 안에서 status를 관리하기 때문에 Board에 있는 status 부분을 지우자.
Boardrender 함수를 아래처럼 수정하면 된다:

    render() {
    return (
      <div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
          {this.renderSquare(7)}
          {this.renderSquare(8)}
        </div>
      </div>
    );
  }

마지막으로, Board 컴포넌트에 있던 handleClick 함수를 Game 컴포넌트로 옮길 것이다. 이제 history를 가지고 보여줄 것이므로 아래처럼 좀 수정해야한다:

  handleClick(i) {
    const history = this.state.history;
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      history: history.concat([{
        squares: squares,
      }]),
      xIsNext: !this.state.xIsNext,
    });
  }
여기서 잠깐! push() vs concat()

우리는 턴이 지날 때마다 history 배열에 추가하기 위해서 concat()을 사용했다. 왜 push()를 안 쓰고 concat()을 사용했을까?

push() : The push() method adds one or more elements to the end of an array and returns the new length of the array. (MDN)

var arr1 = [‘a’, ‘b’, ‘c’];
var arr2 = [‘d’, ‘e’, ‘f’];
var arr3 = arr1.push(arr2);
console.log(arr3); // 4
console.log(arr1); // [“a”, “b”, “c”, [“d”, “e”, “f”]]

concat() : The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array. (MDN)

var arr1 = [‘a’, ‘b’, ‘c’];
var arr2 = [‘d’, ‘e’, ‘f’];
var arr3 = arr1.concat(arr2);
console.log(arr3); //[“a”, “b”, “c”, “d”, “e”, “f”]

쉽게 설명하자면, push()를 쓰면 기존 배열에 영향이 있고, concat()은 영향이 없다. 우리는 이전 history를 유지해야하므로 concat()을 쓰는 것이다.

Board 컴포넌트에서 handleClickGame 컴포넌트로 옮겼으므로, Board에는 이제 renderSquarerender 함수만 있으면 된다.

현재까지 완성본

이전 턴 보여주기

이제 우리는 tic tac toe 게임의 히스토리를 저장하고 있기 때문에, 플레이어에게 과거 턴들을 리스트로 보여줄 수 있다.
자바스크립트에서 배열은 아래와 같이 배열 내 데이터를 매핑하기 위해서 map() 메소드를 쓸 수 있다:

const numbers = [1, 2, 3];
const doubled = numbers.map(x => x * 2); // [2, 4, 6]

우리는 map() 함수를 가지고 history 배열을 화면에 보여줄 버튼으로 매핑할 수 있다.
Gamerender 함수에서 아래와 같이 수정하자:

render() {
    const history = this.state.history;
    const current = history[history.length - 1];
    const winner = calculateWinner(current.squares);

    const moves = history.map((step, move) => {
      const desc = move ?
        'Go to move #' + move :
        'Go to game start';
      return (
        <li>
          <button onClick={() => this.jumpTo(move)}>{desc}</button>
        </li>
      );
    });

    let status;
    if (winner) {
      status = 'Winner: ' + winner;
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
    }

    return (
      <div className="game">
        <div className="game-board">
          <Board
            squares={current.squares}
            onClick={(i) => this.handleClick(i)}
          />
        </div>
        <div className="game-info">
          <div>{status}</div>
          <ol>{moves}</ol>
        </div>
      </div>
    );
  }

현재까지 완성본

턴이 지날 때마다, 우리는 버튼을 포함한 태그를 만들게 된다. 버튼은 this.jumpTo() 라는 함수를 호출하는데, 이 함수는 아직 만들지 않았기 때문에, 아래와 같은 경고창이 뜰 것이다:

Warning: Each child in an array or iterator should have a unique “key” prop. Check the render method of “Game”.

키(Key) 고르기

우리가 리스트를 화면에 보여줄 때, 리액트는 화면에 보여줄 리스트들의 정보를 저장하고 있다. 우리가 리스트의 값을 업데이트하면, 리액트는 무엇이 바뀌었는 지 알아내어서 다시 화면에 보여주게 된다.

<li>Alexa: 7 tasks left</li>
<li>Ben: 5 tasks left</li>

위의 리스트를 아래와 같이 바꾼다고 생각해보자.

<li>Ben: 9 tasks left</li>
<li>Claudia: 8 tasks left</li>
<li>Alexa: 5 tasks left</li>

우리는 AlexaBen 의 순서가 바뀌고 이 사이에 Claudia가 추가되었다는 것을 알 수 있다. 하지만 리액트는 우리의 의도를 쉽게 알 수가 없기 때문에, 우리는 비슷한 리스트들 중에 key를 통해서 무엇이 다른지 리액트에게 알려줄 필요가 있다.
예를 들면 Alexa',Ben,Claudia` 의 데이터베이스 ID를 알고 있다면 다음과 같이 표현할 수 있다:

<li key={user.id}>{user.name}: {user.taskCount} tasks left</li>

이렇게 key를 가지고 만들게되면, 리액트는 리스트 아이템의 키가 이전에 있던 것인지 확인하고 다시 화면에 그리게 된다.
이전에 없던 key면 리액트는 새로운 컴포넌트를 만든다. key가 없어졌다면 리액트는 만들었던 컴포넌트를 제거한다.
그리고 기존의 key가 변경되면 리액트는 기존의 컴포넌트를 없애고 새로운 컴포넌트를 만들게 된다.

keyprops에 종속되어 this.props.key처럼 사용된다고 생각할 수도 있지만 그렇지 않다. 컴포넌트는 keythis.props.key처럼 접근할 수 없고, 리액트가 알아서 key를 통해 업데이트 할 컴포넌트를 결정하게 된다.

동적 리스트(dynamic lists)를 만들 때, 적절한 key를 쓰는 것을 추천한다.

key를 정하지 않으면, 앞서 본 것처럼 리액트는 경고창을 띄워주고 배열의 index를 기본 key로 사용한다.
배열의 index를 key로 쓰는 것은 리스트를 재정렬하거나 기존 배열 가운데 새롭게 추가/삭제하게 될 경우 문제가 될 수 있다.

시간 여행 구현하기

우리가 만든 tic tac toe 게임에서는 플레이어의 턴이 재정렬되거나 중간에 추가, 삭제 될 일은 없기 때문에 우리는 history의 index를 key로 사용할 것이다.

Game 컴포넌트의 render 함수에서 <li key={move}>와 같이 key를 추가하자:

      const moves = history.map((step, move) => {
      const desc = move ?
        'Go to move #' + move :
        'Go to game start';
      return (
        <li key={move}>
          <button onClick={() => this.jumpTo(move)}>{desc}</button>
        </li>
      );
    });


그리고 아직 `this.jumpTo(move)` 에서 `jumpTo` 함수를 만들지 않았다. 이 함수를 만들기 전에 `Game` 컴포넌트에 `stepNumber`를 추가할 것이다.

먼저, `Game`의 생성자에 `stepNumber: 0`을 추가하자:

class Game extends React.Component {  
  constructor(props) {  
    super(props);  
    [this.state](this.state) = {  
      history: \[{  
        squares: Array(9).fill(null),  
      }\],  
      stepNumber: 0,  
      xIsNext: true,  
    };  
  }

그리고 stepNumber를 업데이트 해줄 jumpTo 함수를 만들자. 그리고 stepNumber가 짝수 일 때, xIsNexttrue가 되도록 수정하자:

  handleClick(i) {
    // this method has not changed
  }

  jumpTo(step) {
    this.setState({
      stepNumber: step,
      xIsNext: (step % 2) === 0,
    });
  }

  render() {
    // this method has not changed
  }

이제 우리는 GamehandleClick 함수를 조금 바꿔야 한다.

먼저, 플레이어가 사각형을 클릭했을 때 stepNumber가 업데이트 되도록 this.setState 부분에 stepNumber: history.length,를 추가하자. 이걸 추가해야 플레이어가 사각형을 누를 때마다 턴이 증가하게 된다.

그리고 this.state.historythis.state.history.slice(0, this.state.stepNumber + 1)로 바꾸자.
이로써 게임을 플레이하다가 이전 턴으로 돌아갔을 경우에 그 이후의 턴들의 값을 다 날려버릴 수 있다.

  handleClick(i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      history: history.concat([{
        squares: squares
      }]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext,
    });
  }

마지막으로, Game 컴포넌트의 render 함수를 수정해서 stepNumber에 맞는 현재 상태를 보여주도록 하자:

  render() {
    const history = this.state.history;
    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares);

    // the rest has not changed

이제, 턴을 표시하는 리스트 버튼을 누르게 되면 게임 화면이 바로 그 때의 턴으로 바뀌게 된다.

현재까지 완성본

마무리

이제 드디어 tic tac toe 게임을 완성했다.
여기서 좀 아쉽다면 아래의 것들을 추가해보는 것도 공부에 도움이 될 것이다:

  1. 히스토리에 선택한 (행,열) 표시하기
  2. 히스토리에 현재 상태는 진하게 표시하기
  3. Board 컴포넌트 calculateWinner 함수에서 하드코딩한 부분 2중 루프로 다시 작성하기
  4. 오름차순/내림차순 으로 히스토리를 볼 수 있는 토글 버튼 만들기
  5. 플레이어가 이겼을 경우, 승리하게 된 3개의 사각형을 하이라이트 해주기
  6. 승패가 안가려지고 사각형이 다 채워졌을 경우, 비겼다는 메세지 보여주기

기초 문법, 리액트 컴포넌트와 같은 문서들이 많으니 참고하면서 진행하면 될 것 같다.

이상 뿅!

'개발 > React.js' 카테고리의 다른 글

리액트 공식 튜토리얼을 쉽게 해보자 2  (0) 2019.04.01
리액트 공식 튜토리얼을 쉽게 해보자  (0) 2019.03.29
React.js 개발 환경  (0) 2019.03.19

리액트 공식 튜토리얼

리액트 공식 홈페이지를 가보면 튜토리얼을 제공하고 있다.
공식 홈페이지에서 한글 언어를 지원하길래 튜토리얼을 의미하는 자습서를 눌러보니
여긴 뭐 그대로 영어라 사람들이 편히 이해할 수 있도록 포스팅을 해보기로 했다.
튜토리얼은 tic tac toe 라고 불리는 게임을 만들어보는 내용이다. 굳이 번역을 해보자면 삼목(?)이라고 할 수 있겠다.
(크롬을 열고 tic tac toe 를 검색 해보면 바로 tic tac toe 게임을 즐겨볼 수도 있다.)
링크를 보면 알겠지만 전체적으로 JSX나 Babel, ES6 등 문법적 내용도 있고 Component, props, state와 같은 리액트 개발에 필수적인 내용도 있다.
그래서 적지 않은 양이기 때문에 이 포스팅으로 편히 따라가길 바란다. 구성도 원본과 동일하게 하였다.
(튜토리얼 한글화 작업 중이긴 하다. https://reactjs-org-ko.netlify.com/tutorial/tutorial.html)

튜토리얼 섹션

튜토리얼은 아래와 같이 구성되어 있다고한다.

  • Setup for the Tutorial will give you a starting point to follow the tutorial.
  • Overview will teach you the fundamentals of React: components, props, and state.
  • Completing the Game will teach you the most common techniques in React development.
  • Adding Time Travel will give you a deeper insight into the unique strengths of React.

한글로 표현하자면,

  • 튜토리얼 환경설정어떻게 리액트를 시작할 지 알려줄 것이다.
  • 개요에서는 리액트의 기본Components, props, state에 대해 알려줄 것이다.
  • 게임 완성하기에서는 리액트 개발에서 자주 쓰이는 기술들에 대해 배울 수 있다.
  • 시간 여행 추가하기에서는 리액트만의 강점에 대해 더 깊게 이해할 수 있다.

그래서 우리가 만드는 게 뭐죠?

결과물CodePen 이라고 하는 곳에 올려져 있다.
CodePen은 굳이 환경설정을 하지 않아도 웹 상에서 쉽게 코딩해보고 결과물을 확인 할 수 있게 도와주는 곳이라고 보면 되겠다.

필요한 선행 지식

튜토리얼은 HTML, JavaScript에 대해 기본적으로 알고 있다고 생각하고 만들어졌다.
사실 그렇게 어려운 건 없어서 아무 언어로 프로그래밍을 해봤다면 쉽게 따라할 수 있을 것이다.
그리고 ES6(Babel)에서 추가된 let, const, arrow function과 관련된 내용도 있기 때문에 모른다면
가이드를 참고하기 바란다.

튜토리얼 환경설정

두 가지 방식이 있는데, 하나는 앞서 말한 CodePen에서 진행하는 방법, 하나는 로컬에서 진행하는 방법이다.
로컬에서 진행하려면 Visual Studio Code와 같은 에디터, Node.js 설치가 필요한데, 이건 기존에 작성한 나의 포스트로 대체하겠다.
로컬에서 진행하는 것은 코딩 시작하기에서 더 자세히 쓰겠다.

  1. CodePen으로 웹에서 시작하기
  2. 로컬에서 시작하기

개요

환경설정이 다 끝났으면 이제 본격적으로 시작해보자. 개요에서는 대략적으로 리액트와 리액트 문법에 대해서 작성되었다.

리액트가 뭐죠?

리액트는 유저 인터페이스를 만들어주는 JavaScript 라이브러리다. 리액트는 components 라고 불리는 각각의 조각을 붙여 화면을 만들게 도와준다.
리액트는 여러 컴포넌트 종류가 있는데, React.Component 로 설명을 하려고 한다.

class ShoppingList extends React.Component {
  render() {
    return (
      <div className="shopping-list">
        <h1>Shopping List for {this.props.name}</h1>
        <ul>
          <li>Instagram</li>
          <li>WhatsApp</li>
          <li>Oculus</li>
        </ul>
      </div>
    );
  }
}

// Example usage: <ShoppingList name="Mark" />

리액트는 컴포넌트를 통해 화면을 구성하게 되는데, 데이터가 바뀔 때 stateprops(properties의 준말)를 통하면
리액트가 알아서 효율적으로 데이터를 업데이트하여 화면에 다시 보여주게 된다.
render 메소드(method)는 return 에 화면에 보여줄 것을 담게 되는데, 리액트에서는 React element가 된다.
이것을 앞서 작성한 JSX 방식으로도 할 수 있고, 아래와 같이 React API 를 이용할 수도 있다.(보통 JSX를 추천!)

return React.createElement('div', {className: 'shopping-list'},
  React.createElement('h1', /* ... h1 children ... */),
  React.createElement('ul', /* ... ul children ... */)
);

코딩 시작하기

튜토리얼 환경설정에서 1.CodePen으로 시작한 경우에는
여기를 눌러 시작하면 된다.

  1. 로컬에서 시작한 경우에는 VSC에서 프로젝트 폴더를 열어서 시작하자.
    CSS를 공부할 건 아니라서 CSS는 튜토리얼에서 제공해주는 그대로 사용하자.

로컬에서 환경설정하기

  1. Node.js 최신버전 설치
  2. 프로젝트 폴더를 구성할 위치에서 아래 명령어 실행
npx create-react-app my-app
  1. src/ 폴더 아래에 있는 파일들 전체 삭제(src 폴더 자체는 남겨두자). 아래 명령어를 순서대로 실행해도 된다.
cd my-app
cd src

# Mac / Linux:
rm -f *

# Windows:
del *

# 프로젝트 폴더로 복귀
cd ..
  1. CSS code를 복사해서 src/index.css 에 저장

  2. JS code를 복사해서 src/index.js 에 저장

  3. src/index.js에 아래의 3줄 추가하기:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

그러면 프로젝트 폴더에서 npm start 명령을 통해 실행이 가능하다. 실행 후, http://localhost:3000 에서 3x3의 사각형들이 보이면 끝.
나중에 코드를 수정하고 저장하면 별도로 명령을 재실행하지 않아도 반영된다.

완료 화면

코드들을 살펴보면, 튜토리얼은 3개의 컴포넌트로 구성되어 있다.

  • Square
  • Board
  • Game

Square 컴포넌트에는 하나의 <button> 태그가 있고, Board 컴포넌트를 분석해보면 9개의 Square 컴포넌트가 들어간다.
우리는 /* TODO *//* status */를 채워서 게임을 완성시키자.

props를 통해 데이터 전달하기

튜토리얼에서는 복사/붙여넣기를 하지말고 직접 타이핑하는 것이 개발 실력 향상에 도움이 될 거라고 한다.

우선, Board class의 renderSquare 메소드를 다음 내용으로 바꾸자:

class Board extends React.Component {
  renderSquare(i) {
    return <Square value={i} />;
  }

Sqaurerender 메소드에서 /* TODO */{this.props.value} 로 바꾸자;

class Square extends React.Component {
  render() {
    return (
      <button className="square">
        {this.props.value}
      </button>
    );
  }
}

[Before]

Before

[After]

After

Before에서 After로 화면이 바뀌었으면 성공이다. Square가 상위 컴포넌트인(혹은 parent) Board에서 데이터를 받아 화면으로 보여주는 방식이다.
만약, 결과가 다르다면 링크의 코드와 비교하여 뭘 빠트렸는 지 확인하면 되겠다.

컴포넌트에 이벤트 추가하기

이제 Square를 클릭했을 때 X를 보여주도록 만들어보자. Squarerender 메소드를 다음과 같이 수정하자:

class Square extends React.Component {
 render() {
   return (
     <button className="square" onClick={() => alert('click')}>
       {this.props.value}
     </button>
   );
 }
}

Square를 누르면 click 메세지와 함께 경고창이 뜬다. 여기서는 => 처럼 ES6 문법인 arrow function이 사용되었다.
원래 목표는 사각형에 X를 띄우는 거란 걸 잊지말자. 이걸 위해서 state를 사용할 것이다.
리액트 컴포넌트는 생성자(constructor)에 this.state 를 써서 상태를 저장할 수 있다.
현재의 값을 this.state에 저장하고, Square가 클릭되었을 때 이 값을 바꿔주자.

우선, Square 컴포넌트에 생성자를 추가하고 초기화해주자:

class Square extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: null,
    };
  }

  render() {
    return (
      <button className="square" onClick={() => alert('click')}>
        {this.props.value}
      </button>
    );
  }
}

여기서 잠깐!
모든 생성자를 가지는 리액트 컴포넌트 클래스는 반드시 super(props) 를 호출해야한다.

이제, Squarerender 메소드를 수정하자.

  • 태그의 this.props.valuethis.state.value 로 교체
  • onClick={...} 내용을 onClick={() => this.setState({value: 'X'})} 로 교체
  • 가독성을 위해 className onClick 라인 분리

완성본 :

class Square extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: null,
    };
  }

  render() {
    return (
      <button
        className="square"
        onClick={() => this.setState({value: 'X'})}
      >
        {this.state.value}
      </button>
    );
  }
}

Square를 클릭하면 render 메소드의 onClick으로 인해 Squarethis.state.valueX가 될 것이다.
컴포넌트의 setState를 호출하면, 리액트는 자동적으로 하위 컴포넌트(혹은 자식, child)의 것도 바꾼다.
실행해서 클릭했을 때, 사각형에 X가 표시되는 지 확인하자.
안될 경우, 링크 확인!

개발 툴

크롬이나 파이어폭스에서
리액트로 만든 페이지를 검사할 수 있다.

검사 화면

리액트 개발툴에서 마우스 우클릭-검사를 하면 브라우저 오른쪽에 탭이 열리면서 propsstate를 확인할 수 있다.

CodePen에서 진행하는 방법:

  1. 로그인 / 회원가입 후 e-mail 인증(스팸 방지)
  2. Fork 버튼 클릭
  3. Change View 클릭 후 Debug mode 선택

게임 완성하기

이제는 Square를 클릭했을 때 XO를 번갈아가면서 나오게하고, 승자를 판단하는 것을 추가해야한다.

state를 부모(parent) 컴포넌트에게 전달하기

지금은 각각의 Squarestate를 가지고 있다. 누가 승자인지 판단하기 위해 9개의 Squarestate를 한 곳에서 체크해야한다.

Board가 각각의 Squarestate를 확인할 수도 있지만 이해하기가 좀 어렵고, 버그 걸리기 쉽고, refactor 하기 어려워 추천하지 않는다.
대신에, 우리는 각각의 Square가 아닌 Board에 게임의 state를 저장할 것이다. 그리고 이전에 우리가 각각의 사각형의 번호를 넘겼던 것처럼
BoardSquareprops를 전달할 것이다.

여러 자식들로부터 데이터를 가져오거나, 자식 컴포넌트 간 데이터를 주고 받아야할 때 공통된 state를 부모 컴포넌트에 선언해야한다.
부모 컴포넌트는 props를 통해 state를 자식 컴포넌트에게 전달할 수 있다. 이걸로 자식 컴포넌트들이 서로 동기화(sync) 될 수 있다.

리액트 컴포넌트를 refactor 할 때, state를 부모 컴포넌트로 올리는 것을 자주 하게 될 것이니 이번 기회에 해보는 것이 좋다.

Board에 생성자를 추가하고, 생성자 state에 9개의 Square에 대응되도록 squares라는 사이즈 9짜리 배열을 선언하고 null 로 초기화하자:

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
    };
  }

  renderSquare(i) {
    return <Square value={i} />;
  }

게임이 진행되면 this.state.squares는 이런 모습이 될 것이다:

[
  'O', null, 'X',
  'X', 'X', 'O',
  'O', null, null,
]

지금 BoardrenderSquare 메소드는 이럴 것이다:

  renderSquare(i) {
    return <Square value={i} />;
  }

이건 처음에 우리가 value를 props를 통해 전달해서 사각형에 0부터 8까지 숫자를 넣을 때 작성한 코드이다.
하지만 우리가 그 다음에 클릭한 경우 Xvalue를 가진 state를 가지도록 했고,
{this.state.value} 를 읽도록 했기 때문에 숫자는 볼 수 없었다.

이제 BoardrenderSquare 메소드를 수정해서 우리가 만든 squares 배열을 이용하도록 할 것이다.

 renderSquare(i) {
    return <Square value={this.state.squares[i]} />;
  }

현재까지 코드 완성본

이제는 Square를 클릭했을 때의 이벤트를 바꿔줘야한다.(지금은 X값 세팅만 있다.) 그러기 위해서는 Boardstate를 업데이트 해줘야한다.
하지만 state는 해당 컴포넌트 내부에 선언되어 있어 private하기 때문에, SquareBoardstate를 직접 변경할 수는 없다.
대신에, 우리는 함수(function)를 호출하는 것으로 이를 해결할 것이다.

BoardrenderSquare를 아래와 같이 바꾸자:

renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => this.handleClick(i)}
      />
    );
  }

이제 우리는 Board에서 Square로 2개의 props를 내려줄 것이다: valueonClick.
onClick 이란 propSquare가 클릭 되었을 때 호출 될 것이다.
그리고 Square에 아래 3가지를 수정하자:

  • Squarerender 메소드에서 this.state.valuethis.props.value로 변경
  • Squarerender 메소드에서 this.setState()this.props.onClick()으로 변경
  • Square의 생성자 삭제(이제 게임의 state를 여기서 관리하지 않는다)

다 반영하면, Square 컴포넌트는 이렇게 될 것이다:

class Square extends React.Component {
  render() {
    return (
      <button
        className="square"
        onClick={() => this.props.onClick()}
      >
        {this.props.value}
      </button>
    );
  }
}

Square를 클릭했을 때, 어떻게 동작하게 되는 지 살펴보자:

  1. <button> 안의 onClick이 리액트가 클릭 이벤트 리스너를 세팅하도록 명령한다.
  2. 버튼이 클릭되었을 때, 리액트는 Squarerender()메소드 안에 있는 onClick 이벤트 핸들러를 호출한다.
  3. 이벤트 핸들러는 this.props.onClick()을 호출한다. SquareonClick prop은 Board에서 선언되어 있다.
  4. 클릭되었을 때 BoardSquareonClick={() => this.handleClick(i)} 하도록 했기때문에, Squarethis.handleClick(i)를 호출한다.
  5. 아직 handleClick() 메소드를 구현하지 않아서 사각형을 클릭하면 다음 에러가 발생할 것이다.
    ``TypeError: _this3.handleClick is not a function''

<button>은 built-in 컴포넌트이기 때문에 onClick 을 써도 리액트가 이해할 수 있다.
하지만 Square처럼 커스텀 컴포넌트(이름을 새로 만든 경우)는 SquareonClickprop이나 BoardhandleClick 메소드 중 아무거나 써도 된다.
리액트에서는 props의 경우에는 on[Event]로, method의 경우에는 handle[Event]를 쓰는 것이 일반적이다.

자 그럼 Board클래스 안에 handleClick 이벤트를 구현해보자:

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
    };
  }

  handleClick(i) {
    const squares = this.state.squares.slice();
    squares[i] = 'X';
    this.setState({squares: squares});
  }

  renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => this.handleClick(i)}
      />
    );
  }

  render() {
    const status = 'Next player: X';

    return (
      <div>
        <div className="status">{status}</div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
          {this.renderSquare(7)}
          {this.renderSquare(8)}
        </div>
      </div>
    );
  }
}

완성본 링크

수정을 하고 실행해보면, 이전처럼 클릭했을 때 사각형에 X가 표시된다. 그럼 여태까지 뭐한거지라고 생각할 수도 있겠다.
다시 정리해보자면, 이전에는 각각의 Square에서 state를 관리했지만 이제는 Board에서 관리되기 때문에 여기서 승자를 결정할 수 있게 되었다.
BoardstateSquare가 내려 받아 사용하므로 Square는 이제 controlled components라고 할 수 있다.
Boardstate가 바뀌면 Square는 자동적으로 state를 업데이트하여 다시 화면에 보여주게 된다.

handleClick을 보면 .slice()라는 함수를 통해 기존 배열을 수정하는 대신 squares의 복사본을 만들게 되었다.
이 이유는 다음 섹션에 설명하도록 하겠다.

변경불가성이 중요한 이유

바로 앞에서 squares 배열을 그대로 쓰지 않고 .slice() 를 써서 배열의 복사본을 만들어서 접근하였다. 왜 그렇게 했는지 알아보면서 변경불가성에
대해 이해해보자.

데이터를 바꾸는 두 가지의 방식이 있는데, 하나는 데이터의 값을 직접 변경하는 것이다.
다른 하나는 바뀌었으면 하는 값의 새로운 복사본으로 교체하는 것이다.

데이터의 값을 직접 변경
var player = {score: 1, name: 'Jeff'};
player.score = 2;
// Now player is {score: 2, name: 'Jeff'}
바뀌었으면 하는 값의 새로운 복사본으로 교체
var player = {score: 1, name: 'Jeff'};

var newPlayer = Object.assign({}, player, {score: 2});
// Now player is unchanged, but newPlayer is {score: 2, name: 'Jeff'}

// Or if you are using object spread syntax proposal, you can write:
// var newPlayer = {...player, score: 2};

두 가지 방법의 결과는 동일하지만, 방식의 차이로 인해 아래의 차이가 난다.

히스토리 관리와 재사용

우리는 나중에 시간 여행 추가하기에서 tic tac toe 게임의 히스토리를 관리하고, 게임 진행을 "점프" 하게 할 것이다.
이렇게 했던 것을 또 하거나 취소하는 기능은 어플리케이션에서 자주 쓰이기 때문에, 데이터의 값을 직접 변경하지 않는 것이 좋다.
이전 버전의 값을 가지고 있을 수 있기 때문에 히스토리 관리나 재사용하기에 유리하다.

변화 감지

값을 직접 바꿨으면 변화를 감지하는 건 어렵게 된다. 하지만 새로운 복사본으로 교체하는 방식은 과거의 데이터와 바꾼 데이터가 있기 때문에 서로 비교하면 바뀌었는지 아닌지 알 수 있게 된다.

언제 화면에서 다시 그릴지 결정

우리는 변화 감지에서 본 것 처럼 데이터가 언제 바뀌었는지 알기 때문에 언제 컴포넌트가 다시 화면에 그려져야 하는지도 쉽게 정할 수 있다. shouldComponentUpdate() 를 어떻게 사용하는 지 성능 최적화 를 읽어보길 바란다.

함수 컴포넌트(Function Components)

Square 부분을 함수 컴포넌트(function component)로 바꿔보자.

리액트에서 함수 컴포넌트는 자신의 state를 따로 관리하지 않고 render() 메소드만 가지는 경우 간단히 쓰기 위해 사용된다.
React.Component 를 상속받지 않는 대신, props의 인자를 가지고 render될 부분을 return 하는 메소드를 만들 수 있다.
컴포넌트들을 무조건 React.Component를 상속받는 클래스로 선언하지 말고 위에 해당될 경우 함수형태로 간단히 만들자.

Square 클래스를 다음 함수로 바꾸자:

function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

여기서 기존의 this.propsprops로 바꾸었다.
또한, 기존의 onClick={() => this.props.onClick()}onClick={props.onClick} 로 변경하였다.

현재까지 완성본

순서 바꾸기 추가

지금은 사각형에 X만 표시된다. tic tac toe 게임에 맞게 순서대로 X, O 가 번갈아가면서 나오도록 수정하자.

처음 클릭했을 때는 X가 나오도록 할 것이기 때문에 Board의 생성자를 아래처럼 수정하자:

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
      xIsNext: true,
    };
  }

플레이어의 턴에 따라서 xIsNext의 값을 truefalse로 계속 바꿀 것이다. BoardhandleClick 함수를 수정하자:

handleClick(i) {
    const squares = this.state.squares.slice();
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      squares: squares,
      xIsNext: !this.state.xIsNext,
    });
  }

수정이 되었으면 실행해서 순서대로 X, O 가 번갈아가면서 나오는 지 확인해보자.

그리고 Boardrender()를 수정하여 어떤 플레이어의 차례인 지 보여주도록 수정하자:

render() {
    const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');

    return (
      // the rest has not changed

다 수정했을 때의 Board 클래스의 모습이다:

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
      xIsNext: true,
    };
  }

  handleClick(i) {
    const squares = this.state.squares.slice();
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      squares: squares,
      xIsNext: !this.state.xIsNext,
    });
  }

  renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => this.handleClick(i)}
      />
    );
  }

  render() {
    const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');

    return (
      <div>
        <div className="status">{status}</div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
          {this.renderSquare(7)}
          {this.renderSquare(8)}
        </div>
      </div>
    );
  }
}

현재까지 완성본

승자 결정하기

이제 플레이어의 턴이 누군지 알 수 있게 되었다. 이번에는 게임이 끝나는 상황을 위한 프로그램 작성을 하려고 한다.
이 함수를 index.js 파일 마지막에 붙여넣자:

function calculateWinner(squares) {
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
}

lines 배열에 흔히 빙고가 이뤄지는 경우를 미리 적어두었다.(배열은 index가 0부터 인 것을 잊지말자)
Boardrender 함수에서 calculateWinner(squares) 를 호출하여 승자를 확인할 것이다.
승자가 정해지면 Winner: XWinner: O 로 보여줄 것이다.
Boardrender 함수에서 status 부분을 아래처럼 바꾸자:

render() {
    const winner = calculateWinner(this.state.squares);
    let status;
    if (winner) {
      status = 'Winner: ' + winner;
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
    }

    return (
      // the rest has not changed

그리고 BoardhandleClick에서 이미 클릭되어 XO로 채워진 사각형을 클릭하거나, 승자가 이미 결정된 경우
클릭을 무시하도록 다음과 같이 변경하자:

handleClick(i) {
    const squares = this.state.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      squares: squares,
      xIsNext: !this.state.xIsNext,
    });
  }

현재까지 완성본

이제 정상적으로 동작하는 tic tac toe 게임이 완성되었다. 충실히 따라했다면 리액트의 기본도 어느 정도 이해할 수 있었을 것이다.

'개발 > React.js' 카테고리의 다른 글

리액트 공식 튜토리얼을 쉽게 해보자 2  (0) 2019.04.01
리액트 공식 튜토리얼을 쉽게 해보자  (0) 2019.03.29
React.js 개발 환경  (0) 2019.03.19

Keane 노래 중 마지막 포스팅이다. 멜론에서는 잘 듣고 있으나 노래방에서는 없어서 아쉽다.

 

가사

When you and you forget your name  
When all the faces all look the same

Meet me in the morning when you wake up  
Meet me in the morning then you'll wake up

If only I don't bend and break  
I'll meet you on the other side  
I'll meet you in the light  
If only I don't suffocate  
I'll meet you in the morning when you wait

Bitter and hardened heart  
Oh  
Aching  
Waiting for life to start

Meet me in the morning when you wake up  
Meet me in the morning then you'll wake up

If only I don't bend and break  
I'll meet you on the other side  
I'll meet you in the light  
If only I don't suffocate  
I'll meet you in the morning when you wait

Ooh ooh ooh  
Ooh ooh ooh

If only I don't bend or break  
I'll meet you on the other side  
I'll meet you in the light  
If only I don't suffocate  
I'll meet you in the morning when you wait  
I'll meet you on the other side  
I'll meet you in the light  
If only I don't suffocate  
I'll meet you in the morning when you wait

'음악' 카테고리의 다른 글

로맨틱브리즈(Romantic Breeze) - 별의 바다  (0) 2019.05.16
ADOY - Young  (0) 2019.04.03
Keane - Bend & Break  (0) 2019.03.29
Keane - Somewhere Only We Know  (0) 2019.03.13
Keane - Everybody's Changing  (0) 2019.03.12

마크다운 쓰는 방법

이런 깃허브 페이지는 물론 TrelloSlack 과 같이 개발 관련된 곳이면 마크다운(markdown)을 많이 적용하고 있다. 깃허브를 돌아다니다보면 제일 처음 보게되는 README.md 파일처럼 .md 확장자를 쓴다. HTML 파일에서 자주 볼 수 있는 마크업(markup;태그를 이용해서 작성하는 언어)을 쉽게 대체할 수 있는 것으로 생각하면 되겠다.

마크다운의 장/단점

장점

1. 익숙해지면 쓰기가 엄청 쉽다.
2. 적은 노력으로 나름 잘 꾸밀 수 있다.
 	3. 개발 관련된 곳에서는 점점 사용/지원하는 곳이 많아지고 있다.

단점

1. 통일이 안 되어 있다;Trello에서 작성한 걸 Slack에 붙여넣으면 적용 안되는 게 몇 개씩 나온다
 	2. 처음 써본 사람은 또 공부 해야한다.

개인적으로 디자인 감각이 부족하기 때문에 2. 적은 노력으로 나름 잘 꾸밀 수 있다.(특히 개발 관련 문서) 라는 점이 엄청 마음에 들어서 계속 사용하려고 한다.


마크다운 작성법

마크다운 작성법은 조금씩 다를 수 있기 때문에 Github 기준으로 맞춰서 작성해보려고 한다.

  1. 머릿말
  2. 강조
  3. 코드 블럭
  4. 링크
  5. 목록
  6. 수평선
  7. 이미지

1. 머릿말

1. ===
2. ---
3. #(##은 좀 더 작은 헤더가 됨 )

1.Header

1.Header
===

2.Header

2.Header
---

3.Header

# 3.Header

2. 강조

1. **굵은 글씨**
2. ~~취소선~~
3. `강조할 단어`

1. 굵은 글씨

**1. 굵은 글씨**

2. 취소선

~~2. 취소선~~

3.강조할 단어

`3.강조할 단어`

3. 코드 블럭

1. ``` 소스 코드 ```(or ~~~ 소스 코드 ~~~ )

``` 이나 ~~~ 뭘 쓰든 똑같아 보여 하나만 작성한다. 아직 뭐가 다른건지 잘 모르겠음.


# Set your Google Analytics id to receive `pageview` events.
# To remove Google Anaylics from your page, remove the line below.
google_analytics:

# Setting a disqus shortname will enable the comment section on pages with `comments: true` in the front matter
disqus_shortname:
```
# Set your Google Analytics id to receive `pageview` events.
# To remove Google Anaylics from your page, remove the line below.
google_analytics:

# Setting a disqus shortname will enable the comment section on pages with `comments: true` in the front matter
disqus_shortname:
```

```html, ```javascript, ```c 처럼 ```뒤에 해당 언어를 써주면 그 언어에 맞게 코드가 이쁘게 나온다.

import React, {Component} from 'react';
import './Movie.css';

class Movie extends Component{  
    render() {
        return (     
                <tr  className = "Movie" >
                    <th><MovieRank rank={this.props.rank}/></th>
                    <th>{this.props.title}</th>
                </tr>
        )
    }
}
```html
import React, {Component} from 'react';
import './Movie.css';

class Movie extends Component{  
    render() {
        return (     
                <tr  className = "Movie" >
                    <th><MovieRank rank={this.props.rank}/></th>
                    <th>{this.props.title}</th>
                </tr>
        )
    }
}
```

4. 링크

1. [텍스트](링크)
2. [텍스트](#헤더-네임)

구글로 이동

[구글로 이동](https://google.com)

링크

코드 블럭

[링크](#4-링크)
[코드 블럭](#3-코드-블럭)

Github에서는 #을 이용해 작성한 헤더를 자동으로 링크할 수 있게 해주기 때문에 작성이 쉽다.

헤더에 빈 칸이 있을 경우 -로 연결 해줄 것!!

헤더가 3. 코드 블럭 처럼 숫자, 온점이 있으면 [코드 블럭](#3-코드-블럭) 이렇게 해줘야 한다.

5. 목록

  1. 목록1
  2. 목록2
1. 목록1
2. 목록2
  • 목록1
  • 목록2
- 목록1
- 목록2

6. 수평선







* * *

***

*****

- - -

---------------------------------------

7. 이미지

Blog

많이 쓰일 것들만 작성해봤다.

추가로 주의할 점은 작성하다가 Enter키를 한 번 쓰면 단락 분리가 안된다. 두 번 써주거나 <br>태그 쓸 것.

이상 뿅. 


'개발 > Jekyll' 카테고리의 다른 글

마크다운(Markdown) 작성법  (0) 2019.03.19
깃허브 페이지를 만들어보자  (0) 2019.03.19

+ Recent posts