여기서는 간단하게 개발자가 되기 위한 여러 방법을 소개하려고 한다. 개발자로 취직을 하는 방법이라기 보다는 개인이 가지고 있는 아이디어를 어떻게 구현할 줄 아는 즉, 프로그램 개발이 가능한 사람이 되는 방법을 말해보고자 한다.

자신이 전공자인가?

우선, 자신이 컴퓨터 공학이나 관련 전공자인지, 아닌지로 구별해보자. 컴퓨터 전공이라면 이미 관련 지식을 가지고 있고 선후배나 여러 곳에서 쉽게 정보를 얻을 수 있을 것이다. 나는 전기공학을 나와서 C,C++ 정도는 과에서 수강했는데, 사실 그걸로는 계산기, 숫자야구게임 같은 걸 만들어본 게 전부였다. 여기서 더 나아가 사람들이 쓸 프로그램을 만들고 싶어서 컴공이나 전자공학 쪽 수업을 찾아 듣기도 했고, 학교에서 방학 때 진행한 앱 창작터라는 프로그램에 참여해 안드로이드를 배우고 앱을 만들기도 했다. 사실, 대학교 때 내 주위에는 어떤 것을 만들고 싶다는 사람들은 많이 있었지만, 그것을 실제로 구현할 사람은 많지 않았다. 그래서 흔히 말하는 외주를 맡겨서 홈페이지를 만들기도 하고, 뭣도 모르는 나에게도 많이 문의를 했었다. 하지만 전공자가 아니더라도 요즘은 유튜브나 인프런 같은 무료 강의를 접할 수 있는 곳이 많아서 누구나 충분히 원하는 것을 만들 수 있게 되었다.

전 아무것도 모르는데요?

괜찮다. 혼자 모든 것을 알 수도, 할 수도 없다. 물론, 언제 개발 언어 기초 문법부터 배워서 사람들이 쓸만한 페이지나 프로그램을 만들 수 있을지 막막할 것이다. 개발 언어도 C, JAVA, Python, Go 등 너무 다양해서 무엇부터 골라야 할 지 모른다면, 우선, 당신이 개발하고자 하는 것을 크게 3가지로 구분해보자.

  1. 홈페이지를 만들고 싶다.
  2. 안드로이드/Ios 앱을 만들고싶다.
  3. 게임을 만들고 싶다.

이것들을 어떻게 쉽고, 무료로 만들 수 있을 지 공유해보려고 한다.

홈페이지 만들기

1. 홈페이지를 만들고 싶다. 가 제일 큰 범위가 될 것 같다. 흔히 쓰는 쇼핑몰이 될 수도 있고, 블로그처럼 정보를 제공하는 곳이 될 수도 있겠다. 좀 더 디테일하게 풀어보자면 회원 가입을 받아서 고객 정보를 데이터베이스로 관리하고, 내가 팔고자 하거나 제공하고 싶어하는 것을 보여주고 고객의 결제까지 연동시키면 되겠다. 개발의 총 양을 10이라고 한다면, 보여지는 화면을 만드는 부분 7, 어떤 정보들을 데이터베이스에서 관리할 지 설계하고 구축하는 부분 2, 결제 연동 등 외부 서비스와 연결 1 정도로 대충 생각할 수 있겠다. 물론, 단순히 만들어보기로 끝나지 않고 많은 고객을 가지게 된다면 기본적인 서버 외에 개인 정보 보호를 위한 암호화, 보안 등 신경써야 할 부분이 많겠지만 그것은 차차 나중에 생각하고 프로토타입을 만들어서 결과물을 내는 것에만 집중하자.
아래는 React.js라는 웹 화면을 만들 때 쓰는 라이브러리로 영화 랭킹을 보여주는 페이지를 만드는 강의다. 영화 정보는 다른 곳에서 제공하는 Open API 라는 것을 통해서 랭킹, 다운로드 수 등을 받아올 것이다. 이걸 따라해보고, 영화 정보 대신 내가 보여주거나 팔고 싶은 정보로 바꿔서 만들면 되겠다 라는 생각이 들면 좋겠다.

 

모바일 앱 만들기

