(그래도 앞으로도 바보는 아닐 것이다. 흥.)
오늘 면접을 보면서 생각했던 것은... 아마추어 개발자와 프로 개발자의 차이를 넘기 위해서는 이런 것들이 필수적이구나 하는 깨달음이었던 것 같다. 개발자를 쉽게 생각했던 적은 한 번도 없었지만, 내가 아직 자갈 미세먼지에 불과한 수준이라는 것도 잘 알았지만, 역시 비전공자인 내가 기술적인 방면으로 전문가가 되고 싶을 때 기술 뿐만 아니라 그 기술에 대한 배경 지식들도 분명히 발목을 잡겠구나. 그래서 개발 덕후가 되라고 했구나... 그런 생각들. 아직 모르는 게 너무 많다! 나는 아직은 바보야!
내가 사용한 기술이라고 이력서에 적어뒀으면서도 잘 모르는 부분이 존재하는 것, 블로그에 적었던 내용이지만 기억하지 못해서 대답하지 못하는 점, 분명 공부했던 내용이지만 기억나지 않아서 머리가 새하얗게 된 점... 오늘의 나...조금 멋지지 못했지만 그래도 마음을 다잡고, 내가 잘 대답하지 못했던 것들을 다시 정리해보려고 한다.
둘은 서로 다른 방식으로 API를 디자인하고, 데이터를 요청하는 방법이다.
REST = Representational State Transfer : 웹에서 가장 널리 사용되는 아키텍처
REST는 클라이언트가 필요한 데이터를 요청할 때, 서버가 정해둔 URI(Uniform Resource Identifier)에 HTTP 매서드를 사용해 요청.
GraphQL은 단일 엔드포인트를 사용해 클라이언트가 필요한 데이터를 요청하고, 서버는 해당 데이터만 응답.
REST API의 한계점은 필요 없는 데이터까지 제공하며, endpoint가 필요한 정보를 충분히 제공하지 못하고, 클라이언트의 구조가 변경될 때 엔드포인트가 변경되거나 데이터 수정이 필요하다는 점.
GraphQL은 필요한 데이터만 요청할 수 있음. REST에서 사용되는 URI의 많은 엔드포인트들을 탐색하는 대신 데이터 요청에 대한 명확한 스키마를 작성해 데이터 요청에 대한 지침을 제공할 수 있음. (REST API와 달리 요청과 응답의 형태가 자유롭기 때문에 더 복잡하고 유연한 데이터 요청 및 응답이 가능)
for
for문은 초깃값부터 시작해 증가, 감소하며 조건에 부합하는 동안 계속 순회하며, 중간에 break 문을 만나면 반복을 중단함.
forEach vs. map
forEach는 배열을 순회하면서 각 배열의 원소들에 콜백을 실행한다. 즉, 기존 배열을 변경한다.
map은 배열을 순회하면서 전달인자로 받은 콜백의 실행 결과를 모은 새로운 배열을 리턴한다.
둘의 가장 큰 차이는 리턴값인데, forEach는 문의 밖으로 리턴값으 받지 못한다.
map은 리턴값을 출력할 수 있다. 그리고 map에서 리턴값을 얻기 위해서는 리턴문을 잊지 않고 적어주는 것이 중요함.
find
배열을 순회하다 콜백으로 받은 조건에 맞는 하나의 요소만 반환.
조건이 맞는 여러 개의 조건이 있더라도 처음 발견한 값만 반환.
filter
배열을 순회하며 콜백으로 받은 함수를 적용해 true/false를 판단하고, true인 요소들만 반환.
true인 요소가 없으면 빈 배열을 반환한다. 기존 배열을 변경하지 않고, 결과값에 따른 새 배열을 반환함.
reduce
reduce는 callback 함수와 initialValue를 받고 (초기값은 옵션이며, 초기값을 제공하지 않을 경우 배열의 첫번째 요소를 사용한다.)
콜백함수는 4개의 인자를 받을 수 있는데, 이전값(누산기), 현재값, index(현재인덱스), 배열이다.
reducer 함수의 반환값(콜백의 반환값)은 누산기에 할당되고, 누산기는 순회 중 유지된다.
reducer 함수가 매 요소마다 실행된 뒤 하나의 값을 출력한다. (누적값)
reduce로 map, filter, find 를 대체할 수 있다.
브라우저에 캐싱된 DNS 기록들을 통해 도메인과 대응되는 IP주소가 있는지 확인
캐시에 해당 도메인이 존재하지 않으면 ISP(Internet Service Provider)의 DNS 서버가 도메인을 호스팅하고 있는 서버의 IP 주소를 찾기 위해 DNS 쿼리를 날려줌
이렇게 날린 쿼리로 여러 다른 DNS 서버들을 검색해 해당 사이트 IP 주소를 찾음
브라우저가 올바른 IP 주소를 받으면 웹 서버에게 해당 웹사이트에 맞는 html문서 요청
해당 요청 메시지는 HTTP 요청이며 TCP/IP프로토콜 사용해 서버로 전송됨
WAS(Web Application Server)와 데이터베이스에서 웹페이지 작업 처리, 그 결과들을 웹 서버로 전송
웹 서버는 웹 브라우저에게 html 문서 결과를 전달
브라우저는 해당 html 및 css 파일을 전달 받아 DOM 트리와 CSSOM 트리를 생성
두 트리를 합쳐 렌더 트리를 생성
렌더 트리를 통해 요소들이 배치되는 위치, 크기 등을 계산하는 레이아웃 과정을 거치고
실제로 브라우저에 요소들을 나타내는 페인트 과정을 거쳐 렌더링이 마무리
컴퓨터 간 통신할 때 특정 규칙(프로토콜)에 맞춰 데이터를 전송하고 수신할 수 있음.
TCP/IP는 OSI 7계층 중에서 3(네트워크),4층(전송)을 다루는 프로토콜
Transmission Control Protocol : 전송 제어 프로토콜
Internet Protocol
패킷 통신 방식의 인터넷 프로토콜인 IP와 전송 조절 프로토콜인 TCP
IP는 패킷 전달 여부를 보증하지 않고, 패킷을 보낸 순서와 받는 순서가 다를 수 있음
TCP는 IP 위에서 동작하는 프로토콜로 데이터 전달 보증 & 순서대로 받게 해줌
HTTP, FTP, SMTP 등 TCP를 기반으로 한 많은 수의 애플리케이션 프로토콜들이 IP 위에서 동작하기 때문에 묶어서 TCP/IP로 부르기도 함.
TCP는 작동할 때 3-way handshake 방식으로 통신을 시도함
(데이터를 전송하기 전 통신이 가능한지, 한 번에 얼만큼을 받을 수 있는지 등을 확인 = 신뢰성 있는 통신을 위한 준비)
1 송신자가 수신자에게 'SYN'을 날려 통신이 가능한지 확인 (Port가 열려있어야 함)
2 수신자가 송신자로부터 'SYN'을 받고, 'SYN/ACK'을 송신자에게 날려 통신할 준비가 되어있음을 알림
3 송신자가 수신자의 'SYN/ACK'을 받고, 'ACK'를 날려 전송 시작을 알림
TCP의 특징
흐름 제어 : 송신자는 한 번에 얼마나 보낼 수 있고, 수신자는 자신이 데이터를 어디까지 받았는지 끊임없이 확인하고 TCP Header의 Window size를 이용해 한 번에 받고/보낼 수 있는 데이터 양을 정함 / 수신자는 지금까지 받은 데이터 양을 확인해 송신자에게 보내는데 이를acknowledge number라고 함 / 데이터 순서 번호 표기한 것은 sequence number라고 함
혼잡 제어 : 송신자는 연결 초기 데이터 송출량을 낮게 잡고 보내며 수신자의 수신을 확인한 다음 데이터 송출량을 조금씩 늘리게 되는데, 이렇게 되면 현재 네트워크에서 가장 적합한 데이터 송출량을 확인할 수 있음 => 이를 slow start라고 함
리액트의 Hook으로 둘 다 성능 최적화를 위해 사용할 수 있으며 메모이제이션과 관련이 있음
메모이제이션은 수행한 연산의 결과값을 메모리에 저장해 동일한 입력이 들어오면 저장되어 있는 것을 재사용하는 것
useMemo는 값의 재사용을, useCallback은 함수의 재사용을 위해 사용함
useMemo는 계산 비용이 높은 함수의 결과를 캐시하여 성능을 개선
첫번째 인자로 콜백 함수를 받고, 두번째 인자로 의존성 배열을 받아 의존성 배열 요소 값이 업데이트 될 때만 콜백 함수를 다시 호출해 메모이제이션 된 값을 업데이트하고 다시 메모이제이션 함
useCallback은 특정 컴포넌트가 리렌더링 되더라도 그 함수가 의존하고 있는 값이 바뀌지 않는다면 저장해둔 함수를 다시 꺼내 사용할 수 있게 해줌
첫번째 인자로 함수 자체가 들어가고, 두번째로 의존성 배열이 들어감
자식 컴포넌트의 props로 함수를 전달해줄 때 사용하면 좋다
useCallback 사용 예시
: App.js에서 List 컴포넌트로 getItems라는 함수를 props로 전달해주고 있음
//App.js
import { useCallback, useState} from "react";
import "./styles.css";
import List from "./List";
export default function App() {
const [input, setInput] = useState(1);
const [light, setLight] = useState(true);
const theme = {
backgroundColor: light ? "White" : "grey",
color: light ? "grey" : "white"
};
//input이 바뀌는 경우에 기억되어 있는 getItems 함수를 호출하도록 함.
//이 함수는 List 컴포넌트에 props로 전달된다.
const getItems = useCallback( () => {
return [input + 10, input + 100];
},[input])
const handleChange = (event) => {
if (Number(event.target.value)) {
setInput(Number(event.target.value));
}
};
return (
<>
<div style={theme} className="wall-paper">
<input
type="number"
className="input"
value={input}
onChange={handleChange}
/>
<button
className={(light ? "light" : "dark") + " button"}
onClick={() => setLight((prevLight) => !prevLight)}
>
{light ? "dark mode" : "light mode"}
</button>
<List getItems={getItems} />
</div>
</>
);
}
-------------------------------------------------------
//List.js
import { useState, useEffect} from "react";
//props로 getItems를 App.js에서 받아옴
function List({ getItems }) {
const [items, setItems] = useState([]);
useEffect(() => {
console.log("아이템을 가져옵니다.");
setItems(getItems());
}, [getItems]);
return (
<div>
{items.map((item) => (
<div key={item}>{item}</div>
))}
</div>
);
}
export default List;
+@ 내 코드에서 useCallback을 쓸 부분이 많아 보였는데 왜 안 썼냐고 여쭤보셨다.
어떤 부분들에서 사용할 수 있을지 한 번 찾아보자.
+@ 타입스크립트도 진작에 사용했으면 좋았을 것 같다는 이야기를 해주셨는데, 타입스크립트의 필요성에 대해서 다시 생각해보면서 내 코드 어느 부분에 타입스크립트를 적용할 수 있었을지 생각해보자.
+@ React.memo라는 것도 있는데, 이건 고차 컴포넌트로 컴포넌트를 메모이제이션 해줌
부모 컴포넌트로부터 넘겨받은 props가 같다면 메모이제이션해둔 렌더링 결과를 가져와 재사용
React.memo는 컴포넌트를 감싸주는 방식으로 사용
클로저는 함수가 속한 렉시컬 스코프를 기억하며, 함수가 렉시컬 스코프 밖에서 실행될 때도 이 스코프에 접근할 수 있게 해주는 기능
중첩된 함수에서는 외부에 있는 함수와 내부에 있는 함수의 렉시컬 환경이 저장되어 있기 때문에 내부 함수가 외부 함수에 접근할 수 있고 이를 클로저 함수라고 함
클로저는 현재 상태를 안전하게 변경, 유지할 때 쓰일 수 있음
상태가 의도치않게 변경되지 않도록 상태를 안전히 숨기고, 특정 함수에게만 상태 변경을 허용
(클로저 단점은 일반 함수라면 함수 실행 종료 후 가비지 컬렉션 대상이 되었을 객체가
클로저 패턴에서는 메모리상에 남아있게 됨
외부 함수의 스코프가 내부 함수에 의해 언제든 참조될 수 있기 때문
클로저를 남발할 경우 퍼포먼스 저하 발생 될 수 있음)
여기서부터는 아래 블로그를 많이 참고했음
https://yeoulcoding.tistory.com/149#recentEntries
https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e
https://goidle.github.io/react/in-depth-react-hooks_1/
이건 공식문서
https://ko.reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
리액트에서 사용하는 useState가 클로저 패턴을 이용한다
render 메소드를 통해 상태 변경을 감지해 필요한 부분만 업데이트 할 수 있는 클래스형 컴포넌트와 달리 함수형 컴포넌트는 렌더가 필요할 때마다 함수를 다시 호출한다. (함수형 컴포넌트 개념 자체가 props를 인자로 받고, Jsx문법에 맞는 리액트 컴포넌트를 리턴하는 것이라 렌더링 = 함수 호출이다) 그래서 함수형 컴포넌트에서 상태관리를 하기 위해서는 함수가 다시 호출되었을 때 이전의 상태를 기억하고 있어야 하고, 이를 리액트 훅이 클로저를 적용해 해결한다.
클로저 개념을 이용한 useState를 직접 구현해보면 아래와 같다
실제로 _state나 _setState를 사용하는 시점은 이 _useState의 호출이 끝난 뒤이지만
클로저로 인해 innerState 값을 기억하고 있기 때문에 이후에도 접근이 가능하다
const _useState = (initialValue) => {
let innerState = initialValuel
const _state = () => innerState;
const _setState = (val) => {
innerState = val;
{;
return [_state, _setState];
};
하지만 이렇게 구현한 뒤 _setState를 사용해도 제대로 상태가 변경되지 않는데, 이는 _state가 단순히 변수이기 때문에 _useState의 호출이 끝나면 그대로 리턴되어 변경할 수 없어지기 때문이다.
이런 문제를 해결하기 위해 리액트는 state 값을 useState 외부에 배열 형식으로 저장하는 방법을 사용했다고 한다.
리액트는 useState를 통해 생성한 상태를 접근하고 유지하기 위해 useState 메서드의 바깥에 state를 저장한다. 이 state들은 배열 형식으로 저장이 되고, key로 구분한다. 그러므로 useState 함수 내에서 선언되는 상태들은 이 배열에 순서대로 저장이 되어 있는 것이며(이 순서가 바뀌어 호출되게 되면 엉뚱한 상태를 참조할 수도 있다), 상태가 업데이트 되었을 때 이 상태들은 리액트 컴포넌트 바깥에 선언되어 있기 때문에 업데이트가 된 후에도 이 변수들에 접근을 할 수 있게 된다.
HTTP는 Hyper Text Transfer Protocol로 HTML과 같은 문서를 전송하기 위한 규약
보통 인터넷에 있는 데이터 요청 시 HTTP 프로토콜 사용해 URL/URI 통해 접근 가능
HTTP 요청 시 메소드를 지정해 리소스와 관련된 행동들(CRUD)을 지정할 수 있음
HTTP의 특징으로는 무상태성 & 비연결성이 있음
HTTPS는 HTTP Secure의 약자로 HTTP 프로토콜을 더 안전하게 사용할 수 있음을 의미함
HTTPS는 HTTP 요청과 응답으로 오가는 내용들을 암호화 한다 (암호화 방식은 대칭 키 방식 / 비대칭 키 방식으로 나뉨)
HTTPS는 HTTP와 통신하는 소켓 부분에서 SSL/TLS 프로토콜을 이용해 서버 인증과 데이터 암호화를 진행하는데, 이 SSL/TLS 프로토콜은 CA(Certificate Authority)라는 공인된 인증서 발급 기관들을 통해 발급받은 인증서를 사용하며, 대칭 키와 공개 키 암호화 방식 모두 사용함
즉, HTTP에 SSL/TLS 프로토콜을 더한 것을 HTTPS라고 함