저번에 16강, 8강, 4강, 결승전이 진행되도록 설계(?)를 끝냈었다. 이제 16강일땐 16강 텍스트가 상단에 출력되고, 8강일 땐 8강, 4강일 땐 4강이 출력되고 결승전일땐 결승전이 출력되도록 해야 했다. 4강까지는 숫자 + 강의 형태이고 결승전만 결승전이 출력되도록 하면 되는 거였고, roundOf라는 state로 몇 강인지에 대한 정보를 담고 있으니 다음과 같이 표현하기로 했다.
1) 만약 roundOf가 2라면 -> 결승전 출력
2) 2가 아니라면 -> roundOf + 강 출력
코드로는 삼항연산자를 통해 다음과 같이 작성할 수 있었다.
이제 딱 하나만 하면 된다. 바로 결승전에서 선택된 컴포넌트만 따로 출력하도록 하는 것! 기존에 2개씩 DessertItem이 출력되던 화면을 최종선택된 DessertItem 하나만 출력하는 식으로 화면에 렌더링되는 컴포넌트자체를 싹 바꿔야 하는 상황이었다. 옛날에 멋사에서 Django를 배울 땐 탬플릿 태그? 정확한 명칭은 기억 안나는데..암튼 그거를 통해 특정 조건 안에선 어떤 요소들이 보이게 하고 다른 조건에선 안 보이게 하고 이걸 처리하는 게 쉬웠는데, 리액트로는 이런 걸 어떻게 할 수 있을지 알아봤다. 즉 조건부 렌더링을 어떻게 처리할 수 있을지 고민한 것이다.
구글에 쳐서 나오는 공식문서를 참조한 결과,,황당하리만치 간단했다.
컴포넌트 작성 시 결국 return문을 사용하게 되는데, 각 조건 별로 return되는 내용 자체를 다르게 작성하는 것이다.
그래서 App.js에 다음과 같이 작성해줬다. roundOf가 1이라면, 최종선택된 컴포넌트만 보여주도록!
※ 이 외에도 삼항연산자나 논리연산자를 위한 다양한 조건부 렌더링 방법이 존재하며, 부모로부터 전달받은 prop의 값에 따라 컴포넌트에서 null을 리턴하도록 작성하는 방법 등등 다양한 조건부 렌더링 방법이 존재한다.
이제 기본적인 디자인을 입히기로 했다. 따로 css파일을 만들고 각 컴포넌트가 작성된 js파일에서 이를 import하는 식으로 쓰기로 했다. 이렇게 하면 컴포넌트별로 style태그가 만들어져서 적용되는 효과가 생긴다!
화면 최상단에 옛날에 만들어준 JofeStudio 로고를 이용해서, 이 로고를 클릭하면 내 유튜브 페이지로 이동하도록 만들었다. a태그쓰면 되는 거니까 간단히 끝날 줄 알았는데, 예기치 못한 오류에 직면했다. 나는 현재 페이지가 이동되는 게 아니라 새로운 페이지에서 유튜브 페이지가 나오길 원했기 때문에 target="_blank"를 사용했는데,
Using target=_blank without rel=noopener noreferrer is a security risk
이런 에러 메시지가 뜬 것! 구글링해보니까 이 역시 바로 해결방법이 나왔다. 우선 에러가 뜬 원인은 이렇게 하면 tabnabbing이란 피싱 공격에 노출될 수 있기 때문이라 한다. 새롭게 열린 탭에서 기존 페이지를 피싱 페이지로 바꾸는 공격이라 한다. 이를 해결하려면, a태그의 rel속성으로 "noopener noreferrer"를 줘서 해결할 수 있었다. 이 속성이 부여된 링크로 열린 페이지는 location변경과 같은 js요청을 거부한다고 한다.
※ 참고 : 리액트는 단일 URL을 가지고 SPA(single page application)으로 사이트를 표현하는 프레임워크이기 때문에 a태그 등으로 새로 페이지를 부르면 앱이 지니고 있는 상태가 초기화되고 렌더링된 컴포넌트도 모두 사라지게 된다. 따라서 리액트에서는 a태그보다는 link태그의 사용을 권한다고 한다. a태그의 href는 페이지를 이동시킬 때 페이지를 새로 불러오는 방식이라 상태 값이 유지되지 못하고 속도도 저하된다고 함..(다만 내가 지금 한 것처럼 새 탭에 띄울 때는 상관 없는 듯 하다)
end system들은 access ISP를 통해 인터넷에 연결된다. 이 access ISP들도 서로 연결된다면 어떤 end system들이던 간에 서로 packet을 교환할 수 있을 것이다.
이때, 수백만 개의 access ISP들을 어떻게 연결 즉 네트워크를 어떻게 구성할까?
1) 모든 access ISP들을 서로 연결
이런 구조를 마치 그물망 같다고 해서 "mesh 구조"라고 부른다. 당연한 얘기지만 비효율적인 방법.
2) 중간에 하나의 글로벌 ISP(global transit ISP) 놓기
일종의 중간 다리 역할을 해주는 ISP를 놓은 것이라 보면 된다. 모든 access ISP들이 global ISP를 통해 다른 access ISP로 가는 것이며, 이 때 global ISP엔 돈을 지불한다. 우리가 KT인터넷 같은 거 쓰면서 돈 내는 것처럼 말이다.
3) 여러 글로벌 ISP들이 생기고 이들을 이어줌
만약 중간 다리 역할을 해주는 글로벌 ISP가 하나라면 데이터가 한 곳에만 계속 모이니 동작을 제대로 하지 못 할 수도 있고, 그 글로벌 ISP가 지 맘대로 독점적으로 비용을 책정하면 서비스 이용에 큰 불편이 따른다. 그래서 많은 회사가 글로벌 ISP 서비스를 시작하였고 이들끼리 하나의 군집을 이루는 식으로 발전됐다.
그리고 이 ISP들 간에도 연결이 돼야 하는데, peering link를 통해 서로 우리 둘은 무료로 packet교환하자!라면서 서로 네트워크를 연결하게 됐다. 그리고 둘 뿐만이 아니라 다수의 ISP들이 서로 peering할 수 있는 만남의 장소로 IXP를 만들어 이를 통해 서로 간의 packet을 교환하게 됐다. 즉 IXP가 이 네트워크 계층 구조의 최상위에 있는 것이라 볼 수 있다.
4) regional net, content provider network의 등장
이후 글로벌 ISP뿐만 아니라 지역의 네트워크를 모아서 서비스해주는 regional net(지역 네트워크)도 등장하였다. 또한 구글이나 넷플릭스 등과 같은 회사에서 만든 content provider network라는 것도 생겨났다. 이 회사들은 자신들의 고유한 사설 네트워크를 구축하고 하위 ISP와 직접 peering하는 등의 방법으로 상위 계층 ISP를 우회하는 방식을 쓴다! 자신들의 데이터를 일반 네트워크를 통해 전달하게 되면 수많은 트래픽이 생기고, 이 때문에 굉장히 많은 비용을 청구받게 되니 만들게 된 것. 그래서 자체 네트워크를 통해 서비스하도록 하는 것이다.
dessertList에서 selectIndex와 그 다음 번째의 디저트들을 보여주고, SelectButton도 같이 보여준다. 이 때 onSelect prop으로 저번에 작성한 selectDessert메소드를 넘겨준다.
그러나 지금까지의 selectDessert메소드는 단순히 selectIndex를 2씩 증가시키는 기능만 했다. 그러면 이제 이 메소드에 선택한 애들을 골라담아주는 기능도 넣어야 한다.
1. 선택한 애들 담기 - 파라미터가 있는 메소드를 prop으로 주는 법을 배우다
처음에 고민이 됐던 건,.선택하기 버튼을 분리한 마당에 특정 디저트를 어떻게 선택버튼과 매칭시켜서 담게 할 것인가였다. 그러다가 간단히 해결될거란 생각이 들었던게, selectDessert의 prop으로 특정 디저트들을 보내면 될 것 같았다. 선택된 디저트들을 담을 selectedList라는 state를 만들고, 선택하기 버튼이 눌릴 때마다 해당 디저트들(정확히는 DessertItem 컴포넌트)를 거기에 추가해준다! 따라서 selectDessert메소드는 선택된 디저트를 파라미터로 받아 다음과 같이 처리하도록 했다.
onSelect라는 prop으로 selectDessert라는 메소드를 주는 모습. 그러나 내가 지금 원하는 건 onSelect라는 prop으로 "특정 디저트 아이템을 파라미터로 받는 selectDessert메소드"를 넘겨야 한다!! 근데 그렇다고 다음과 같이 수정하면 당연히 안 될 것이다.
<SelectButton onSelect={selectDessert(item)}/>
ㅋㅋㅋㅋㅋ 저렇게 되면 item을 인자로 쓰는 selectDessert메소드를 prop으로 넘기는 게 아니라 호출된 값?을 넘기는 식으로 쓰일 것이다. 내가 아무리 잘 몰라도 이 정도 상식은 있는 사람이다..와 이거 어떡하지? 어떻게 해야 하지? 라는 생각이 들었다. 모를 땐 역시 구글링이 답이라고, 검색해보니 금방 방법을 찾을 수 있었다.
이제 라운드 시스템만 잘 만들면 대략적인 틀은 다 만들게 된다. 처음 16개 중 8개만 고르면 8강을 진행하고, 거기서 또 4개를 고르면 4강을 진행하고 이런 식으로 가야 한다.
어떻게할까..고민했다. 16개중 8개를 고르는 순간 selectIndex가 14에서 16으로 바뀌기 때문에, 16으로 바뀐 순간 selectedList를 dessertList에 덮어씌우기로 했다. 그래서 다음과 같이 코드를 짰는데, 뭔가 이상했다.
roundOf는 state값으로 처음엔 16을 지닌다. setter함수(setSelectIndex)를 통해 selectIndex값을 2만큼 늘려주고 나서 selectIndex값이 16일 때의 동작을 정의한 것인데, 문제는 16이 된 순간에 바로 setter함수들이 동작하지 않는다는 것이었다. 뭔가 이상하다 싶어서 setSelectIndex 전후로 console에 selectIndex값을 찍어봤는데 같은 값이 나왔다. ...도대체 뭐지?
바로, 리액트의 setter함수는 비동기적으로 동작하기 때문이었다.
react는 state가 변경될 때마다 그 state를 갖는 컴포넌트를 새로 렌더링하는데, state를 바꾸는 작업이 한꺼번에 이루어지면 그만큼 많은 렌더링이 발생하므로 이를 방지하기 위해 일종의 대기열에 넣고 변경사항을 한꺼번에 적용한다. 식당을 예로 들면 여러 테이블로부터 주문이 들어올 때마다 그때그때 받는게 아니라 마지막 테이블이 주문할 때까지 다 기다리다가 한꺼번에 받는 식. 하나의 이벤트 핸들러에서 state변경이 여러번 이뤄진다면 이벤트가 끝날 시점에 state변경을 일괄적으로 진행한다고 한다.
따라서, 내가 한 방식처럼 같은 메소드에서 setter함수로 바꾸자마자 그 값의 변화에 따른 동작을 하는 것이 예기치 못한 오류?를 발생할 수 있었던 것.
해결하는 방법은 setter함수를 호출할 때 두번째 파라미터로 콜백함수를 넣는 것이었으나, 이 기능은 없어졌다고 한다. useEffect의 사용을 권장한다고 함!! 딱 구글링으로 이렇게 보자마자 무릎을 탁 쳤다. useEffect는 state가 변할 때마다 실행되도록 만들 수 있으니까!!
전 세계 여러 컴퓨터를 하나로 연결하는 거대한 통신망을 의미하는 개념. 클라이언트와 서버로 구성되며 TCP/IP라는 기본 프로토콜을 통해 제공된다. 프로토콜에 대한 개념은 이후에 다루니 지금 당장은 패쓰.
이 인터넷을 구성요소 관점에서 보면 다음과 같다.
우선 인터넷을 구성하는 요소들은 hosts, communication links, packet switches가 있다. hosts는 우리가 쓰는 PC, 스마트폰, 노트북, 서버 컴퓨터 등의 수많은 장치를 표현하는 개념이며 네트워크의 끝단에 위치한다고 해서 end systems라고도 부른다. 웹 브라우저, 롤, PC카톡 등의 응용 프로그램들이 이 end systems에서 돌아간다. communication links는 이 end systems들을 유/무선으로 연결해주는 것으로 fiber(광섬유), copper(구리선) 등이 있다. 일종의 도로 역할을 해준다고 보면 되며, 각 link들은 저마다의 전송률(bandwidth라고도 부름)을 가진다. packet switches는 라우터나 스위치를 말하는 것으로 packet들(일종의 정보 패키지. 도로에서 실을 것을 싣고 목적지로 가는 트럭으로 이해하면 쉽다)을 전달하는 역할을 한다. 도로들을 달리다가 교차로를 만날 때, 어디로 가야 할지를 알려주는 역할이라 보면 됨.
각 End System들은 ISP( = Internet Service Provider)를 통해 인터넷에 접속한다. 이 ISP들을 라우터라 부르기도 하는 것이며, 우리 나라에서의 KT, SKT등 인터넷 서비스를 제공해주는 애들이라고 생각하면 된다. 이 ISP들은 regional ISP, global ISP처럼 계층 구조를 가진다.
packet을 트럭, comminication links를 도로, packet swiches를 교차로에 비유했다. end systems는 빌딩에 비유할 수 있다. 왜 빌딩인지 한번쯤 생각해보는 것이 중요하다. 빌딩에 여러 호실이 있는 것 때문에 빌딩이라 비유한 것이다. 내 컴퓨터에서도 브라우저를 여러 개 띄울 수 있고 PC카톡도 할 수 있고 롤도 할 수 있는데, 내 컴퓨터를 end system이라 하면 이 컴퓨터에 오는 트럭이 A브라우저로 갈지 B브라우저로 갈지 PC카톡으로 갈지 롤로 갈지 뭐 그런 상황들이 있다. 그래서 end system을 빌딩에 비유할 수 있는 것이다.
2. 프로토콜이란 무엇인가?
end system과 라우터 등이 서로 communication links를 통해 연결돼있는 것을 앞서 언급했다. 이들의 통신과정은 송신단과 수신단으로 구분된다. 프로토콜은 이때 이렇게 서로 연결된 애들 사이에서의 통신 방법을 의미한다. 사람이 의사를 전달하고 대화할 때도 어떤 형식과 절차가 있는 것처럼 얘네들 사이에서도 특정 형식과 절차가 있고, 이런 것을 프로토콜이라 부른다. 정리하자면 둘 이상의 통신 개체 간에 교환되는 메시지들의 순서와 형식, 그리고 메시지 교환 이후의 동작을 정의하는 것이 프로토콜이다.
3. 네트워크의 구조
1) network edge
네트워크 끝단(edge)에는 end systems = hosts들이 있으며, host는 request의 주체인 클라이언트와 response의 주체인 서버로 구분하기도 한다.
2) access networks
end system이 네트워크에 접근하기 위한 네트워크를 말한다. 한 end system으로부터 다른 먼 거리의 end system까지의 경로 상에 있는 첫 번째 라우터에 연결하는 네트워크를 말하는 것임. 쉽게 생각해 end systems들이 인터넷을 사용할 수 있도록 처음에 길을 터주는 놈이라 보면 됨. 랜선을 떠올리면 이해하기 쉽다. 유선으로 네트워크를 사용하려면 PC에 랜선을 꼽아서 사용해야 하니까..
3) network core
수많은 라우터들이 서로 그물처럼 얽혀있는 구조를 말하며 쉽게 말하면 서로 연결된 모든 라우터들을 말하는 개념
4. access networks 찍먹
일단 뭐 단순 가정집에서의 네트워크에서 access networks를 생각해보자. 다음처럼 3가지 종류가 있다.
1) DSL (Digital Subscriber Line)
: 집집마다 깔린 전화선을 사용하는 방식. 전화선의 voice신호와 컴퓨터의 data신호가 전화선을 통해 한꺼번에 전송되는데, 이들의 주파수를 분리함으로써 한꺼번에 보낸다. 이를 토대로 data신호는 ISP 라우터로, voice신호는 telephone network로 보낸다.
대부분 upstream(업로드 속도라고 생각하면 됨)보다 downstream(다운로드 속도라 생각하면 됨)이 더 빠르며 DSL도 마찬가지다. 이런 것을 비대칭적이다(asymmetric)고 함.
2) Cable network (HFC)
DSL이 전화선을 사용하던 것에 비해 cable network방식은 동축케이블을 사용하며 전화기 대신 TV가 연결된 것을 알 수 있고 cable medem을 쓴다. 한 가지 또 특징적인 것은 한 라인 상의(그림 에서 초록색 선) 여러 집의 케이블들이 동시에 연결되는 형태(즉 shared하다)라는 점. 참고로 DSL은 한 집과 다른 집에서 나오는 선들이 shared하지 않고 dedicated 즉 독립적이다. TV신호와 data신호의 구분, 그리고 다른 집에서의 신호와의 구분을 위해서 이 녀석도 서로 다른 주파수를 사용해 신호를 나르게 된다. 이 때 이렇게 전체 주파수 밴드를 세부적으로 쪼개서 각각의 주파수로 실어 나르는 것을 FDM( = frequency division multiplexing)이라 한다.
3) FTTH (fiber to the home)
사실 FTTx로 마지막 x에 뭐가 들어오든 된다는 건데 여기서는 home의 h가 들어간 거다. CO로부터 home까지 직접 광섬유 경로를 제공하는 방식. CO에서 하나의 광섬유(direct Fiber)를 제공한다.
5. core network 찍먹
앞서 말한 것처럼 서로 연결되어 있는 라우터들의 집합체를 말하는 개념으로, end system들 간의 데이터 교환을 돕는다. 즉 end system들을 연결해주는 중간역할을 하는 것이 core network.
어떤 end system으로부터 정보가 이동할 때 항상 라우터들 즉 같은 경로를 거치는가?라고 묻는다면 대답은 No다. routing altorithm에 따라 그 때 그 때 거치게 되는 경로가 다르다. 서울에서 부산으로 갈 때 항상 같은 길로만 가지 않고 도로 상황에 따라 다른 길로 가기도 하는 상황으로 이해할 수 있다.
아무튼 이 core network에서 데이터가 전송(forward로 표현하기도 함)되는 것인데 packet switching과 circuit switching으로 구분할 수 있다.
1) packet switching
store-and-forward 즉 '저장 후 전달 방식'을 쓴다. end system으로부터 A라우터를 거쳐 다른 end system으로 데이터를 보낼 때, 비트 단위로 쪼개서 보내게 된다. 이 때 A라우터에서 비트 단위로 받을 때마다 즉시 destination으로 보내는 게 아니라 다 받고 나면 error가 있나없아 확인하고 destination으로 보내게 된다. 10개로 쪼갰으면 10개 다 받은 다음에 보내겠다는 마인드..
참고로 이 과정에서 당연히 시간 즉 딜레이가 걸리게 된다. 그럼 얼마나 걸리느냐? L bit의 packet을 R이란 전송률(transmission rate)로 보낸다고 하면 L / R만큼의 시간이 걸린다. L비트 데이터를 R의 속도로 보내면 당연히 L / R의 시간이 걸린다..중딩 때 배운 거속시가 생각나는 부분.
근데 여기서 한 가지 생각해야 할 게 있다. 라우터의 버퍼에 데이터가 쌓이는 속도보다 라우터의 버퍼에 있는 데이터가 전송되는 속도가 더 빠르다면 뭐 좋겠지만..대부분 그렇지 않다. 대부분의 라우터는 전송가능한 속도보다 더 빠른 속도로 데이터들(즉 packet)들이 들어온다. queue형태로 쌓이게 되는데, 이 경우 당연히 queue가 넘치게 된다. 참고로 이 queue에서 packet들이 대기하는 상황을 queueing delay라고 한다. 암튼 이렇게 버퍼가 full이 되면..남는 것들은 버린다. 당황스럽다. 버린다고 한다. 난 최선을 다했지만 이 이상은 힘들다. 버린다 수고해라! 뭐 이런 게 다 있나 싶다. 근데 뭐 이게 현재 쓰이고 있는 정책?이라 한다..
2) circuit switching
end system간의 통신을 위해 경로상 필요한 자원인 link를 독점적으로 예약(reserve)하고 사용한다. 즉 언제든지 '정해진 경로'로 데이터를 보낼 수 있는 것이며 보장된 전송률로 데이터를 보내는 것이 가능한 방식. 아니 그럼 더 좋은 거 아닌가? 생각할 수 있지만 내 꺼라고 찜해놓은 거라 다른 애들은 이걸 못 쓴다..비효율적임.
뭐 이와 반대로 packet switching 방식은 전송률을 보장하진 않지만 on-demand방식으로 링크 사용하고 싶으면 요청하고 사용이 가능하다. 식당으로 치면 예약 그런 거 없이 먹고 싶을 때 올 수 있다는 말. 뭐 손님 많으면 웨이팅 하는거지..packet switching은 link를 공유하므로 효율적이지만 delay가 있을 수 있다는 말이 되겠다.
저번에 mock데이터들을 map메소드를 통해 가공한 애들을 들을 출력하는 것까지 완성했었다. 얘네들을 dessertList라는 배열에 담기로 했는데, 이 때 생각할 거리가 좀 있어서 다음과 같이 처리하기로 했다.
1. 일단 디저트들을 좀 섞는 게 어떤지?
: 그냥 이대로 쓰면 매번 내 사이트를 들어올 때마다 같은 구성으로 16강이 진행되는(예를 들면 처음엔 마카롱은 항상 다쿠아즈와 경쟁하는) 문제가 있다. 따라서 이를 섞어야 할 필요가 있었다. 0이상 1미만의 난수를 랜덤 생성하는 Math.random과 콜백함수의 리턴값이 양수/음수/0인지를 보고 정렬하는 sort()를 이용해서
그래서 DessertItem 컴포넌트에 선택하기 버튼을 추가해서 만들기로 하고, 이를 위해 App.js에서 selectIndex를 + 2만큼 증가해주는 메소드를 만든 후 이를 DessertItem의 prop으로 보내 onClick속성으로 활용키로 했다. 좋아좋아 아주 순조로워~~
정리하자면
mock데이터를 가공해 각각의 요소들을 JSX요소들로 만들고 이를 dessertList라는 배열에 담는 거였는데, 우선 JSX요소들로 만들어주기 전에 섞어줄거임(mock데이터 배열의 요소들을). 그리고 얘네들을 selectIndex라는 state를 활용해 2개씩 보여줄 예정이니까, mock데이터들을 섞은 후에 map메소드로 JSX요소들(정확히는 DessertItem 컴포넌트들)로 가공해줄 때 'selectIndex를 2씩 증가해주는 메소드를 onClick속성으로 갖는 버튼'을 갖게 한다!!
즉 웹사이트를 처음 열자마자 mock데이터에서 갖고 온 애들을 섞고, 버튼까지 딸린 컴포넌트들로 가공한 애들을 dessertList에 담아둬야 함. 근데 옛날에 useEffect를 사용하면 처음 렌더링될때만 어떤 동작을 할 수 있다고 어렴풋이 들었던 기억이 나서, 다음과 같이 useEffect를 이용하게 됐다.
console.log()는 그냥 테스트용으로 찍어보던 거라 무시바람..암튼 이렇게하면 아무 문제없을 줄 알았다.
그런데 오류가 났다.
React Hook useEffect has a missing dependency: 'selectNextDessert'. Either include it or remove the dependency array
selectNextDessert가 selectIndex라는 state값을 늘려주는 메소드였는데, 이거 땜에 문제가 생긴다고 한다. useEffect의 두 번째 파라미터인 빈 배열 안에 selectIndex를 넣어주면 오류가 안 나긴 하지만 그렇게되면 selectIndex가 늘어날 때마다 shuffle이 계속 실행되는 불상사가 발생..[]자체를 없애면 렌더링될때마다 useEffect의 콜백함수가 실행되고..
구글링도 열심히 해보고 했지만 도무지 모르겠다. okky커뮤니티에도 상세하게 질문을 남겼지만 답변이 달리지 않았다. 내가 완전 기본적인 내용을 놓치고 있는 듯한..
# 뭐 어떻게든 해결은 했다만..
결국 통 모르겠으니, 특단의 조치를 내렸다. 선택하기 버튼 자체를 분리해서 만들기로 한 것! 이렇게하니까 오류는 안 생기긴 한다..