리액트 어플리케이션은 props를 통해 컴포넌트 간에 데이터를 전달하는데, 여러군데서 필요한 데이터가 있을 때는 최상위 컴포넌트에서 state로 관리하고, 이를 컴포넌트에 전달한다. 기본적인 props 시스템에서는 parent component에서 child1,2,3이 있을 때 child3에게 props를 전달하려고 하면, child1,2는 해당 props를 사용하지 않더라도 props를 받아서 child3에게 전달해주어야 한다. 이렇게 규모가 작은 경우에는 여러 컴포넌트를 거쳐서 props를 전달하는 것이 그리 복잡하지 않으나 프로젝트 규모가 커지고, 여러 컴포넌트를 거쳐야 한다면 데이터의 전달이 복잡해진다. 또한 유지보수할 때 어려움을 겪을 수 있다. 이렇게 여러 컴포넌트를 거쳐서 props로 전달하지 않고, parent component에서 데이터를 받길 원하는 child3나 그 외의 자식에게 직접 전달하고 싶을 때 Conetext API를 사용한다. 결국 Conetext API의 목적은 데이터를 주고 받는 행위에 대한 것이다.
Context API 사용하기
createContext 함수를 이용하여 context를 만들고, 파라미터에는 Context의 기본 상태를 지정한다.
원래 cerateContext 생성시 만든 value는 black이었으나 Provider를 red로 변경하였다. 이전에 createContext 함수를 사용할 때 파라미터로 지정해 둔 createContext({ color: "black" });은 별도로 Provider를 사용하지 않았을 때 사용한다. 만약 Provider를 사용하면서 value는 지정하지 않았다면 오류가 발생한다.
Single Page Application. 한 개의 페이지로 이루어진 어플리케이션. 예전에는 웹 페이지가 여러 페이지로 구성되어 있었고, 사용자가 다른 페이지로 이동할 때 마다 새로운 html을 받아오고, 서버에서 리소스를 받아서 해석한 뒤 화면에 렌더링하는 방식이었음.
모던 웹 어플리케이션
사용자와의 인터랙션이 자주 발생하는 모던 웹에서는 화면 전환이 일어날 때마다 html을 계속 서버에 요청하면 성능이슈가 있을 수 있고, 굳이 바뀌지 않아도 되는 부분도 새로 불러와서 보여줘야 하는 불필요한 로딩이 있다.
트래픽 과부하
서버부하
리액트와 같은 라이브러리가 뷰 렌더링을 사용자의 브라우저가 담당하도록 하고, 어플리케이션을 브라우저에 불러와서 실행시킨 후 사용자와의 인터랙션이 발생하면 필요한 부분만 JS를 이용하여 업데이트 한다. SPA는 사용자에게 제공하는 페이지는 한 종류지만 해당 페이지에 로딩된 JS와 현재 사용자의 브라우저 주소 상태에 따라 다양한 브라우저를 보여줄 수 있다. 이렇게 각 주소에 따라 다른 화면을 보여주는 것을 라우팅이라 한다.
BrowserRouter
라우터 사용 전에 react-router-dom을 설치해준다. BrowserRouter은 react-router-dom에 내장되어 있는 컴포넌트. 사용하려면 index.js파일에서 BrowserRouter로 <App/>을 감싸준다.
BrowserRouter를 사용하면 페이지 새로고침 없이 주소를 변경하고, 이 주소에 관련된 정보를 props로 쉽게 조회하거나 사용할 수 있다.
Route
Route를 써서 특정 주소에 컴포넌트 연결. 어떤 규칙에 따라 어떤 컴포넌트를 보여줄지 지정하는 것.
1
<Route path='주소규칙' component={보여 줄 컴포넌트}/>
여러 개의 path에 같은 컴포넌트를 보여주고 싶을 때 기존에는 아래와 같이 두 번 작성했다.
이렇게 하는 대신 path props를 배열로 설정해 주면 여러 경로에서 같은 컴포넌트를 보여줄 수 있다. path를 쓸 때 맨 앞에 / 쓰는 것을 잊지 않도록 한다. /가 빠지면 route의 주소를 제대로 받아올 수 없다. /users/:id에 id로 올 값이 ‘a’, ‘b’가 있다면 어떤 링크를 클릭하느냐에 따라 /user/a, /user/b로 주소가 받아와져야 한다. 만약 /를 빠뜨리면 /user/user/a, /user/user/a/b 이런 식으로 기존 주소에 새로 클릭하는 링크주소가 계속 붙여져서 제대로 된 경로를 찾지 못한다.
클릭하면 다른 주소로 이동시켜 주는 컴포넌트. 일반 웹 어플리케이션에서는 a태그를 사용하여 페이지를 전환하는데 리액트 라우터에서는 a태그를 사용하지 않는다. a 태그를 사용하면 페이지를 전환하는 과정에서 새로 페이지를 불러오기 때문에 어플리케이션이 들고 있던 상태들을 모두 날려버린다. 따라서 컴포넌트들이 처음부터 다시 렌더링된다.
1
<Link to="주소">내용</Link>
URL 파라미터와 쿼리
페이지 주소 정의시 유동적인 값을 전달해야 할 때 사용.
파라미터: /profiles/velopert
쿼리: /about?details=true
일반적으로 파라미터는 특정 아이디나 이름을 사용하여 조회할 때 사용. 쿼리는 어떤 키워드를 검색하거나 페이지에 필요한 옵션을 전달할 때 사용.
URL 파라미터
라우트로 사용되는 컴포넌트에서 받아오는 match라는 객체 안의 params 값을 참조. 이 객체 안에는 현재 컴포넌트가 어떤 경로 규칙에 의해 보이는지에 대한 정보가 들어있다.
path 사용규칙: /profiles/:username :username과 같이 사용하면 match.params.username 값을 통해 현재 설정된 username값을 조회할 수 있다.
위와 같이 설정했다면 profile 컴포넌트에서는 match를 props로 받아와서 유동적인 username에 따라 해당값을 조회할 수 있게 한다.
useParams
URL 파라미터의 key/value object를 리턴하는 hook이다. 이를 이용해서 현재 의 match.params에 접근할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
functionBlogPost() { let { slug } = useParams(); return<div>Now showing post {slug}</div>; }
location 객체에 들어 있는 search 값에서 조회할 수 있고, Route로 사용된 컴포넌트에게 props로 전달된다. search 값은 문자열 형태이기 때문에 qs라이브러리를 설치해서 객체로 변환해서 읽어올 수 있도록 한다.
1 2 3 4 5 6 7
const About = ({ location }) => { const query = qs.parse(location.search, { ignoreQueryPrefix: true //문자열 맨 앞의 ?를 생략 }); const showDetail = query.detail === "true"; //쿼리 파싱 결과 값은 문자열
쿼리 문자열을 객체로 파싱하는 과정에서 결과 값은 언제나 문자열이다. 따라서 만약 숫자를 받아와야 한다면 parseInt를 통해 꼭 숫자로 변환시켜줘야 한다. 혹은 value =1 또는 value=true와 같은 형태를 사용한다면 ‘true’와 문자열이 일치하는지 비교한다.
UseLocation
현재의 URL을 나타내는 객체를 리턴하는 hook이다. url이 변경될 때마다 useState처럼 새로운 location을 반환하는 것이라고 생각할 수 있다.
첫 번째 라우트 컴포넌트처럼 component 대신 props를 넣어줄 수 있는데, 이는 컴포넌트 자체를 전달하는 것이 아닌, 보여주고 싶은 JSX를 넣어주는 것.
history
match, location처럼 라우트로 사용되는 컴포넌트에 전달되는 props 중 하나, 컴포넌트 내에 구현하는 메서드에서 라우터 API를 호출할 수 있다.
특정 버튼을 눌렀을 때 뒤로가기
로그인 후 화면 전환
다른 페이지로 이탈 방지
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
//useHistory hook 사용 import { useHistory } from"react-router-dom";
functionHomeButton() { let history = useHistory();
functionhandleClick() { history.push("/home"); }
return ( <button type="button" onClick={handleClick}> Go home </button> ); }
withRouter
HOC로 라우트로 사용된 컴포넌트가 아니어도 match, location, history 객체를 접근할 수 있게 한다.
Switch
여러 route를 감싸서 그중 일치하는 단 하나의 라우트만 렌더링시켜준다. 모든 규칙과 일치하지 않을 때 보여줄 Not Found도 구현할 수 있다. 아래와 같이구현하면 처음으로 매치되는 항목인 path="/"을 화면에 보여준다. 만약 이 경로에 exact라는 키워드가 붙어있지 않았다면 path="/about", "/info"가 렌더링 되었을 것이다. 만약 지정된 path가 아닌 임의의 경로, 예를 들어 /abc와 같이 입력했다면 Switch 내부에서 일치하는 라우트를 찾지 못하게 되었으므로 가장 마지막에 설정된 Not Found 관련 라우트가 렌더링된다.
Link와 비슷하다. 현재 경로와 Link에서 사용하는 경로가 일치할 때 특정 스타일이나 CSS 클래스를 적용할 수 있는 컴포넌트를 말한다. NavLink가 활성화되었을 때 스타일을 적용하려면 activeStyle을 사용하고, CSS클래스를 적용할 것이면 activeClassName값을 props로 넣어준다.
리덕스 생태계에서 가장 사용률이 높은 상태관리 라이브러리. 상태관리 로직들을 다른 파일로 분리해서 효율적으로 관리할 수 있고, 글로벌 상태도 관리할 수 있음.
Context API + useReducer를 사용해도 글로벌 상태를 관리할 수 있는데 Redux는 이보다 이전에 존재했던 라이브러리.
리덕스가 무조건 필요한 것은 아니다. 단순히 글로벌 상태를 관리하기 위한 것인데, 글로벌 상태가 별로 없다면 Context API를 사용하는 것으로도 충분히 가능함.
Context API를 쓰는 것과 Redux의 차이점
미들웨어
주요 기능: 비동기 작업을 더욱 체계적으로 관리 가능.
특정 조건에 따라 액션이 무시되게 만들 수 있디.
액션을 콘솔에 출력하거나 서버쪽에 로깅할 수 있게 한다.
액션이 디스패치 됐을 때 이를 수정해서 리듀서에 전달되도록 할 수 있다.
특정 액션이 발생했을 때 이에 기반하여 다른 액션이 발생되도록 할 수 있다.
특정 액션이 발생했을 때 특정 자바스크립트 함수를 실행시킬 수 있다.
유용한 함수와 Hooks를 지원받음.
connect: 전역적인 상태, action을 디스패치하는 함수들을 props로 받아와서 사용할 수 있다.
useSelector, useDispatch, useStore: Redux에서 관리하고 있는 상태를 쉽게 조회하고, action을 쉽게 디스패치할 수 있다.
만약 Context API를 사용한다면 위와 같은 hooks를 쓰는 것이 아니라 직접 만들어줘야 한다.
기본적인 최적화가 이미 되어있어서 필요한 상태가 바뀔 때만 리렌더링 된다.
하나의 커다란 상태 Context API를 사용해서 글로벌 상태를 관리하게 되면 기능별로 Context를 만들어서 사용해야 한다. 반면 Redux에서는 모든 글로벌 상태를 하나의 커다란 객체에 넣어서 사용하는 것이 필수이다. 따라서 매번 Context를 만들어서 사용하지 않아도 됨.
DevTools: 개발자 도구가 있어서 현재상태를 한 눈에 볼 수 있다.
Redux를 사용하고 있는 프로젝트가 이미 많다.
Redux는 언제 써야하는가?
프로젝트의 규모가 큰가? Y-Redux, N-Context API 비동기 작업을 자주 하는가? Y-Redux, N-Context API Redux가 편하게 느껴지는가? Y-Redux, N-Context API or MobX
Redux에서 사용되는 키워드
액션(Action) 상태에 어떤 변화가 필요할 때 액션을 발생시킨다. 하나의 객체이며 type이라는 값이 필수적으로 있어야함. 그외 필요한 값을 추가할 수도 있음. 상태 업데이트 시에 type을 보고 어떻게 업데이트할지 정하는 것.
1 2 3 4 5 6 7 8 9 10 11 12 13
{ type: "TOGGLE_VALUE"; }
// 필수적인 type 값 외에 필요한 값을 넣어줄 수 있다. // 새로운 할 일을 만드는 액션이며, 이 data를 추가하겠다는 의미. { type: "ADD_TODO", data: { id: 0, text: "리덕스 배우기" } }
액션 생성함수 (Action Creator) 액션을 만드는 함수. 단순히 파라미터를 받아와서 액션 객체 형태로 만든다. 필수로 사용해야 하는 것은 아니지만 이렇게 하면 액션 객체를 만드는 것이 훨씬 편하다. 즉 컴포넌트에서 쉽게 액션을 발생시키기 위함이므로 보통 함수 앞에 export 키워드를 붙여서 다른 파일에서 불러와서 사용한다.
위와 같이 단순히 숫자를 더하거나 빼는 경우가 아닌 객체 또는 배열이라면 스프레드 연산자 등으로 불변성을 반드시 지켜준다.
default: 부분에서 state를 그대로 반환한다. (useReducer에서는 default: 부분에 throw new Error(‘Unhandled Action’)처럼 에러를 발생시키도록 처리했다.)
API 호출이나 라우팅 전환같은 사이드이펙트를 일으키지 않도록 한다.
Date.now()나 Math.random() 같이 순수하지 않은 함수를 호출하지 않는다.
스토어 (Store) 리덕스에서는 한 애플리케이션당 하나의 스토어를 만든다. 스토어 안에는, 현재의 앱 상태와, 리듀서가 들어가있고, 추가적으로 몇가지 내장 함수들이 있다.
디스패치 (dispatch) 디스패치는 스토어의 내장함수 중 하나. 디스패치는 액션을 발생 시키는 것(액션을 스토어에게 전달)
1
dispatch({ type: "INCREASE" });
액션을 만들어서 dispatch의 파라미터로 넣어서 호출한다. 이렇게 호출하면 스토어가 리듀서 함수를 실행시켜서 해당 액션을 처리하는 로직이 있다면 이를 참고하여 새로운 상태를 만들어준다.
구독 (subscribe) 구독 또한 스토어의 내장함수 중 하나. subscribe 함수는, 함수 형태의 값을 파라미터로 받아온다. subscribe 함수에 특정 함수를 전달해주면, 액션이 디스패치 되었을 때 마다 전달해준 함수가 호출된다. 스토어의 상태가 업데이트 될 때마다 특정함수를 호출할 수 있다. 리액트에서 리덕스를 사용하게 될 때 보통 이 함수를 직접 사용하는 일은 별로 없다. 그 대신에 react-redux 라는 라이브러리에서 제공하는 connect 함수 또는 useSelector Hook 을 사용하여 리덕스 스토어의 상태에 구독한다.
리덕스의 3가지 규칙
하나의 애플리케이션 안에는 하나의 스토어. 여러 개의 스토어를 사용할 수도 있지만 권장되지 않는다.
상태는 읽기전용이므로 불변성을 지켜준다.
변화를 일으키는 함수인 리듀서는 순수한 함수여야 한다. 동일한 인풋에는 동일한 아웃풋이 있어야 한다. new Date()와 같은 함수는 순수하지 않은 함수이다.
리듀서 함수는 이전 상태와, 액션 객체를 파라미터로 받는다.
이전의 상태는 절대로 건드리지 않고, 변화를 일으킨 새로운 상태 객체를 만들어서 반환한다.
액션이 dispatch 되어서 리듀서에서 이를 처리하기 전에 사전에 지정된 작업들을 설정. 액션과 리듀서 사이의 중간자역할. 액션이 디스패치 된 다음, 리듀서에서 해당 액션을 받아와서 업데이트 하기 전, *추가적인 작업을 할 수 있다. 보통 비동기 작업을 처리할 때 사용한다.
추가적인 작업이란?
특정 조건에 따라 액션이 무시되게 만들 수 있습니다.
액션을 콘솔에 출력하거나, 서버쪽에 로깅을 할 수 있습니다.
액션이 디스패치 됐을 때 이를 수정해서 리듀서에게 전달되도록 할 수 있습니다.
특정 액션이 발생했을 때 이에 기반하여 다른 액션이 발생되도록 할 수 있습니다.
특정 액션이 발생했을 때 특정 자바스크립트 함수를 실행시킬 수 있습니다.
함수를 연달아서 두번 리턴하는 함수.
1 2 3
const middleware = (store) =>(next) =>(action) => { // 하고 싶은 작업... };
1 2 3 4 5 6 7 8 9 10 11 12
functionmiddleware(store) { //store: 리덕스 스토어의 인스턴스, dispatch, getState, subscribe 내장함수들이 들어있다. returnfunction (next) { // next: next(action)의 형태로 액션을 다음 미들웨어에게 전달. // 다음 미들웨어가 없다면 리듀서에게 전달한다. //next를 호출하지 않으면 액션이 무시처리되어 리듀서에게 전달안됨. returnfunction (action) { //action: 현재 처리하고 있는 액션 객체 // 하고 싶은 작업... }; }; }
리덕스 스토어에 여러 개의 미들웨어 등록 가능 새로운 액션이 디스패치되면 첫 번째 등록한 미들웨어가 호출되고, next(action)을 호출하면 다음 미들웨어로 액션이 넘어간다. 미들웨어에서 store.dispatch를 사용하면 다른 액션을 추가로 발생시킬 수 있다.
redux-thunk
redux-thunk를 사용하여 동기화된 액션을 네트워크 요청과 함께 사용할 수 있다. 이를 통해 action creator는 액션 객체 대신 함수를 반환할 수 있다. 이를 통해 action creator가 thunk가 되는 것이다. action creator가 함수를 반환하면 thunk middleware에 읳해 실행되는데, 이는 순수할 필요가 없다. 비동기 api 호출과 같은 사이드 이펙트가 허용되고, 동기적인 action을 보낸다.
객체 대신 함수를 생성하는 액션 생성함수를 작성. 미들웨어 안에서는 액션 값을 객체가 아닌 함수로 받아오게 하여 액션이 함수타입이면 실행시키게 만들 수 있다. 즉, 액션 객체가 아닌 함수를 디스패치 할 수 있다.
React의 데이터는 부모에서 자식으로 props를 통해 전달되는데, 자식 컴포넌트들이 늘어나면 전달해야하는 브릿지(중간에 의미없이 props만 전달하는 컴포넌트)가 늘어난다.
Context API를 사용하면 네스팅 컴포넌트 트리(부모->자식->자식->자식…의 구조)를 타고 props를 명시적으로 넘겨주지 않아도 컴포넌트 간에 값을 공유하도록 할 수 있다. 이렇게 하면 멀리 떨어진 컴포넌트에게 상태 또는 상태관리 함수를 보낼 수 있다. dispatch를 이용하여 전달한다.
context API: React 컴포넌트 트리 안에서 전역적(global)이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법. 선호 로케일, 테마, 데이터 캐시, 현재 로그인한 유저, 선호하는 언어 등을 관리할 때 사용.
언제 필요한가?
다양한 레벨에 네스틴된 많은 컴포넌트에게 데이터를 전달할 때. 단, context는 컴포넌트를 그룹화한다는 의미이기 때문에 재사용이 어려워지므로 꼭 필요할 때만 쓰도록 한다.
context 객체를 만든다. Context 객체를 구독하고 있는 컴포넌트를 렌더링할 때 React는 트리 상위에서 가장 가까이 있는 짝이 맞는 Provider로부터 현재값을 읽는다.
defaultValue: 트리 안에서 적절한 Provider를 찾지 못했을 때 쓰임. Provider를 통해 undefined를 값으로 보내도 구독 컴포넌트들이 defaultValue를 읽지 않음.
Context.Provider
1 2 3 4
//상위 컴포넌트에서의 정의 <MyContext.Provider value={/* 어떤 값 */}> {하위 컴포넌트} </MyContext.Provider>
Provider: Context 오브젝트에 포함된 React컴포넌트로 context를 구독하는 컴포넌트들에게 context의 변화를 알리는 역할을 한다.
Context 변경 사항을 자손들에게 제공한다. Provider 의 Value는 하위의 모든 Consumer 에서 사용할 수 있으며, Provider 하위의 모든 Consumer 는 Provider 의 value가 변경 될 때마다 리렌더링 된다. Privider가 받은 value를 하위 컴포넌트에 넘길 때, 값을 전달 받을 수 있는 컴포넌트의 수 제한은 없다.
Context.Consumer
1 2 3 4 5
//하위 컴포넌트에서 정의 <MyContext.Consumer>{(value) => //render something based on the context value } </MyContext.Consumer>
MyContext Provide의 Value의 변경 사항을 구독하며, Context 에서 가장 가까운 Provider 의 Value 를 참조한다.
함수 컴포넌트 안에서 context를 읽기 위해 쓸 수 있고, Context.Consumer의 자식은 함수여야 한다. 이 함수는 context의 현재값을 받고 React 노드를 반환한다.
사용자 인터페이스를 구축하기 위한 선언적이고 효율적이며 유연한 JavaScript 라이브러리
컴포넌트라고 불리는 작고 고립된 코드의 파편을 이용하여 복잡한 UI를 구성한다. 이 컴포넌트를 사용하여 React에게 화면에 표현하고 싶은 것이 무엇인지 알려줌.
render함수: 화면에서 보고자 하는 내용을 반환. 컴포넌트가 props라는 매개변수를 받아오면 render 함수를 통해 표시할 뷰 계층 구조를 반환한다. React 엘리먼트는 JavaScript 객체이며 변수에 저장하거나 프로그램 여기저기에 전달할 수 있다.
props
부모 컴포넌트에서 자식 컴포넌트로 props를 전달하면 React 앱에서 부모에서 자식으로 정보가 어떻게 흘러가는지 알려준다. 자식 컴포넌트에서 클릭 이벤트 발생에 대한 변화가 필요한데, state는 부모 컴포넌트에 있다면 자식은 부모의 state를 직접 변경할 수 없다. 컴포넌트는 자신이 정의한 state에만 접근할 수 있기 때문이다. 따라서 부모 컴포넌트가 자식 컴포넌트에게 props를 전달하고, 자식 컴포넌트에서는 함수를 호출한다. 아래의 경우 square를 클릭하면 부모 컴포넌트인 Board에서 넘겨받은 함수가 호출된다. 참고로 React에서 이벤트를 나타내는 prop에는 on[Event], 이벤트를 처리하는 함수에는 handle[Event]를 사용하는 것이 일반적이다.
이렇게 부모 컴포넌트가 자식 컴포넌트로 props를 전달하면 부모 컴포넌트에게 값을 받아 클릭 이벤트와 같은 변화가 생길 때 부모 컴포넌트에게 정보를 전달한다. 이런 경우 자식 컴포넌트를 제어되는 컴포넌트라고 한다. 부모 컴포넌트에 의해 제어되기 때문이다.
무엇인가를 기억하기 위해 사용. class 컴포넌트에서는 this.state를 설정하는 것으로 state를 가진다. 주의할 것은 하위 클래스의 생성자를 정의할 때 반드시 super를 호출해야한다. 아래와 같이 작성시 Square의 render 함수 내부에서 onClick 핸들러를 통해 this.setState를 호출하는 것으로 React에게 <button>을 클릭할 때 Square를 다시 렌더링해야 한다고 알릴 수 있다. 업데이트 이후에 Square의 this.state.value는 ‘X’가 되어 게임 판에서 X가 나타나는 것을 확인할 수 있다. 어떤 Square를 클릭하던 X가 나타날 것이다. 컴포넌트에서 setState를 호출하면 React는 자동으로 컴포넌트 내부의 자식 컴포넌트 역시 업데이트한다.
특정값을 유지해야 할 필요가 있을 때 부모 컴포넌트에 상태를 저장하고, 부모 컴포넌트가 자식 컴포넌트에게 props를 전달하는 것으로 무엇을 표시해야 할지 알려줄 수 있다. 여러개의 자식으로부터 데이터를 모으거나 두 개의 자식 컴포넌트들이 서로 통신하게 하려면 부모 컴포넌트에 공유 state를 정의해야 한다. 부모 컴포넌트는 props를 사용하여 자식 컴포넌트에 state를 다시 전달할 수 있다. 이것은 자식 컴포넌트들이 서로 또는 부모 컴포넌트와 동기화 하도록 만든다. 아래의 경우 부모 컴포넌트인 Board에 생성자를 추가하고, 값을 유지해야하는 squares의 null 배열을 초기 state로 설정했다.
// Objects by reference. var arr = Array(3).fill({}); // [{}, {}, {}] arr[0].hi = "hi"; // [{ hi: "hi" }, { hi: "hi" }, { hi: "hi" }]
key 선택
이런 경고가 있다면,
경고 배열이나 이터레이터의 자식들은 고유의 “key” prop을 가지고 있어야 합니다. “Game”의 render 함수를 확인해주세요.
리스트를 렌더링할 때 React는 렌더링하는 리스트 아이템에 대한 정보를 저장한다. 리스트를 업데이트 할 때 React는 무엇이 변했는 지 결정해야 한다. 리스트의 아이템들은 추가, 제거, 재배열, 업데이트 될 수 있다. React는 컴퓨터 프로그램이며 사람이 의도한 바가 무엇인지 알지 못한다. 그렇기 때문에 리스트 아이템에 key prop을 지정하여 각 아이템이 다른 아이템들과 다르다는 것을 알려주어야 한다.
목록을 다시 렌더링하면 React는 각 리스트 아이템의 키를 가져가며 이전 리스트 아이템에서 일치하는 키를 탐색한다. 현재 리스트에서 이전에 존재하지 않는 키를 가지고 있다면 React는 새로운 컴포넌트를 생성한다. 현재 리스트가 이전 리스트에 존재했던 키를 가지고 있지 않다면 React는 그 키를 가진 컴포넌트를 제거한다. 만약 두 키가 일치한다면 해당 구성요소는 이동한다. 키는 각 컴포넌트를 구별할 수 있도록 하여 React에게 다시 렌더링할 때 state를 유지할 수 있게 한다. 만약 컴포넌트의 키가 변한다면 컴포넌트는 제거되고 새로운 state와 함께 다시 생성된다.동적인 리스트를 만들 때마다 적절한 키를 할당하는 것이 강력하게 추천된다.
비전공자라면 CS 지식을 물어볼 가능성이 높고, 개발을 잘해도 CS 문외한이면 안뽑음. CS기본 지식이 있어야 커뮤니케이션에 문제가 없다고 생각.
TO BE
개발자는 프론트엔드를 주로 만들지만 필요에 따라 다양한 기술을 익히고 바로 적용할 수 있다. 기본과 실무능력을 고루 갖춘, 개발 실력이 높아야 한다.
필수 CS 기본 지식 항목
운영체제, 네트워크
자료구조/알고리즘 (코딩 테스트를 통해 별도 검증)
질문 예시
언제 멀티 프로세서를 사용하고, 언제 멑티쓰레드를 써야하는지 설명해주세요. 멀티 쓰레드를 사용하면 쓰레드간 자원 공유가 가능하기 때문에 쓰레드간 별도의 통신 오버헤드가 적습니다. 다만 공유된 자원간의 읽고 쓰기가 빈번할 경우, 추가적인 오버헤드가 드는 동기화 기법을 사용해야 합니다. 따러서 인스턴스 간 공유된 자원간의 읽고 쓰기가 빈번한 경우에는 멀티 프로세서 사용을 고려하고…
채팅 서버-클라이언트간에는 tcp와 ucp 중 어떤 프로토콜을 사용하는 것이 좋은지?
프로세스와 스레드 차이(운영체제)
프로세스는 운영체제로부터 자원을 할당받아 실행하고, 스레드는 프로세스로부터 자원을 할당받아 실행
Thread 는 기본적으로 프로그램이 작업을 완료하는데 사용할 수 있는 단일 프로세스로 각 스레드는 한 번에 하나의 작업만 수행할 수 있다. 자바스크립트는 싱글스레드이므로 컴퓨터가 여러 개의 CPU를 가지고 있어도 main thread라 불리는 단일 thread에서만 작업을 실행할 수 있다. 단 자바스크립트 엔진은 싱글 스레드로 동작, 브라우저는 멀티 스레드로 동작한다.
그러나 다음과 같이 Web workers를 이용하여 하나의 Task가 끝나기 전까지 다음 Task가 Blocking되는 문제를 해결하였다. 여러 개의 작업을 동시에 실행할 수 있도록 시간이 오래 걸리는 처리는 worker thread에 보내서 처리하는 것이다.
Main thread: Task A —> Task C
Worker thread: Expensive task B
동기식 처리
현재 실행 중인 task가 종료될 때까지 다음에 실행될 task가 대기하는 방식이다. task를 순서대로 하나씩 처리하여 실행 순서가 직관적이지만 앞선 task가 종료되기 전까지 이후 task들은 대기하여야 한다는(블로킹) 단점이 있다.
Block code: 웹 앱이 브라우저에서 특정 코드를 실행하느라 브라우저에게 제어권을 돌려주지 않으면 브라우저는 마치 정지된 것처럼 보이는 현상. 즉 사용자의 입력을 처리하느라 웹 앱이 프로세서에 대한 제어권을 브라우저에게 반환하지 않는 현상.
1 2 3 4 5 6 7 8 9 10
// 콜백함수는 인수로 함수를 넘겨줄 수 있다. functionfakeSetTimeout(callback, delay) { callback(); }
함수를 호출하면 실행 컨텍스트가 실행되고 이는 스택에 푸시되는데, 함수 코드의 실행이 종료되면 실행 컨텍스트 스택에서도 팝되어 제거된다. 함수의 실행순서는 이 스택으로 관리되는데 자바스크립트 엔진은 단 하나의 실행 컨텍스트 스택을 갖는다. 따라서 실행 중인 하나의 함수가 실행될 동안에 다른 함수는 실행할 수 없고, 현재 실행 중인 함수가 모두 종료되어야 다음 함수가 실행될 수 있다. 이처럼 한 번에 하나의 task만 실행할 수 있는 것을 싱글스레드방식이라고 한다. 위의 콜백 함수는 동기식 처리이고, 위에서부터 아래로 순차적으로 실행된다. 따라서 메인함수가 호출되어 실행되면 그 함수가 실행되고 종료되면, 그 다음 함수, 또 그 다음 함수 이런 방식으로 순서대로 console.log에 출력된다. 따라서 0,hello,1의 순서대로 console에 표시된다.
비동기식 처리
자바스크립트의 비동기 처리란 특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 것이다. 이러한 처리가 필요한 이유는 화면에서 서버로 데이터를 요청했을 때, 서버에 보낸 요청에 대한 응답이 언제 돌아올지 모르고, 그러한 상황에서 다른 코드를 실행하지 않은 상태에서 마냥 기다릴 수 없기 때문이다. 비동기식 처리는 블로킹이 발생하지 않는다는 장점이 있으나 task의 실행 순서는 보장되지 않는다는 단점이 있다.
타이머 함수인 setTimeout과 setInterval, HTTP 요청, 이벤트 핸들러는 비동기 처리 방식으로 동작한다.
비동기식 처리 모델은 싱글스레드인 자바스크립트의 단점을 보완해주는 것으로 현재 실행중인 함수가 아직 종료되지 않았더라도 그 다음 task를 곧바로 실행할 수 있다. 위의 setTimeout함수는 일정 시간이 경과한 후에 콜백 함수를 호출하는 것인데, setTimeout 함수를 호출한 후 아직 종료되지 않았어도 곧바로 console.log(1)을 실행할 수 있다. 따라서 위의 콜백함수는 0,1,hello의 순서대로 console에 출력된다. 참고로 setTimeout은 web API의 타이머에게 0초 뒤에 Queue에 실행할 콜백함수를 넣어달라고 요청하는 것이다.
비동기식 처리의 문제점
Web worker는 DOM에 접근할 수 없기 때문에 UI 업데이트에 관한 지시를 할 수 없다.
worker에서 실행되는 코드는 차단되지 않지만 동기적으로 실행된다. 순서대로 실행되는 task A, task B가 있다고 했을 때, A에서 작업된 결과를 B에서 return 받아서 써야하는 작업이 필요하다면 A에서의 처리 결과를 반환받기 전에 B가 실행되면 에러가 발생하는 문제점이 있다. 이러한 문제를 없애기 위해 특정 작업을 비동기로 처리하는 promise를 사용하는 것이다.
비동기 처리는 Event Loop, Task Quaue와 관련있다.
동시성 모델과 Event Loop, 자바스크립트 엔진과 브라우저 환경의 구조
자바스크립트는 코드 실행, 이벤트 수집과 처리, 큐에 놓인 하위 작업들을 담당하는 이벤트 루프에 기반한 동시성(concurrency) 모델을 가지고 있다.
Call Stack
LIFO: Last In First Out 자바스크립트는 단 하나의 콜 스텍을 사용한다. 실행 컨텍스트가 추가되고 제거되는 곳이 콜 스텍이다. 스텍에 실행 컨텍스트가 생성되면 현재 실행 중인 컨텍스트가 종료되어 제거되기 전까지 다른 task는 실행되지 못하고 대기하여야 한다.
Heap
객체가 저장되는 곳이다. 동적인 데이터를 저장하는 규모가 큰 구조화되지 않은 자료구조라고 볼 수 있다. Heap에 의해 점유된 메모리는 Javascript Code가 실행이 완료된 후에도 존재하며, 이후 더이상 해당 데이터가 필요하지 않게 되었을 때, JS Garbage Collector에 의해 제거된다.
Call Stack, Heap: 동기적 처리 Web API, Queue, Event Loop: 비동기적 처리(브라우저 또는 Node.js가 담당)
Web API
브라우저에서 제공하는 API. DOM API와 타이머 함수, HTTP 요청(Ajax)과 같은 비동기 처리를 포함. Call Stack에서 비동기 함수가 실행되면 Web API를 호출하고(호출만 가능), Queue에서 대기하다가 Call Stack이 비었을 때 push 되어 실행된다.
Queue
FIFO: First In First Out setTimeout이나 setInterval과 같은 비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역. 여기서 task가 대기하고 있다가 콜 스텍이 비워졌을 때 실행되며, 가장 처음에 들어온 것부터 실행된다.
Event Loop
Call Stack과 Queue를 반복해서 확인하는 감시자 역할. 감시하다가 Call Stack이 비어있고, Queue에 대기 중인 함수가 있다면 Queue에 있는 함수를 Call Stack로 이동시켜 실행한다.