2019-07-23 어제 업데이트로 엘프 사냥꾼 및 패배한 플레이어의 아이템 승계가 가능해졌다. 최근에 S1이 마무리 되기도 했는데, PC에서는 롤토체스에게 점유율이 뺏겼지만 모바일에서는 아직 그래도 강점이 있다고 생각한다.

S1 시즌은 룩5에서 마무리 되었다. 매치 78판, 우승률 : 24%, 탑3 비율 : 58% 평균 순위 : 3.3 정도, S2 현재 시즌은 매치 35판, 우승률 28%, 탑3 비율 62%로 룩4단이다. 내가 즐겨쓰는 빌드를 소개해보려고 한다.

  1. 빙하기사
  2. 암살자
  3. 사냥꾼
  4. 조류족(엘프)
  5. 마법사
  6. 전사

(여기서 작성한 점수는 저만의 개인적 덱 파워 평가 점수입니다.)

1. 빙하기사(☆)

4빙하로 전체 공속 증가를 받고, 기사 시너지로 탱킹을 보조하는 빌드이다. 기사 시너지를 2,4,6 갯수마다 받을 수 있어 효율이 매우 좋기 때문에 잘 풀린다면 거의 모든 조합 상대로 충분히 1등을 할 수 있다. 약점은 정직한 덱이라서 앞라인부터 순서대로 정리해야하는 점, 초중반 운영이 어려운 점이다. 초반에 버서커 없는 2빙하 시너지로는 연승을 하기 힘들고(2성 2빙하라도 잘 진다), 기사 또한 저렙에서는 2성을 찍기가 힘들기 때문에 5,9,17 에 렙업하는 것보다 조금 더 빠른 템포로 렙업을 하는 것을 추천한다. 개인적으로는 13 라운드에서 경험치를 3번 사서 렙업 하는 것을 추천하는데, 버서커를 빨리 구해서 4빙하를 갖출수 있냐/없냐가 빙하기사의 초중반 운영을 크게 좌우하기 때문이다. 내가 원하지 않아도 연패코인 후 4빙하를 갖추고 연승하는 루트를 제일 많이 하게 되는 것 같다. 후반에 4기사 4빙하 정도 갖추었는데 자리가 남는다면, 언데드 시너지(악령 기사+소울 리퍼)를 추가하여 방깎을 노리거나 해적 선장이나 폭풍 주술사를 기용해서 상대 박스 배치 카운터를 노리자.

2. 암살자(★☆☆~☆)

초중반 매우 강력하게 나갈 수 있는 빌드이다. 아주 초반부터 3 암살자 시너지로 이득을 챙겨갈 수 있으며, 6 레벨을 찍으면서 바로 6 암살자 시너지로 템포를 빨리 끌어올릴 수 있다. 빠른 빌드 완성이 가능해서 존버 트리를 초중반에 많이 팰 수 있다는 장점이 있지만, 후반으로 넘어갈 수록 1등할 확률이 낮아진다. 그만큼 템포를 올려 빨리 상대방을 죽여야 하는 빌드다. 암살자는 종족 시너지 받는 것에 따라 좀 갈리는데, 주로 쓰는 것은 3 조류(엘프)로 탱킹을 좀 더 살려주는 것을 많이 쓴다. 유령의 그림자(조류 3코스트 암살자), 빛의 자객(조류 4코스트 암살자) 외에 자연의 예언자를 메인 탱커로 전방 배치하여 사용한다. 초반 금기 주술사가 잘 떠서 3성까지 사용해봤는데 이것도 나쁘지 않았던 것 같다. 또 다른 방식은 신과의 조합이다. 6암살자를 할 경우, 받는 종족 시너지가 악마 하나이기 때문에 전쟁의 신을 탱커로 두고 악마 캐릭터를 2개 넣어서 쿨타임 감소를 챙기는 것이다. 팬텀 퀸 궁극기가 쿨타임이 짧은 편이라 신 시너지와도 매우 좋다. 그 외에는 사실 쿨타임 한 번 더 돌리기 전에 상대를 죽이거나 죽기 때문에 큰 영향은 없어보이지만, 전쟁의 신 자체만 해도 탱커로 매우 좋아서 괜찮은 방식으로 생각한다. 리롤이 얼마나 잘되는지가 덱의 파워에 꽤 영향이 미친다. 다른 빌드로 구성하다가 너무 말렸을 경우에 전환해도 운이 괜찮으면 중간은 갈 수 있는 빌드라고 생각한다. 

