简易React Playground实现 - 4.预览更新
1.背景
每次代码转译后iframe元素都需要重新渲染,这部分可以优化。此外,我们对于转译报错的情况,没有做容错处理,需要优化。
2.需求分析
我们可以给宿主(当前应用)和iframe通过postMessage去实现通信。每一次代码转译后,将代码通过消息发送给iframe,iframe有消息的接收事件,当接收到之后,eval执行代码。
3.代码实现
3.1iframe部分
iframe部分我们移除对script的渲染,通过iframe接收message事件,传输的代码eval执行。
const htmlStr = `
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Playground</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
</head>
<body>
<div id="root"></div>
<script>
// 监听来自父窗口的消息
window.addEventListener('message', (event) => {
try {
eval(event.data)
} catch(error) {
// 发送信息给父窗口
window.parent.postMessage('error', '*');
}
});
</script>
</body>
</html>
`;
2.宿主代码
React应用这边,当代码转译后,通过postMessage将转译后代码发送给iframe
这样做不必每次都转Blob再转url
import React, { useEffect, useRef } from "react";
import { transform } from "@babel/standalone";
const initValue = `
const App = () => {
return (
<div>Hello</div>
)
}
ReactDOM.render(<App />,document.getElementById("root"))
`;
const blob = new Blob([htmlStr], {
type: "text/html",
});
const url = URL.createObjectURL(blob);
export default function App() {
const textAreaRef = useRef<HTMLTextAreaElement>(null);
const iframeRef = useRef<HTMLIFrameElement>(null);
const handleClick = () => {
const value = textAreaRef.current?.value;
if (value) {
try {
const { code } = transform(value, {
presets: ["react"],
});
if (code) {
handleUpdatePreview(code)
}
} catch(error) {
handleUpdatePreview("")
}
}
};
// 代码更新,发送信息
const handleUpdatePreview = (code) => {
if (iframeRef.current) {
iframeRef.current.contentWindow?.postMessage(code, "*");
}
}
useEffect(() => {
window.addEventListener("message",(e)=>{
console.log("message>>",e)
})
return () => {
window.removeEventListener("message",(e)=>{
console.log("message>>",e)
})
}
},[])
return (
<>
<textarea ref={textAreaRef} defaultValue={initValue} />
<button onClick={handleClick}>transform</button>
<iframe src={url} ref={iframeRef}/>
</>
);
}