Shadow DOM

Shadow DOM

shadow DOM을 이용하면 앱의 일부를 캡슐화해서, 일반 DOM 트리를 대상으로 하는 전역 스타일과 분리할 수 있어요. Material UI 컴포넌트를 shadow DOM 안에서도 올바르게 렌더링하는 방법을 함께 살펴볼게요.

출처: 문서

본문

Material UI와 함께 shadow DOM 사용하기

1. 스타일 (Styles)

shadow DOM은 요소에 숨겨진 별도의 DOM을 연결하는 API예요. 페이지의 다른 코드와 각 컴포넌트의 구조·스타일·동작을 분리해서 충돌을 막아야 할 때 유용하죠.

자세한 내용은 MDN의 shadow DOM 문서를 참고해보세요.

아래 코드 스니펫은 shadow DOM 안에 스타일을 적용하는 방법을 보여줘요:

const container = document.querySelector('#root');
const shadowContainer = container.attachShadow({ mode: 'open' });
const shadowRootElement = document.createElement('div');
shadowContainer.appendChild(shadowRootElement);

const cache = createCache({
  key: 'css',
  prepend: true,
  container: shadowContainer,
});

ReactDOM.createRoot(shadowRootElement).render(
  <CacheProvider value={cache}>
    <App />
  </CacheProvider>,
);

2. 테마 (Theme)

Menu, Dialog, Popover 같은 Material UI 컴포넌트는 Portal 컴포넌트를 사용해서 현재 DOM 계층 밖의 컨테이너에 새로운 "하위 트리(subtree)"를 렌더링해요. 기본적으로 이 컨테이너는 document.body예요.

하지만 스타일이 Shadow DOM 안에서만 적용되기 때문에, Portal들도 Shadow DOM 컨테이너 안에서 렌더링되도록 해야 해요:

const theme = createTheme({
  components: {
    MuiPopover: {
      defaultProps: {
        container: shadowRootElement,
      },
    },
    MuiPopper: {
      defaultProps: {
        container: shadowRootElement,
      },
    },
    MuiModal: {
      defaultProps: {
        container: shadowRootElement,
      },
    },
  },
});

// ...

<ThemeProvider theme={theme}>
  <App />
</ThemeProvider>;

container prop은 Portal을 통해 직접 렌더링되는 기본 컴포넌트(Modal, Popover, Popper)에만 설정해주면 돼요. 상위 레벨 컴포넌트(Dialog, Menu, Tooltip 등)는 이 기본 컴포넌트들을 내부적으로 렌더링하므로 container prop을 자동으로 상속해요.

3. CSS 테마 변수 (선택 사항)

:::info TypeScript를 사용한다면 먼저 테마의 인터페이스를 확장해야 해요. :::

shadow DOM 안에서 CSS 테마 변수를 사용하려면, CSS 변수를 생성할 셀렉터를 설정해줘야 해요:

 const theme = createTheme({
+  cssVariables: {
+    rootSelector: ':host',
+    colorSchemeSelector: 'class',
+  },
   components: {
     // ...same as above steps
   }
 })

마지막으로, 1단계의 shadowRootElement를 값으로 사용해 colorSchemeNode prop을 설정해줘요:

 <ThemeProvider
   theme={theme}
+  colorSchemeNode={shadowRootElement}
 >

데모 (Demo)

아래 예시를 보면, shadow DOM 바깥의 컴포넌트는 전역 스타일에 영향을 받지만 shadow DOM 안의 컴포넌트는 그렇지 않은 것을 확인할 수 있어요:

export default function ShadowDOMDemoNoSnap() {
  return (
    <iframe
      title="codesandbox"
      src="https://codesandbox.io/embed/6vcl2f?view=Editor+%2B+Preview&module=%2Findex.tsx"
      style={{
        width: '100%',
        height: 350,
        border: 0,
      }}
      sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
    />
  );
}

더 알아보기 (Learn more)