3. 신(☆~★)

이걸로 한 번 우승하면 뽕맛 때문에 다음에도 어영부영 이 빌드를 타다가 망하게 만드는 마약같은 빌드. 전쟁의 신이 초반에 2성이 된다면 초반 연승도 쉽게 가능하며, 후반 천둥의 신까지 제 때 나온다면 순식간에 다 정리가 가능하다. 대신, 신 외에 종족 시너지를 받으면 안되기 때문에, 궁극기가 강한 특정 캐릭터만 골라서 써야한다. 그래서 중반부터 급격히 템포가 떨어지고, 2성작이 잘 안되거나 천둥의 신이 안나오면 손절각 보기도 어렵기 때문에 1등 아니면 꼴등하기 쉬운 빌드라고 할 수 있다. 주로 사용하는 캐릭터는 땜장이나 벌목기, 패전차 중 2개, 팬텀 퀸을 쓴다면 그림자 마귀나 헬나이트, 인간은 마나의 원천 단일, 빙하에서는 저주술사, 오거나 드워프는 단일 시너지라 사용불가, 정령은 우뢰의 정령, 언데드는 소울 리퍼, 조류족은 빛나는 용, 야수는 악마 독충을 쓴다. 그래서 직업 시너지로 3메이지 2 흑마법사 정도 가능하겠다. 신 시너지의 포인트는 쿨감이기 때문에 마나의 원천과 아이템으로 적절한 마나 수급과 짧은 쿨타임으로 최대한 이득을 보는 것이 중요하다.

4. 사냥꾼(☆~★)

사냥꾼은 초반부터 갖추기는 쉽지 않지만, 후반에 세이렌, 해일사냥꾼까지해서 6 사냥꾼이 되면 딜과 CC(군중제어)가 완벽한 덱이 된다. 대신 원거리 유닛을 사용하기 때문에 박스배치를 쓰는데, 역CC에 약할수도 있는 점을 조심해야한다. 초반에는 전사나 기사 시너지를 받으면서 앞라인 탱킹을 시키고 3 사냥꾼 시너지 정도로 유지하는 것이 좋다. 악령 기사와 언데드 순찰자로 초반 언데드 시너지를 받는 것을 추천한다. 박스 배치를 통해 원거리 유닛을 최대한 살리고, 세이렌이나 해일사냥꾼이 궁극기를 최대한 쓰도록 아이템을 조합하자. 최고 추천 아이템은 재생의 오브를 통해 세이렌이나 해일사냥꾼 궁극기를 두 번 이상 쓰도록 하는 것이다. 그 외 물리 데미지 관련은 언데드 순찰자나 드워프 소총수, 마법 데미지는 바람 순찰자에게 주도록 하자. 6 사냥꾼 조합이 갖춰지면 그 외에는 기사나 전사로 탱킹을 넣어도 되고, 사실 아무거나 센 거 넣어주면 되겠다.

5. 조류족(엘프)(★☆☆~☆)

