Javascript 稍后动态插入:如何实现 运行?
Javascript dynamically inserted later on: how to make it run?
我的 React 应用程序中有脚本,稍后会动态插入。脚本未加载。
在我的数据库中有一个名为 content
的字段,其中包含包含 html 和 javascript 的数据。有很多条记录,每条记录可以在 content
字段中包含多个脚本。因此,在我的 React 应用程序中静态指定每个脚本 url 并不是一个真正的选项。例如,记录字段可能如下所示:
<p>Some text and html</p>
<div id="xxx_hype_container">
<script type="text/javascript" charset="utf-8" src="https://example.com/uploads/hype_generated_script.js?499892"></script>
</div>
<div style="display: none;" aria-hidden="true">
<div>Some text.</div>
Etc…
我使用 dangerouslySetInnerHTML
:
在我的 React 应用程序中调用此字段
render() {
return (
<div data-page="clarifies">
<div className="container">
<div dangerouslySetInnerHTML={{ __html: post.content }} />
... some other data
</div>
</div>
);
}
它正确地从数据库加载数据并显示该数据的 html。但是,Javascript 不会被执行。我认为该脚本不起作用,因为它是稍后动态插入的。 如何制作这些脚本work/run?
This post 建议动态插入脚本的解决方案,但我不认为我可以应用此解决方案,因为在我的情况下 script/code 是从数据库插入的(那么如何使用nodeScriptReplace
在代码上...?)。有什么建议可以让我的脚本正常工作吗?
更新 回应@lissettdm 他们的回答:
constructor(props) {
this.ref = React.createRef();
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.postData !== this.props.postData) {
this.setState({
loading: false,
post: this.props.postData.data,
//etc
});
setTimeout(() => parseElements());
console.log(this.props.postData.data.content);
// returns html string like: `<div id="hype_container" style="margin: auto; etc.`
const node = document.createRange().createContextualFragment(this.props.postData.data.content);
console.log(JSON.stringify(this.ref));
// returns {"current":null}
console.log(node);
// returns [object DocumentFragment]
this.ref.current.appendChild(node);
// produces error "Cannot read properties of null"
}
}
render() {
const { history } = this.props;
/etc.
return (
{loading ? (
some code
) : (
<div data-page="clarifies">
<div className="container">
<div ref={this.ref}></div>
... some other data
</div>
</div>
);
);
}
this.ref.current.appendChild(node);
行产生错误:
TypeError: Cannot read properties of null (reading 'appendChild')
在没有 React 推荐方法的情况下渲染原始 HTML 不是一个好习惯。 React 推荐方法 dangerouslySetInnerHTML 来渲染原始 HTML.
有多种方法可以做到这一点。您可以创建一个函数,调用该函数可以动态创建 <script>
标签并将其注入到 React 应用程序的 <body>
中。
const addScript = () => {
const script = document.createElement("script");
script.src = '<url-of-the-script>';
script.async = true;
script.onload = function() {
// Do something
};
document.head.appendChild(script);
}
您可以在使用 useEffect
挂钩加载所需组件时调用此 addScript
函数。
useEffect(() => {
addScript();
return () => {
// remove the script on component unmount
};
}, []);
您需要先使用提取调用从数据库中提取动态数据,然后使用 useEffect,在提取数据后,在其中将其设置为 useState 挂钩变量,该变量将为此保存数据。
const [dbData,setDbData] = useState([]);
useEffect(()=>{
const dbReqFunc = async () => {
// req your dynamic data here and set the data fetched to dbData
// using setDbData(...)
};
dbReqFunc();
},[]); // Making the call on component loading (Modify accordingly based on
// needs)
在此之后,一旦设置了数据,您就可以使用另一个 useEffect 挂钩,它应该在前一个 useEffect 挂钩的下方。然后,您可以为获取的整个动态 URL 调用函数并将其附加到 HTML 文档(我已将所有脚本附加到 div 以便于清理)。
useEffect(()=>{
if(dbData.length>0){
const elem = document.createElement('div');
dbData.map(data=> {
// based on data returned modify accordingly
const script = document.createElement("script");
script.src = '...script-url...';
script.async = true;
script.onload = function() {
// Do something
};
//...other script fields...
elem.appendChild(script);
});
// attach elem to body
document.body.appendChild(elem);
return ()=>{
// clean-up function
document.body.removeChild(elem);
};
}
},[dbData]);
这应该加载脚本数据并且应该加载脚本。
注意:确保在第二次 useEffect 之前放置用于获取数据的动态数据库调用。所以,它按顺序运行。
如果您确定 HTML 字符串内容是安全的并且包含具有有效 HTML 的字符串,您可以使用 Range.createContextualFragment()(执行脚本)
function App() {
const ref = useRef();
useEffect(() => {
/* convert your HTML string into DocumentFragment*/
const node = document.createRange().createContextualFragment(HTML);
ref.current.appendChild(node);
}, []);
return (
<div>
<h1>HTML String</h1>
<div>
<div ref={ref}></div>
</div>
</div>
);
}
查看 script
内容如何在 JavaScript 控制台 working example
上执行
如果您使用 class component
在 class 构造函数中创建 ref
,然后更新节点内容,我在 componentDidMount
中这样做只是为了测试:
class App extends React.Component {
constructor(props) {
super(props);
this.ref = React.createRef();
}
componentDidMount() {
const node = document.createRange().createContextualFragment(HTML);
this.ref.current.appendChild(node);
}
render() {
return (
<div>
<h1>HTML String</h1>
<div>
<div ref={this.ref}></div>
</div>
</div>
);
}
}
看到这个working example
我的 React 应用程序中有脚本,稍后会动态插入。脚本未加载。
在我的数据库中有一个名为 content
的字段,其中包含包含 html 和 javascript 的数据。有很多条记录,每条记录可以在 content
字段中包含多个脚本。因此,在我的 React 应用程序中静态指定每个脚本 url 并不是一个真正的选项。例如,记录字段可能如下所示:
<p>Some text and html</p>
<div id="xxx_hype_container">
<script type="text/javascript" charset="utf-8" src="https://example.com/uploads/hype_generated_script.js?499892"></script>
</div>
<div style="display: none;" aria-hidden="true">
<div>Some text.</div>
Etc…
我使用 dangerouslySetInnerHTML
:
render() {
return (
<div data-page="clarifies">
<div className="container">
<div dangerouslySetInnerHTML={{ __html: post.content }} />
... some other data
</div>
</div>
);
}
它正确地从数据库加载数据并显示该数据的 html。但是,Javascript 不会被执行。我认为该脚本不起作用,因为它是稍后动态插入的。 如何制作这些脚本work/run?
This post 建议动态插入脚本的解决方案,但我不认为我可以应用此解决方案,因为在我的情况下 script/code 是从数据库插入的(那么如何使用nodeScriptReplace
在代码上...?)。有什么建议可以让我的脚本正常工作吗?
更新 回应@lissettdm 他们的回答:
constructor(props) {
this.ref = React.createRef();
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.postData !== this.props.postData) {
this.setState({
loading: false,
post: this.props.postData.data,
//etc
});
setTimeout(() => parseElements());
console.log(this.props.postData.data.content);
// returns html string like: `<div id="hype_container" style="margin: auto; etc.`
const node = document.createRange().createContextualFragment(this.props.postData.data.content);
console.log(JSON.stringify(this.ref));
// returns {"current":null}
console.log(node);
// returns [object DocumentFragment]
this.ref.current.appendChild(node);
// produces error "Cannot read properties of null"
}
}
render() {
const { history } = this.props;
/etc.
return (
{loading ? (
some code
) : (
<div data-page="clarifies">
<div className="container">
<div ref={this.ref}></div>
... some other data
</div>
</div>
);
);
}
this.ref.current.appendChild(node);
行产生错误:
TypeError: Cannot read properties of null (reading 'appendChild')
在没有 React 推荐方法的情况下渲染原始 HTML 不是一个好习惯。 React 推荐方法 dangerouslySetInnerHTML 来渲染原始 HTML.
有多种方法可以做到这一点。您可以创建一个函数,调用该函数可以动态创建 <script>
标签并将其注入到 React 应用程序的 <body>
中。
const addScript = () => {
const script = document.createElement("script");
script.src = '<url-of-the-script>';
script.async = true;
script.onload = function() {
// Do something
};
document.head.appendChild(script);
}
您可以在使用 useEffect
挂钩加载所需组件时调用此 addScript
函数。
useEffect(() => {
addScript();
return () => {
// remove the script on component unmount
};
}, []);
您需要先使用提取调用从数据库中提取动态数据,然后使用 useEffect,在提取数据后,在其中将其设置为 useState 挂钩变量,该变量将为此保存数据。
const [dbData,setDbData] = useState([]);
useEffect(()=>{
const dbReqFunc = async () => {
// req your dynamic data here and set the data fetched to dbData
// using setDbData(...)
};
dbReqFunc();
},[]); // Making the call on component loading (Modify accordingly based on
// needs)
在此之后,一旦设置了数据,您就可以使用另一个 useEffect 挂钩,它应该在前一个 useEffect 挂钩的下方。然后,您可以为获取的整个动态 URL 调用函数并将其附加到 HTML 文档(我已将所有脚本附加到 div 以便于清理)。
useEffect(()=>{
if(dbData.length>0){
const elem = document.createElement('div');
dbData.map(data=> {
// based on data returned modify accordingly
const script = document.createElement("script");
script.src = '...script-url...';
script.async = true;
script.onload = function() {
// Do something
};
//...other script fields...
elem.appendChild(script);
});
// attach elem to body
document.body.appendChild(elem);
return ()=>{
// clean-up function
document.body.removeChild(elem);
};
}
},[dbData]);
这应该加载脚本数据并且应该加载脚本。
注意:确保在第二次 useEffect 之前放置用于获取数据的动态数据库调用。所以,它按顺序运行。
如果您确定 HTML 字符串内容是安全的并且包含具有有效 HTML 的字符串,您可以使用 Range.createContextualFragment()(执行脚本)
function App() {
const ref = useRef();
useEffect(() => {
/* convert your HTML string into DocumentFragment*/
const node = document.createRange().createContextualFragment(HTML);
ref.current.appendChild(node);
}, []);
return (
<div>
<h1>HTML String</h1>
<div>
<div ref={ref}></div>
</div>
</div>
);
}
查看 script
内容如何在 JavaScript 控制台 working example
如果您使用 class component
在 class 构造函数中创建 ref
,然后更新节点内容,我在 componentDidMount
中这样做只是为了测试:
class App extends React.Component {
constructor(props) {
super(props);
this.ref = React.createRef();
}
componentDidMount() {
const node = document.createRange().createContextualFragment(HTML);
this.ref.current.appendChild(node);
}
render() {
return (
<div>
<h1>HTML String</h1>
<div>
<div ref={this.ref}></div>
</div>
</div>
);
}
}
看到这个working example