2. 안드로이드/Ios 앱을 만들고싶다. 는 두 가지 방법이 있다. 앞서 홈페이지 만들기에서 한 것을 웹뷰에 그냥 띄워서 보여주는 앱을 만드는 방법(하이브리드)과 아예 새롭게 앱을 만드는 방법(네이티브)이 있다. 요즘은 굳이 네이티브에만 집중하지 않고, 굳이 PC용 따로, 모바일용 따로 만드는 불편함을 줄이기 위해 하이브리드 방식을 많이 선택하는 편이다. 그리고 React Native처럼 모바일을 위한 라이브러리도 만들어져 있기 때문에 한 번 개발해서 PC, 모바일을 한 번에 지원하는 것을 추천한다. 여기서 개발자는 모바일 푸쉬나 알람, 진동 등 모바일 고유의 기능만 따로 추가하면 되겠다.
아래는 React Native로 to do list 앱을 만드는 강의다. 이 강의를 따라해보고 굳이 네이티브 방식을 안해도 되겠다라고 생각이 들길 바란다.

 

게임 만들기

3. 게임을 만들고 싶다. 는 앞서 본 것들과는 다르게 좀 더 특화되어 있다. 자신이 어떤 게임을 만들고 싶은지에 따라 Unity,Unreal 등 게임 개발 엔진을 쓸 지, 아니면 비교적 간단한 캐주얼 게임을 위한 cocos2d를 쓸 지, 앞서 홈페이지나 모바일 앱 개발 언어로 직접 구현할 지 선택할 수 있다. Steam이나 게임 유통 플랫폼에 올라오는 게임들은 UnityUnreal처럼 게임 엔진을 사용하는 경우가 많다. 게임 엔진이란 개념이 어렵다면, 3d 게임에서 쓰이는 중력(Gravity)과 같은 것을 미리 구현해놓은 것이다. 개발자는 직접 개발하기 어려운 중력에 대해 필요한 값만 넣어주어 원하는 방식대로 게임을 만드는 것이다. 이 외에도 카메라의 위치, 광원 등 다양한 요소를 지원해주기 때문에 대형 게임들도 이러한 게임 엔진을 많이 사용한다. 보통은 UI 쪽을 구성하고 해당 UI가 어떻게 동작할 지 스크립트를 작성해서 구현해주는 방식이다. UI 쪽에서 난 디자인은 못 하는데? 라고 생각한다면 Unity에서는 Asset Store가 있고, 거기에 무료로도 많이 올라와 있기 때문에 한 번 둘러보길 바란다. 무료 게임 강의는 아쉽게도 찾기가 힘들었다. 앞서 예시들처럼 간단한 프로젝트를 만들어보면서 배워야 좋은데, asset 까지 같이 무료로 제공하기가 힘든 문제도 있기 때문이지 않을까한다. 대신, Unity나 Unreal 중에 게임 2~3 개를 만들 수 있도록 소개와 가이드해주는 책을 사서 해보는 것을 추천한다. 보통 그런 책은 샘플 코드와 asset 도 같이 제공하기 때문에 개발이 편할 것이다. 여기서 중요한 것은 Unity나 Unreal 엔진 최신 버전에 최대한 가까운 버전을 쓰는 책을 고르는 것이다.

마무리하며

개인적으로 처음 개발하는 사람에게 제일 중요하게 생각하는 것은 한 번 성취감을 맛보는 것이다. 나는 개발하는 과정이 그리 즐겁지 않다. 시간도 오래 걸리고, 해결되지 않는 버그와 씨름하며 머리 아프기 때문이다. 하지만 내가 만들어보고 싶은 것을 스스로 만들어낸다는 것이 좋다. 물론, 유저들에게 쓰디쓴 평가를 받기도 하고, 대박칠 줄 알았던 앱이 시덥지 않기도 하다. 대신, 내가 만든 프로그램으로 돈을 벌기도하고, 취직할 때 도움이 되기도 했다. 본인이 개발자가 아니라 기획이나 마케팅 등 다른 분야로 갈 생각이라고 해도, 개발을 경험한 것과 아닌 것에 대한 차이는 크다고 생각한다. 개발자로 진로를 계획하고 있다면, 본인이 개발하는 것들을 Github 에서 관리하는 것을 추천한다. 

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

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

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


인트로

 

우선, 형상관리는 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' 카테고리의 다른 글

개발을 어떻게 시작해야 할 지 모르겠다면?  (0) 2019.07.09

시간여행 추가 부분을 추가하려고 하니, 문서 길이가 너무 길어서 그런지 오류가 발생한다.
그래서 어쩔 수 없이 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' 카테고리의 다른 글

