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"
/>
);
}