조류족은 시너지 효과가 회피율 증가이기 때문에 카운터성이 짙은 덱이라고 할 수 있다. 회피는 사냥꾼 덱 시너지 효과로 카운터 당하기도 하고, 마법은 피할 수 없기 때문이다. 대신 이 것 외에는 충분히 강력한 힘을 발휘할 수 있는 덱이다. 우선 기본은 금기 주술사와 더불어 3 암살자나 3 사냥꾼, 아니면 드루이드+자연 쪽으로 시작하는 것이 좋다. 조류족은 직업 시너지가 암살자, 사냥꾼, 드루이드가 많은 편이기 때문에 이것들 중 하나를 갖춘 뒤, 나머지 하나를 더 추가하면서 완성하는 것이 좋다. 암살자를 쓴다면  유령의 그림자, 빛의 자객 외에 사막의 주인이나 팬텀퀸 등으로 3 암살자 시너지를 받고, 사냥꾼이면 날빛 궁수, 바람 순찰자 외에 하나를 더 추가하자. 드루이드 쪽에서는 자연의 예언자로 탱킹을 맡기는게 좋다. 악마 독충, 현명한 예언자 등 소환물 메타로 가는 것도 괜찮으니 잘 나오는 거로 골라서 해보자.

6. 마법사(☆~☆)

강력한 범위 마법으로 적을 한번에 쓸어담는 덱이다. 단점은 일정 수준의 데미지가 나오지 않으면 크립을 잡기에도 매우 힘들고, 물리 아이템의 효과가 매우 떨어진다. 장점은 높은 광역 데미지인데, 궁극기를 못 쓰고 죽을 경우도 많아 데미지 기대값이 고르지는 않다. 마법사의 키는 우뢰의 정령과 토르톨라족 장로이다. 초반에는 우뢰의 정령과 함께 땅이나 물의 정령으로 정령 시너지인 석화로 탱킹을 늘리는 것이 중요하고, 토르톨라족 장로가 메인 딜을 맡게 된다. 6 마법사는 최근 오거 마법사가 비활성화 되어 좀 힘들게 된 면이 있다. 그래서 사실 6 마법사를 하기보다는 신 시너지+마법사가 더욱 더 강해보인다. 마법사 시너지는 모든 적 체스말의 마법 저항력을 떨어트리기 때문에 굳이 마법사가 아니더라도 해적 선장처럼 좋은 광역 궁극기를 가진 캐릭터와 같이 사용해도 좋다. 앞서 말한 단점이지만 데미지 기대값이 고르지 못하고 탱킹에도 애매한 면이 많아서 개인적으로 내가 하기에는 제일 별로라고 평가하는 덱이다. 

7. 전사(☆)

초반부터 쉽게 받을 수 있는 시너지이고, 조합이나 배치도 딱히 어려운 것 없는 쉬운 덱이다. 그냥 전사만 주구장창 사서 센 애들만 올려두면 된다. 운좋게 초반 2성이 잘 뜨면 트럭하기도 쉽고, 3전사, 6전사 시너지까지 받기도 매우 쉽기 때문에 쉽고 강한 덱이라고 생각한다. 나쁘지 않게 풀려서 체력이 많다면 50골드를 유지하면서 10 렙을 찍고 천천히 남은 상대의 덱을 보고 9 전사 시너지+ 부족한 부분을 완성시키는 식으로 진행해보자. 전사 시너지는 방어력만 올려주기 때문에 나가 시너지를 갖춰서 부족한 마법방어력을 올리는 것도 좋고, 자이로콥터나 어둠의 정령으로 광역 데미지를 추가해주는 것도 좋다. 덱 구성이 쉬운데도 은근히 강력해서 나쁘지 않은 것 같다. 버서커를 쓰기 쉽기 때문에 4빙하를 갖춰서 6 전사 4 빙하를 쓰기에도 괜찮다. 개인적으로는 6 에서 9 시너지로 넘어가기 전에 좀 취약하다고 생각되는데, 마법 데미지 면역인 악마 독충 등을 중간에 섞어 최대 4 자연 시너지를 받는 것도 나쁘지 않았다. 잘 안풀리게 되면 6전사+3사냥꾼이나 3마법사도 차선책으로 쓸만하다. 해적 검사에게 마나/ 마법 데미지 관련 아이템을, 버서커에게 물리 데미지 관련 아이템, 도끼 전사나 전쟁의 신에게 방어템을 주는 것이 좋다. 재미는 없지만 충분히 한 번은 해볼만한 덱인 것 같다.

 

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