리액트 공식 튜토리얼을 쉽게 해보자  (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
React.js 개발 환경  (0) 2019.03.19

마크다운 쓰는 방법

이런 깃허브 페이지는 물론 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' 카테고리의 다른 글

깃허브 페이지를 만들어보자  (0) 2019.03.19

블로그 작성을 해보기로 결심한 후에 저번에 작성한 어떤 블로그를 쓸 것인가? 포스트처럼 여러 블로그 플랫폼을 알아보고 최종적으로 깃허브 페이지를 이용하기로 결정했다.

Blog

깃허브 페이지를 무작정 만들어보려고 하니, 할 게 너무 많아서 잘 되어 있는 걸 가져와야겠다고 생각이 들었다. 깃허브에서 Jekyll 검색 하면 여러 테마들을 볼 수 있다. 그런데 내가 필요한 것들이 다 구성되어 있는 걸 찾기가 힘들었다.(깃허브 페이지는 필요한 기능들을 하나씩 다 구성해줘야한다.)

필요한 기능

  1. 카테고리 구성
  2. 댓글
  3. 통계

구글링하다보니 박민(isme2n)님 깃허브 페이지 가 제일 맘에 들어서 가져다 쓰기로 정했다.

맘에 드는 테마 가져오기


  1. 라이센스 확인
  2. fork
  3. config 등 수정

우선 맘에 드는 테마를 가져오려고 해도, 이게 지적 재산권, 저작권 침해인지 확인을 해야한다. 박민(isme2n)님 깃허브 페이지 라이센스 확인해보니 MIT License라 편히 사용해도 되는 것 같다. 그래도 많은 사람들이 댓글로 써도 되는지 물어보고 사용하는 걸로 보인다.

( 1. 라이센스 확인 )

fork 하는 법은 박민(isme2n)님 깃허브 페이지 여기서 fork 버튼만 누르면 된다.

그리고 깃허브 페이지는 본인계정.github.io 로만 만들어지므로 Settings에서 Repository 를 Rename 해주면 된다. 
(ex. https://jmhmunhwan.github.io/)

( 2. fork )

fork 받은 것들을 살펴보면 수정해야 할 것들이 많다. 우선 _config.yml 파일 에 공용적으로 쓰이는 환경 설정 정보들이 많으므로 이걸 수정했다. 살펴보니 댓글은 DISQUS로, 통계는 Google Analytics 로 사용하고 있다. 나도 해당 기능을 쓰고 싶으니 id 같은 값만 내걸로 바꿔서 쓰면 될 듯 하다.

# 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:

그리고 _includes/contents.html 에서는 구글 애드센스가 적용되어 있다. 아래와 같이 data-ad-clientdata-ad-slot을 내걸로 바꿔주면 끝. 이 파일 외에도 right-side.html,post.html에도 만들어져있다. 애드센스를 제대로 적용하려면 가입하고, 사이트 검증하고, 광고 단위를 만들고 적용해야하는데, 이것도 다음에 따로 포스팅해야겠다.

<div class="markdown-body">
  <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
                <!-- 블로그-하단-반응형 -->
                <ins class="adsbygoogle"
                    style="display:block; width:98%; height:300px;"
                    data-ad-client="             "
                    data-ad-slot="        "
                    data-ad-format="auto"></ins>
                <script>
                (adsbygoogle = window.adsbygoogle || []).push({});
                </script>
                <br/>
  
</div>
<br/>
<br/>
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<!-- 블로그-하단-반응형 -->
<ins class="adsbygoogle"
    style="display:block; width:98%; height:300px;"
    data-ad-client="             "
    data-ad-slot="        "
    data-ad-format="auto"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>

잘 적용되었나 보자. 잘 적용되었으면 아래에 광고가 나올 것이다.

( 3. config 등 수정 )

위 방법으로 하면 원작자님이 쓰던 것들도 다 들고와서 좀 난감하다. About.html 같은 건 수정하면 되지만, 포스트들은 하나씩 삭제해야한다. 이건 Git으로 commit, push 해야겠다.

포스팅하기

_posts 폴더에 md 파일을 작성하면 알아서 포스팅이 된다. 테마별로 각각 js를 이용해서 포스팅 되도록 코딩되어 있기 때문에 맞춰서 하면 된다. 2019-02-11-Github Page.md 처럼 YYYY-MM-DD-TITLE.md 로 저장하면 된다. 이 테마에서는 _featured_categories_featured_tags로 카테고리를 구별하게 만들어져있다. 대분류가 카테고리, 소분류가 태그로 이해하면 된다. 실제 적용은 아래처럼 하면 된다.

---
layout: post
title: 깃허브 페이지 작성해보기
subtitle: 5분만에 따라해보기
gh-repo: jmhmunhwan/jmhmunhwan.github.io
gh-badge: [star, fork, follow]
category : devlog
tags: blog
comments: true
---

tag 에 적어놓은 blog 처럼 기존에 없는 걸 추가할 경우에는 해당 폴더에 다른 md 파일들을 보고 양식을 맞추어 새롭게 하나 만들어주면 된다. (ex. devlog-blog.md 파일 )

---
layout: tag-blog
title: Blog
slug: blog
category: devlog
menu: false
order: 3
---

마크다운 사용법에 대해서는 따로 기술하겠다. 이만 뿅. 

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

마크다운(Markdown) 작성법  (0) 2019.03.19

리액트가 뭐죠?

React.js는 웹 개발용 javascript 라이브러리다. 이게 뭐냐고 물어보면 그냥 웹 화면을 쉽게 개발할 수 있게 도와주는 거라고 말하고 싶다. Spring으로 웹 개발을 해봤다면 JavaApache Tomcat가 익숙하겠지? React.js 에서는 JavaScriptNode.js 가 그 역할을 대신한다고 볼 수 있다.

좀 더 개념을 정리해보자면 Spring이나 React.js 는 웹 페이지를 개발을 쉽게 도와주는 Framework 이다. Java나 JavaScript는 개발 언어가 되겠고, Apache Tomcat과 Node.js 는 엄밀히 말하면 좀 다를 수 있겠지만 서버를 띄워주는 역할을 한다. 이를 간단히 표로 비교해보면 아래와 같다.


FrameworkSpringReact.js
LanguageJavaJavaScript
Server SideApache Tomcat(Apache Maven)Node.js(npm)


Spring은 우리나라가 유난히 Java를 많이 쓰는 곳이라 아직도 많은 곳에서 쓴다. 하나의 페이지를 추가 개발하려면 Controller, Service, ServiceImpl, DAO 등등 거의 하나씩 새로 또 만들어줘야했다. 보통 Spring으로 개발할 경우에는 Dependency가 필요한 데, 이를 Maven을 통해서 관리되도록 했다. 이 때, Java등 버전을 또 제대로 못 맞추면 빌드하기가 힘들었다. Spring에서도 위와 같은 문제 때문에 Spring Boot로 좀 더 쉽게 개발할 수 있도록 지원하고 있다.

React.js는 facebook에서 밀고 있는 framework이다. Spring Boot처럼 쉽게 리액트 개발을 시작하기 위해서는 facebook에서 제공하는 create-react-app을 이용하면 된다. 우선 create-react-app 으로 이동해서 README.md 파일을 한 번 보자. Creating an App 여기 밑으로 개발 환경을 어떻게 맞추면 될 지도 상세히 적어두었다. 나같이 어줍잖은 블로그보다 이런 공식 깃헙을 보는게 개발 환경을 정할 때는 중요하다. (보니까 내가 쓰던 npm도 버전이 올라가면서 npx로 쓸 수 있게 변경이 되었나보다)

개발 환경

  1. Node.js설치(최신 LTS 버전: 10.15.1 (includes npm 6.4.1))
  2. Visual Studio Code(자바스크립트 개발툴;구글에서 VSC로 검색하면 편함)
  3. Yarn(Stable: v1.13.0; 굳이 없어도 되지만 하는 김에 같이 해보자 )

설치는 그냥 다운로드 받고 실행-Next-Finish 만 하면 된다.(자동으로 PATH 등록이 될 것이다)

실행하기

이제 Node.js를 이용해서 create-react-app을 가져올 것이다. 개발환경을 위와 동일하게 맞췄다면 터미널을 열어서 아래의 명령을 실행하자.

음..터미널이 뭐에요? 터미널 중에 뭘 해야하죠?
  1. 윈도우 키-모든 프로그램을 눌러보면 Node.js가 보일 것이다. Node.js command prompt 를 실행하자.
  2. 윈도우 키-프로그램 및 파일 검색-cmd 입력
  3. VSC 에서 Ctrl+` 을 누르면 터미널이 열린다.

npx

npx create-react-app my-app

실행이 잘 된다. 나는 Yarn도 미리 설치가 되어 있어서 그런지 yarn 명령어를 추천해준다.

cd my-app
yarn start

이렇게하니 바로 http://localhost:3000/ 가 띄워지고 거기서 리액트 로고가 열심히 돌고 있다. 이러면 리액트 환경설정이 끝났다. 이제 js나 css를 수정하면 나만의 웹 페이지를 개발할 수 있게 된다. Yarn이 설치가 안되어 있는 경우는 어떻게 뜨는 지 확인하기가 좀 귀찮으니 Node.js 에서 Yarn 설치하는 방법만 추가로 작성하겠다.

Node.js 에서 Yarn 설치하기
npm install yarn

여기서 -g 옵션을 줘서 글로벌하게 쓸 수 있게 할 수도 있는데, 글로벌을 남용하면 이상하게 꼬일 수 있으니 조심하자. Node.js 명령어들에 대해서는 다음에 한 번 정리해야겠다. 이상 끝. 

+ Recent posts