我在组件库和演示站点上工作。
使用Emotion为组件设置样式,并使用Gatsby构建演示站点。
出于预览目的,我想在iframe中渲染组件。这样可以确保网站中的样式不会层叠到各个组件上,并使其更易于处理响应式布局等。
我还想保留iframe中的热重装。
Here,您可以看到一个示例,该示例显示了网站上的line-height
是如何级联到Button
组件上的,从而使其变得很高。
如何在iframe中渲染Button
及其所有样式?
答案 0 :(得分:2)
我认为这里的问题是将emotion
生成的样式应用于iframe内的按钮。
我发现Mitchell(情感核心团队)的这个出色示例完全满足您的需求:github
这是您的代码箱的一个分支,上面复制了代码,并带有一个基本的自制<Iframe>
元素:codesandbox
以下是相关代码:
// src/components/Iframe.js
import React, { useRef, useEffect, useState } from 'react'
import { createPortal } from 'react-dom'
import { CacheProvider } from '@emotion/core'
import createCache from '@emotion/cache'
import weakMemoize from '@emotion/weak-memoize'
// literally copied from Mitchell's codesandbox
// https://github.com/emotion-js/emotion/issues/760#issuecomment-404353706
let memoizedCreateCacheWithContainer = weakMemoize(container => {
let newCache = createCache({ container });
return newCache;
});
/* render Emotion style to iframe's head element */
function EmotionProvider({ children, $head }) {
return (
<CacheProvider value={memoizedCreateCacheWithContainer($head)}>
{children}
</CacheProvider>
)
}
/* hack-ish: force iframe to update */
function useForceUpdate(){
const [_, setValue] = useState()
return () => setValue(0)
}
/* rudimentary Iframe component with Portal */
export function Iframe({ children, ...props }) {
const iFrameRef = useRef(null)
const [$iFrameBody, setIframeBody] = useState(null)
const [$iFrameHead, setIframeHead] = useState(null)
const forceUpdate = useForceUpdate()
useEffect(function(){
if (!iFrameRef.current) return
const $iframe = iFrameRef.current
$iframe.addEventListener('load', onLoad)
function onLoad() {
// TODO can probably attach these to ref itself?
setIframeBody($iframe.contentDocument.body)
setIframeHead($iframe.contentDocument.head)
// force update, otherwise portal children won't show up
forceUpdate()
}
return function() {
// eslint-disable-next-line no-restricted-globals
$iframe.removeEventListener('load', onload)
}
})
return (<iframe {...props} title="s" ref={iFrameRef}>
{$iFrameBody && $iFrameHead && createPortal((
<EmotionProvider $head={$iFrameHead}>{children}</EmotionProvider>
), $iFrameBody)}
</iframe>)
}
如果您希望在gatsby build
期间预渲染iFrame,则需要做更多的工作。
对于styled-components
用户,我发现Stephen Haney的这段代码比emotion
更优雅:
[...]
styled-components
包含一个StyleSheetManager组件,该组件 可以拿一个目标道具。目标需要一个DOM节点,它将 将其动态创建的样式表附加到该节点。
react-frame-component
使用React的Context API新版本来 公开FrameContextProvider
。它包括IFrame
文档和 上下文中的窗口。您可以按以下方式结合使用这两个API来使用
styled-components
在您的IFrame中:{ frameContext => ( <StyleSheetManager target={frameContext.document.head}> <React.Fragment> {/* your children here */} </React.Fragment> </StyleSheetManager> ) } </FrameContextConsumer> </Frame>
这与react v16.4.1,样式组件v3.3.3和react-frame-component v4.0.0完美配合。
答案 1 :(得分:0)
i forked您的沙盒以显示解决方案。
步骤:
react-frame-component
(https://github.com/ryanseddon/react-frame-component)。它将为我们呈现任何传递的内容的iframe。 style
个节点,因此我们将对其进行复制。在沙箱中,我编写了非常原始的代码来检查该想法,它是否有效,但是在生产中,我应该编写一些更高级的内容: <Frame>
<FrameContextConsumer>
{// Callback is invoked with iframe's window and document instances
({ document }) => {
if (isFirstRender) {
setTimeout(() => {
// find styles in main document
const styles = Array.from(
window.document.head.querySelectorAll("style[data-emotion]")
)
// and add it to the child
styles.forEach(s =>
document.head.appendChild(s.cloneNode(true))
)
isFirstRender = false
}, 100)
}
// Render Children
return <Button>Primary</Button>
}}
</FrameContextConsumer>
</Frame>
注意:我不熟悉emotion
,但是我认为它不会在生产中(通过webpack ofc)创建style
节点,但是会创建一个文件,例如styles.css
。
然后,您应该将链接添加到子文档:
if (isFirstRender) {
setTimeout(() => {
const link = document.createElement("link");
link.href = "styles.scss";
link.rel = "stylesheet";
document.head.appendChild(link);
isFirstRender = false
}, 100)
}