자신이 전공자인가?

우선, 자신이 컴퓨터 공학이나 관련 전공자인지, 아닌지로 구별해보자. 컴퓨터 전공이라면 이미 관련 지식을 가지고 있고 선후배나 여러 곳에서 쉽게 정보를 얻을 수 있을 것이다. 나는 전기공학을 나와서 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 에서 관리하는 것을 추천한다. 

메인화면


설치 및 시작하기

아직 정식 버전은 아니지만 최근 플레이 스토어Auto Chess(미출시)- Dragonest Game 로 앱이 올라왔다. 링크 참조. 지속적인 패치로 한글화나 게임 버전 등은 이제 큰 불편이 없는 정도가 되었다. 유닛들을 가져올 때, 도타 영웅들을 그대로 가져온 게 아니라서 이름의 변화가 조금 있어서 게임 전 미리 도감을 확인해 보는 것을 추천한다. 패치때마다 비활성화 되어 아예 게임에서 안 나오는 캐릭터들이 있기 때문이다. 전체적인 게임 진행이나 UI 등은 기존과 거의 흡사하기 때문에 별 무리 없이 재밌게 즐길 수 있을 것이다. 최근에는 내가 가지고 있는 캐릭터 하이라이트, 아이템 줄 영웅 추천 등 편의성을 위한 업데이트가 많이 이루어져 더욱 모바일로 즐기기에도 편해졌다.

게임 플레이

게임은 스탠다드/랭킹전/판타지 모드를 선택해서 즐길 수 있다. 전부 사람들과 할 수 있는데, 스탠다드는 그냥 노멀 게임이라고 보면 되고, 랭킹은 병사-기사-룩 등 계급을 가지고 등급전을 하게 된다. 랭킹전은 시즌제로 운영하고, 시즌마다 보상이 주어진다. 무슨 게임을 하던지 짐꾼을 뽑을 때 쓰는 사탕을 받을 수 있으니 모아서 짐꾼을 뽑아보자. 최근에는 데미지, 합성 이펙트, 감정 표현 등 다양하게 꾸밀 수 있게 업데이트 되고 있다.

게임의 기본 플레이 방식은 크게 아래와 같다.

  1. 캐릭터를 사서 자동으로 매칭되는 상대방과 싸우는 게임이다.
  2. 같은 캐릭터 3개를 모으면 업그레이드 된다.(ex.★x3 -> ★★x1)
  3. 캐릭터마다 가지고 있는 종족/직업 시너지를 갖추면 더 강력해진다.
  4. 아이템은 다양한 조합 방식에 따라 만들어 잘 어울리는 캐릭터에게 주자.
  5. 골드는 내가 가지고 있는 10원 단위마다 1원씩 발생(50골드 이상인 경우 최대 이자 5골드)
  6. 매치에서 승리하면 1골드, 연승/연패에 따른 추가 골드 발생
  7. 레벨에 따라 최대 체스판에 올릴 수 있는 캐릭터 수 증가

시너지?

최근 버전 유용한 시너지 조합에 대해 궁금하다면 심화편 을 참고해주길 바란다. 밸런스는 나름 잘 잡혀있는 편인 것 같다. 비활성화 되는 캐릭터 때문에 유닛 풀이 바뀌는 것만 생각해서 잘 플레이하면 순위권은 가능해보인다.

후기

출퇴근하면서 해봤는데, 지하철에서 딱히 끊기지도 않고 쾌적한 플레이를 할 수 있었다. 대신 플레이 타임이 스팀에서처럼 30분 정도 필요하기 때문에 사람이 혼잡한 상황에서는 좀 힘들수도 있겠다. 체스류 중에서 모바일로 하기에는 제일 괜찮아보인다. 가끔 업데이트 전에 접속도 못 하는 건 아쉽다. 랭크는 막더라도 일반 게임은 항상 할 수 있다면 더욱 좋겠다.

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


인트로

 

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

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

투더문(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

+ Recent posts