자바스크립트에서 타이머가 동작하는 방식을 정리해보았다. 자바스크립트의 싱글스레드 이해해도 많은 도움이 되는 것 같다.
타이머 함수
자바스크립트 타이머 관련 함수는 3 가지가 있다. 하지만 자바스크립트는 해당 delay의 정확한 시간을 보장하지 않는다.
또한 타이머는 외부의 웹 api로 브라우저에서 제공한다.
1 2 3 4 5 6
let id = setTimeout(fn, delay); // delay 만큼의 지연 후 fn을 호출한다. 나중에 취소할 수 있는 고유 ID를 반환한다. let id = setInteral(fn, delay); // setTimeout과 유사하지만 delay 마다 fn을 계속 호출한다. clearInterval(id); // id에 해당하는 타이머의 콜백을 발생하지 않도록 한다.
싱글스레드로 인한 실행예시
이는 자바스크립트가 싱글스레드로 동작하기 때문이다. 이는 한 번에 하나의 코드블럭만 실행할 수 있다는 것을 의미한다.
이는 실행 가능할 때(빈 자리가 있을 때)만 실행된다.
아래의 그림은 이를 설명하는 그림이다.
위에서부터 실행된다. 첫 번째 자바스크립트 블록은 18ms동안 실행되며 마우스 클릭 블록은 11ms동안 실행된다.
첫 번째 자바스크립트 블럭의 실행순서는 다음과 같다.
setTimeout 타이머 설정
마우스 클릭 이벤트 발생
setInterval 타이머 설정
setTimeout 실행
하지만 1,2는 비동기 이벤트이고 현재 자바스크립트 블럭의 실행이 끝나지 않았으므로 즉시 실행되지 않고 큐에 넣어서 대기한다.
자바스크립트 블록의 실행이 끝났을 경우 내부에선 대기중인 큐를 확인한다. 이 과정이 what's waiting이다. 확인에도 시간이 소요된다.
그 다음 큐에서 클릭 핸들러를 가져와 실행하여 Mouse Click Callback 블럭이 생긴다.
Mouse Click Callback 실행중에 setInterval이 발생하여 대기중인 큐에 넣는다.
Mouse Click Callback의 실행이 끝나게 되면, 내부에서 다시 큐를 확인하여 setTimeout을 실행한다.
setTimeout의 실행 중에 setInterval이 발생하지만 이미 큐에는 setInterval이 대기중이므로 이는 무시된다.
그 후 setTimeout의 실행이 끝난 뒤에 발생한 setInterval은 what's Waiting? 없이 즉시 실행된다.(실행중인 블록과 대기중인 큐에 아무 것도 없기 때문이다.)
정리
자바스크립트 엔진은 싱글스레드로 동작하여 비동기 이벤트를 큐에 넣는다.
타이머 함수는 웹 api로 브라우저에서 제공하여 외부에서 delay마다 큐에 넣는다.
setInterval은 마지막 콜백에 상관없이 delay마다 웹API에서 실행을 시도한다. 이미 큐에 있을 경우 무시된다.
setTimeout은 웹 API에서 delay마다 실행을 시도하지만 실행되고 있는 콜백이 있을 경우 해당 콜백의 실행이 끝날 때까지 큐에서 대기한 후 실행을 시도한다. 때문에 delay만큼의 시간을 보장할 수 없다.
next.js의 경우 node_modules내부의 패키지에서 전역으로 css를 import하면 에러를 발생시킨다. 구글링을 통해 해결하였다.
1
./node_modules/@fullcalendar/common/main.css Global CSS cannot be imported from within node_modules. Read more: https://err.sh/next.js/css-npm Location: node_modules/@fullcalendar/common/main.js
해결방법
1
npm i next-transpile-modules @babel/preset-react
next-transpile-modules 는 node_modules 내부의 패키지에 css/scss파일이 포함 될 수 있도록 transpile 하는 플러그인이다.
! [rejected] main -> main (non-fast-forward) error: failed to push some refs to 'https://github.com/Deep-brain-Academy-2 hint: Updates were rejected because the tip of your current branch is behin hint: its remote counterpart. Integrate the remote changes (e.g. hint: 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
const mongoose = require("mongoose"); //모듈을 불러옴 require("dotenv").config(); //.env를 사용하기 위해 불러옴
functiondbConnect() { if (process.env.NODE_ENV !== "production") { mongoose.set("debug", true); } // production이 아닐 경우 debug가 true가 되도록 하였다. mongoose.Promise = global.Promise; mongoose .connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, }) .then(() =>console.log("Successfully connected to mongodb!")) .catch((e) =>console.error(e)); } // 연결 후 콘솔에 출력하도록 하였다.
module.exports = { dbConnect }; // app.js에서 사용할 수 있도록 하였다.
exportfunctionrender( element: React$Element<any>, container: Container, callback: ?Function ) { if (__DEV__) { console.error( "ReactDOM.render is no longer supported in React 18. Use createRoot " + "instead. Until you switch to the new API, your app will behave as " + "if it's running React 17. Learn " + "more: https://reactjs.org/link/switch-to-createroot" ); }
if (!isValidContainerLegacy(container)) { thrownewError("Target container is not a DOM element."); }
if (__DEV__) { const isModernRoot = isContainerMarkedAsRoot(container) && container._reactRootContainer === undefined; if (isModernRoot) { console.error( "You are calling ReactDOM.render() on a container that was previously " + "passed to ReactDOM.createRoot(). This is not supported. " + "Did you mean to call root.render(element)?" ); } } return legacyRenderSubtreeIntoContainer( null, element, container, false, callback ); }
ReactDOM.render()는 처음 호출할 때 기존의 DOM 엘리먼트를 교체하며 이후의 호출은 React의 DOM diffing 알고리즘을 사용하여 더욱 효율적으로 업데이트한다.
ReactDOM.render()는 컨테이너 노드를 수정하지 않고 컨테이너의 하위 노드만 수정하여 자식 노드를 덮어쓸 필요 없이 기존의 DOM 노드에 컴포넌트를 추가할 수 있다.
ReactDOM.render()는 현재 ReactComponent 루트(root) 인스턴스에 대한 참조를 반환한다. 그러나 이 반환 값을 사용하는 것은 피해야한다. ReactComponent 인스턴스의 참조가 필요하다면 권장하는 해결책은 루트 엘리먼트에 콜백 ref를 붙여야한다.
ReactDOM.render()를 사용해 서버에서 렌더링한 컨테이너에 이벤트를 보충할 때는 hydrate()를 사용해야 한다.
exportfunctionhydrate( element: React$Node, container: Container, callback: ?Function ) { if (__DEV__) { console.error( "ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot " + "instead. Until you switch to the new API, your app will behave as " + "if it's running React 17. Learn " + "more: https://reactjs.org/link/switch-to-createroot" ); }
if (!isValidContainerLegacy(container)) { thrownewError("Target container is not a DOM element."); }
if (__DEV__) { const isModernRoot = isContainerMarkedAsRoot(container) && container._reactRootContainer === undefined; if (isModernRoot) { console.error( "You are calling ReactDOM.hydrate() on a container that was previously " + "passed to ReactDOM.createRoot(). This is not supported. " + "Did you mean to call hydrateRoot(container, element)?" ); } } // TODO: throw or warn if we couldn't hydrate? return legacyRenderSubtreeIntoContainer( null, element, container, true, callback ); }