我在一个组件库和一个演示站点上工作。
组件的样式使用 Emotion并且演示站点是用 Gatsby 构建的.
出于预览目的,我想在 iframe 中呈现组件。这将确保来自网站的样式不会级联到组件,更容易处理响应式布局等。
我还想在 iframe 中保留热重载。
Here , 你可以看到一个例子 line-height
从网站级联到Button
组件使其非常高。
我如何渲染 Button
它的所有样式都在 iframe 中?
最佳答案
我认为这里的问题是应用 emotion
生成的样式到放置在 iframe 内的按钮。
我找到了 Mitchell(情感核心团队)的这个很好的例子,它完全符合你的需要:github
这是您的代码和复制代码的 fork ,带有基本的自制<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
includes a StyleSheetManager component that can take a target prop. The target expects a DOM node, and it will attach its dynamically created stylesheets to that node.
react-frame-component
uses React’s new version of its Context API to expose aFrameContextProvider
. It includes theIFrame
document and window in the context.You can combine these two APIs as follows to use
styled-components
inside your IFrames:{ frameContext => ( <StyleSheetManager target={frameContext.document.head}> <React.Fragment> {/* your children here */} </React.Fragment> </StyleSheetManager> ) } </FrameContextConsumer> </Frame>
This works perfectly with react v16.4.1, styled-components v3.3.3, and react-frame-component v4.0.0.
关于reactjs - 如何在 Gatsby 站点的 iframe 中渲染带有 Emotion 样式的 React 组件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59098769/