현재 회사에서 chrome extension을 React를 사용해 개발하고 있다.
extension에서는 manifest.json의 content_scripts를 통해, 실행 시 자동으로 js 파일을 동작하게 할 수 있다.
→ 실행할 content-sciprts 파일에서 react를 사용해 페이지를 그려주면 되는 것.
// content-script.ts
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.createElement("div");
document.body.appendChild(rootElement);
const root = createRoot(rootElement);
root.render(<App />);
해당 js파일을 실행해 사용자의 웹페이지에 우리의 애플리케이션(App컴포넌트)이 보여야 하기 때문에
element를 생성해 body에 appendChild한 후,
react-dom의 createRoot api를 사용해 Root를 생성한 뒤
App이라는 JSX를 render했다.
그런데, 오늘 다른 팀원이 이런 코드를 작성했다.
// content-script.ts
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.createElement("div");
document.body.appendChild(rootElement);
const root = createRoot(rootElement);
root.render(<App />); // 최초 Root.render 발생
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "open") {
root.render(<App />); // 두번째 Root.render 발생
} else {
root.render(<></>);
}
});
chrome의 onMessage api를 통해 sendMessage를 수신하여
open 이벤트 발생 시 애플리케이션을 화면에 그리고, 그렇지 않은 경우 삭제하는 로직.
물론 이후 수정되었지만, 처음 위 수정사항을 보았을 때 이런 의문이 들었다.
runtime에서 open 이벤트를 수신할 경우 root.render는 content-script가 실행될 때 한 번, 이벤트 수신 시 한 번.
이렇게 총 두 번 실행되는데...
→ 화면에 우리가 작성한 App 컴포넌트가 두 개 생성되는 건 아닐까?
정답은... 아니다!
runtime에서 open 이벤트를 여러 번 발생시켜도, App 컴포넌트가 여러 개 생성되지는 않는다. 왜?
→ 늘 그렇듯 공식 문서를 보면 된다.
https://react.dev/reference/react-dom/client/createRoot#ive-created-a-root-but-nothing-is-displayed
createRoot(domNode, options?)
createRoot 실행 시 Root 타입의 객체를 반환받는다.
export interface Root {
render(children: React.ReactNode): void;
unmount(): void;
}
해당 타입 자체는 별다를 게 없지만...
Call createRoot to create a React root for displaying content inside a browser DOM element.
해당 객체를 통해 browser의 DOM 요소에 content를 띄워줄 수 있는 것!
createRoot api를 통해 전달받은 domNode를 위한 root를 생성하고,
React는 이렇게 생성된 root 내부의 DOM을 계속해서 관리(manage)하게 된다.
(일반적인 React application은 root가 하나지만
페이지 일부에 React를 위한 sprinkles를 사용하는 페이지의 경우, 여러 개의 root가 존재할 수도 있다)
root.render(reactNode)
이렇게 생성된 root의 render 메서드를 통해, 해당 dom 내부에 우리가 만든 React 컴포넌트를 display 할 수 있다.
root.render(<App />);
React는 root 내에 App 컴포넌트를 display 하고, 내부의 DOM을 지속적으로 관리한다.
그런데 이때 중요한 점은...
1. 처음 root.render를 호출할 경우, React는 입력받은 컴포넌트를 렌더링 하기 전 root 내 존재하는 HTML 요소들을 초기화한다.
2. 만약 동일한 root에 render를 여러 번 호출하는 경우, 마지막으로 전달된 JSX를 반영해 필요한 경우에만 DOM을 업데이트한다.
즉 render가 다시 호출될 경우 React는 그 직전 JSX의 DOM에서 재사용 가능한 부분과 새로 생성할 부분을 결정하며
변경사항이 존재할 경우에만 이를 반영하는 것!
// content-script.ts
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.createElement("div");
document.body.appendChild(rootElement);
const root = createRoot(rootElement);
root.render(<App />); // 최초 Root.render 발생
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "open") {
root.render(<App />);
// 두번째 Root.render 발생
// onMessage 직전 App의 dom tree에 변경사항이 발생했을 경우에만, 요소들이 새로 생성
} else {
root.render(<></>);
}
});
아하..! 이제 위 코드를 보고 들었던 의문, root.render()를 여러 번 호출하면 어떤 일이 생길까 를 대답할 수 있다.
→ 바로 직전 생성된 JSX의 DOM에 변경 사항이 발생했을 경우에는, 해당 요소들이 새로 생성된다.
→ 그렇지 않은 경우에는, 아무런 일도 일어나지 않는다.
그리고 이 JSX의 DOM에 변경 사항이 발생했다 를 판단하는 기준은
→ 이전에 배웠던 React의 비교(diffing) 알고리즘에 따라 결정될 테다!
참고
https://react.dev/reference/react-dom/client/createRoot#root-render
https://react.dev/learn/preserving-and-resetting-state
https://23life.tistory.com/184
'source-code > React' 카테고리의 다른 글
Drag&Drop 컴포넌트에서 클릭 이벤트와 드래그 이벤트 분리하기 (1) | 2024.08.01 |
---|---|
Cannot read properties of null (reading 'useContext') 에러 해결 (0) | 2024.01.16 |
useQuery data type 지정하기 (feat. 관심사 분리) (1) | 2023.08.16 |
react-query와 error 전파 handling (0) | 2023.08.16 |
Suspense를 통한 Render-as-you-fetch (0) | 2023.08.16 |