구조를 어떻게 ? 작은 앱을 만든다고 생각한다. 예를들어서 prodicts와 cart 앱을 만들어서 각각 다른 포트로 연다고 생각하면 된다 이를 위해서 각각의 폴터에 package.json, html, 을 만들고 webpack 을 이용해서 파일을 bundle하고 서버를 연다.
fucntion 키워드를 사용하는 typescirpt 컴포넌트는 children 을 가지지 않는다. (React.FC 를 명시하지 않을 경우) React.FC 로 선언한 컴포넌트는 children을 가진다.
children 의 타입은 React.ReactNode
styled Components를 사용할때나 여러가지 상황에서 굳이 React.FC를 선언하면 불편해진다. 명확하게 표시하고 싶을때 사용해도 되지만 굳이 사용하지 않아도 무방하다.
useReducer 를 사용할땐 Action 타입을 지정해 준다.
알고리즘
그리드 알고리즘 : 매순간 최적이라고 생각하는 것을 선택한다. 하지만 반드시 답을 보장하지는 않는다.
수업
npm i express-generator ( express 탬플릿을 만들어줌)
styled components
theme 를 관리하기 위해서 styled compoents 에 ThemeProvider, ThemContext 조합을 사용한다. 이렇게 theme 을 주입하면 모든 컴포넌트에서 props로 theme 변수에 접근할 수 있다.
useContext 를 사용해 인수로 ThemeContext 를 넘겨주면 컴포넌트 내에서 변수로 사용할 수 도 있다. 여기서 테마를 바꾸는 setTheme 같은 작업을 수행할 수 있다.
테마 같은 경우는 theme 폴더에 파일별로 관리하되 id 를 주어서 어떤 테마인지 식별할 수 있도록 하면 현제 내 테마 상태가 어떤건지 id로 쉽게 판별할 수 있다.
backend express
app.use(express.urlencoded({extended:false})) ⇒ html form → body 로 변환 해 주는 것
cors: 클라이언트와 서버가 다른 도메인에 있다면 원칙적으로는 어떤 데이터도 받을 수 없다. 서버에서 Access-Contorol-Allow-Origin 설정 되어 있어야 함. Access-Contorol-Allow-Methods 로 가능한 메서드 설정해 주어야 함. 이럴 때 사용하는 미들웨어 cors → app.use(cors( { origin: [‘http~’]})) , option으로 origin, optionSuccessStatus, credentials → Access-Contorols-Allow-Credintial 설정
그 밖에 자주 사용되는 미들웨어 : cookie-parser (req.cookie 로 읽을 수 있도록 함.), morgan (어떤 요청, 얼마나 걸렸는지 로그를 남기고 싶을때) app.use(morgan(‘combined’)) <compbine → 포맷임, 다른건 깃헙에서 찾아보길>, helmet(보안에 필요한 헤더를 추가해줌)
브라우저에 내장 객체이다. 자바스크립트로 request를 보낼 수 있게 해준다. 이름과 달리 XML 뿐만 아니라 다른 어떤 형식에 대해서도 가능하다.
요즘은 fetch라는 모던한 기능이 있어서 더이상 사용되지 않는다. 모던 웹에서 3가지 이류로 사용된다.
예전 코드에 이미 적용된 경우
플로필 하고 싶지 않고 옛날 브라우저에서도 가능하게 하려면 사용해야 함.
업로드 과정을 트래킹 한다든지 아직 fetch에서 지원하지 않는 기능을 사용하려면
the basic
XMLHttpRequest에는 비동기와 동기 동작이 있다. 주로 사용되는 동기 동작부터 알아본다.
XMLHttpRequest 생성
1
let xhr = new XMLHttpRequest();
보통 생성하고 나서 초기화 한다.
1
xhr.open(mehtod, URL, [async, user, password]);
method : HTTP method 보통 GET이나 POST이다.
URL : 리퀘스트 보내는 URL, 스트링이나 URL 오브젝트가 될 수 있다.
async : 만약 false로 설정할 경우 동기로 동작한다.
user, password : 기본 HTTP에서 인증을 위해 사용함. 필요할때 추가한다.
open이라는 메서드는 request를 구성한다고 생각하면 된다. 실제적으로 요청을 보내는 것은 send라는 메서드를 통해서 진행된다.
보내기
1
xhr.send([body]);
실제 적으로 연결을 open 하고 서버로 데이터를 request 한다. body 파라미터를 통해서 데이터를 보낼 수 있는데, GET메서드의 경우 body 파라미터가 필요 없을 수 있다. POST 메서드로 요청을 보낼때 body에 데이터를 담아서 보낸다.
응답에 대한 xhr 이벤트를 듣는다.
다음에 세가지 이벤트가 주로 폭넓게 사용된다.
load : request가 완전히 완료 되었을때(심지어 400 이나 500에 상태코드를 받았을 때도), 응답이 완전히 받아졌을때도
error : request가 만들어지지 않았을때, 잘못된 URL이거나 하는 등..
progress : 얼마나 다운로드 됬는지 리포트 한기 위해서 주기적으로 트리거 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
xhr.onload = function () { alert(`Loaded: ${xhr.status}${xhr.response}`); };
xhr.onerror = function () { // only triggers if the request couldn't be made at all alert(`Network Error`); };
xhr.onprogress = function (event) { // triggers periodically // event.loaded - how many bytes downloaded // event.lengthComputable = true if the server sent Content-Length header // event.total - total number of bytes (if lengthComputable) alert(`Received ${event.loaded} of ${event.total}`); };
// 1. Create a new XMLHttpRequest object let xhr = new XMLHttpRequest();
// 2. Configure it: GET-request for the URL /article/.../load xhr.open("GET", "/article/xmlhttprequest/example/load");
// 3. Send the request over the network xhr.send();
// 4. This will be called after the response is received xhr.onload = function () { if (xhr.status != 200) { // analyze HTTP status of the response alert(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found } else { // show the result alert(`Done, got ${xhr.response.length} bytes`); // response is the server } };
xhr.onprogress = function (event) { if (event.lengthComputable) { alert(`Received ${event.loaded} of ${event.total} bytes`); } else { alert(`Received ${event.loaded} bytes`); // no Content-Length } };
xhr.onerror = function () { alert("Request failed"); };
서버가 응답하면 다음에 xhr 속성에서 결과를 받아올 수 있다.
status : HTTP status code를 확인할 수 있다. HTTP가 아닌 오류의 경우에는 0이 될 수 있다.
statusText : HTTP status message를 확인할 수 있다. (OK, Not Found 같은거..)
response(오래된 스크립트는 responseText를 사용할 것이다.) : 서버에 응답 본문이다.
또한, timeout 프로퍼티를 설정해서 설정한 시간동안 응답을 받이 못하면 timeout 이벤트가 트리거 되게 할 수 도 있다.
1
xhr.timeout = 10000; // timeout in ms, 10 seconds
파라미터를 설정하고 적절히 인코딩 되는것을 보장하기 위해서 URL Object를 사용할 수 도 있다.
1 2 3 4 5
let url = new URL("https://google.com/search"); url.searchParams.set("q", "test me!");
// the parameter 'q' is encoded xhr.open("GET", url); // https://google.com/search?q=test+me%21
// the response is {"message": "Hello, world!"} xhr.onload = function () { let responseObj = xhr.response; alert(responseObj.message); // Hello, world! };
예전에는 xhr.responseText 나 xhr.responseXML 같은걸 사용했는데 요즘에는 xhr.responseeType을 설정하고 xhr.response를 받는 방법을 주로 사용한다.
Ready states
XMLHttpRequest는 진행됨에 따라서 상태가 바뀐다. 접근 가능한 상태는 다음과 같다.
1 2 3 4 5
UNSENT = 0; // initial state OPENED = 1; // open called HEADERS_RECEIVED = 2; // response headers received LOADING = 3; // response is loading (a data packed is received) DONE = 4; // request complete
순서는 다음과 같아 진다. 0 -> 1 -> 2 -> 3 -> … -> 3 -> 4, 3이 반복되는 이유는 네트워크에서 패킷을 받을때마다 3 상태가 되기 때문이다.
readystateChange이벤트를 통해 트래킹 할 수 있다.
1 2 3 4 5 6 7 8
xhr.onreadystatechange = function () { if (xhr.readyState == 3) { // loading } if (xhr.readyState == 4) { // request finished } };
이 이벤트는 예전에 사용하던 것으로 역사적인 이유로 남아있다. 요즘은 open/ error / progress 같은것을 사용하면 된다.
Aborting request
xhr.abort() 를 호출하면 즉시 request를 중단할 수 있다.
1
xhr.abort();
abort가 트리거 되면. xhr.status 는 0 이 된다.
Synchronous requests
open에 3번째 파라미터인 async에 값이 false 로 절정되면 요청이 비동기로 실행될 것이다. 이는 alert 이나 prompt와 같이 실행이 멈춘되 응답을 받으면 다시 실행된다는 것을 의미한다.
<script> // pre-fill FormData from the form let formData = new FormData(document.forms.person); // add one more field formData.append("middle", "Lee"); // send it out let xhr = new XMLHttpRequest(); xhr.open("POST", "/article/xmlhttprequest/post/user"); xhr.send(formData); xhr.onload = () => alert(xhr.response); </script>
form 은 기본적으로 multipart/form-data로 보내진다. 만약 json으로 보내고 싶으면 JSON.stringfy() 를 통해 스트링으로 변환 후 보내면 된다. 이때, 헤더에 Content-Type: applicatioin/json을 설정해 두면 서버 측에 프레임워크에서 자동으로 JSON으로 디코딩 할수 있는 정보를 줄수 있다.
1 2 3 4 5 6 7 8 9 10 11
let xhr = new XMLHttpRequest();
let json = JSON.stringify({ name: "John", surname: "Smith", });
<script> functionupload(file) { let xhr = new XMLHttpRequest(); // track upload progress xhr.upload.onprogress = function (event) { console.log(`Uploaded ${event.loaded} of ${event.total}`); }; // track completion: both successful or not xhr.onloadend = function () { if (xhr.status == 200) { console.log("success"); } else { console.log("error " + this.status); } }; xhr.open("POST", "/article/xmlhttprequest/post/upload"); xhr.send(file); } </script>
Cross-origin requests
기본적으로 fetch의 정책와 같이 Cross-origin request 를 보낼 수 있다. fetch 와 바찬가지로 cookie와 HTTP-authorization을 다른 origin으로 보낼 수 없는데 이를 이런 설정을 사용하기 위해서는 xhr.withCredentials이 true가 되어야 한다.
1 2 3 4 5
let xhr = new XMLHttpRequest(); xhr.withCredentials = true;
asyncfunctionsubscribe() { let response = await fetch("/subscribe");
if (response.status == 502) { // Status 502 is a connection timeout error, // may happen when the connection was pending for too long, // and the remote server or a proxy closed it // let's reconnect await subscribe(); } elseif (response.status != 200) { // An error - let's show it showMessage(response.statusText); // Reconnect in one second awaitnewPromise((resolve) =>setTimeout(resolve, 1000)); await subscribe(); } else { // Get and show the message let message = await response.text(); showMessage(message); // Call subscribe() again to get the next message await subscribe(); } }
subscribe();
요즘은 web push 와 같은 기능을 사용할 수 있기 때문에, 실시간이 중요한 서비스에서는 web push 기능을 사용하는것이 바람직하다.
웹 소켓을 사용하면 HTTP의 새로운 요청이나 중단 없이 데이터를 주고 받을 수 있다. 커넥션을 종료 시키지 않은 체 패킷 형태로 이루어지며 양방향 통신이 가능하다. 실시간으로 데이터를 교환하는 시스템이나 주식 트레이딩 시스템에 적합하다.
간단한 예시
1
let socket = new WebSocket("ws://javascript.info");
ws말고 wss:// 라는 프로토콜도 사용하는데 이는 HTTP와 HTTPS의 관계와 유사하다.
항상 wss://를 사용해라. ws를 사용하면 데이터가 암호화 되지 않고 전달되는데 오래된 프락시 서버에 경우는 웹솟켓을 알지 못해서 이상한 헤더를 붙혀서 전달하는 일이 발생한다. wss를 사용하면 TLS라는 보안 계층을 통해서 데이터가 암호화 되어서 프락시에서 열어 볼 수 없고, 복호화는 받는 쪽에서 연결 하기 때문에 안전하게 연결할 수 있다.
다음 이벤트를 사용할 수 있다.
open - 커넥션이 제대로 만들어졌을때 발생함.
message - 데이터를 수신하였을때 발생함.
error - 에러가 생겼을때 발생함.
close - 커넥션이 종료되었을때 발생함.
커넥션이 만들어진 상태에서 무엇을 보내고 싶다면 socket.send(data)를 사용하면 된다.
느린 데이터 환경에서 데이터를 전송하고 있다고 생각해 보자. 사용자는 계속해서 send 하겠지만 데이터는 버퍼링 되어 메모리에 저장되고 충분히 전달하기에 빠른 환경에 있을때 보내지게 될 것이다.
socket.bufferedAmount 프로퍼티는 보내고 있는 시점에서 남아있는 바이트나 버퍼를 저장하고 있다. 이 정보를 활용해서 버퍼에 데이터가 없을때 send 하는 로직을 구성할 수 있다.
1 2 3 4 5 6 7
// every 100ms examine the socket and send more data // only if all the existing data was sent out setInterval(() => { if (socket.bufferedAmount == 0) { socket.send(moreData()); } }, 100);
// the other party socket.onclose = (event) => { // event.code === 1000 // event.reason === "Work complete" // event.wasClean === true (clean close) };
많이 사용하는 코드는 다음과 같다.
1000 : default, normal closure
1006 : 브라우저 구현에 의해 연결이 비정상적으로 (로컬로) 닫혔음을 의미하는 특수 코드
다른 코드는 다음과 같다.
1001 : 서버가 꺼지거나 브라우저에서 페이지를 떠났다.
1009 : 메세지가 처리하기에 너무 크다.
1011 : 서버에 알수 없는 에러
WebSocket Code 는 HTTP 코드와 비슷한듯 다르다. 특별히 1000 미만의 코드는 미리 예약되어 있으며 설정하려고 하면 에러를 발생시킨다.
1 2 3 4 5 6
// in case connection is broken socket.onclose = (event) => { // event.code === 1006 // event.reason === "" // event.wasClean === false (no closing frame) };
Connection state
socket.readyState 프로퍼티를 사용하여서 연결 상태를 얻을 수 있다.
0 : “CONNECTiNG” : 연결 중 아직 연결된 것은 아님
1 : “OPEN”: communicating
2 : “CLOSING” : the connection is closing
3 : “CLOSED” : the connection is closed.
Chat example
웹소켓을 사용하는 체팅 예제를 살펴보자. 클라이언트에 집중해서 볼 것이지만, 서버도 간단하게 구현 가능하다.
메세지를 담을 form 이 필요하고, 메세지를 표시할 div 가 필요하다.
1 2 3 4 5 6 7 8
<!-- message form --> <formname="publish"> <inputtype="text"name="message" /> <inputtype="submit"value="Send" /> </form>
<!-- div with messages --> <divid="messages"></div>
자바스크립트를 통해서 해야 할것 :
연결
socket.send(message)
수신 메세지 div에 표시
구현 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
let socket = new WebSocket("wss://javascript.info/article/websocket/chat/ws") // send message from the form document.forms.publish.onsubmit = function() => { let outgoingMessage = this.message.value; socket.send(outgoingMessage); returnfalse; }
// message received - show the message in div socket.onmessage = function(event) => { let message = event.data; let messageElem = document.createElement('div'); messageElem.textContent = message; docuemnt.getElementById('messages').prepend(messageElem); }