如何在原生JavaScript中调用已部署的React组件(类似Partial View)
解决方案
现有方案失效原因
你当前直接拉取React应用完整HTML插入容器的方案行不通,核心原因有3个:
- DOMParser解析出来的是包含
<html>、<head>、<body>的完整文档结构,嵌套在div中不符合HTML规范,浏览器无法正常解析 - 插入的HTML中的script标签不会被浏览器自动执行,React代码根本没有运行
- 拉取到的静态资源路径是相对路径,插入当前页后会以当前页域名为准,导致资源加载失败
你要的类似ASP.NET MVC分部视图的效果完全可以实现,具体方案如下:
实现步骤
1. 调整React项目配置
你使用的是create-react-app脚手架,我们需要修改配置让项目输出可被外部调用的组件包,而不是独立单页应用:
- 安装
@craco/craco用来修改CRA配置(不需要执行eject)
npm install @craco/craco --save-dev
- 项目根目录新建
craco.config.js,配置如下:
module.exports = { webpack: { configure: (webpackConfig) => { // 关闭代码拆分,打包为单文件方便引入 webpackConfig.optimization.splitChunks = { cacheGroups: { default: false, }, }; webpackConfig.optimization.runtimeChunk = false; // 配置输出为UMD格式,暴露全局对象 webpackConfig.output.library = 'KitchenQuiz'; webpackConfig.output.libraryTarget = 'umd'; webpackConfig.output.filename = 'static/js/kitchen-quiz.js'; webpackConfig.output.chunkFilename = 'static/js/[name].chunk.js'; // 关闭文件名哈希,方便固定引用地址 webpackConfig.plugins.forEach(plugin => { if (plugin.constructor.name === 'MiniCssExtractPlugin') { plugin.options.filename = 'static/css/kitchen-quiz.css'; } }); return webpackConfig; } } }
- 修改
package.json中的scripts命令,将react-scripts替换为craco:
"scripts": { "start": "craco start", "build": "craco build", "test": "craco test", "eject": "react-scripts eject" }
- 修改React入口文件
src/index.js,暴露渲染/卸载方法供外部调用:
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; // 不要直接渲染到固定dom,改为暴露方法 window.KitchenQuiz = { // 渲染到指定容器 render: (containerId, props = {}) => { const container = document.getElementById(containerId); if (container) { ReactDOM.render(<App {...props} />, container); } }, // 卸载组件 unmount: (containerId) => { const container = document.getElementById(containerId); if (container) { ReactDOM.unmountComponentAtNode(container); } } }
- 执行
npm run build打包,将打包后的产物部署到原路径http://phpstack-444580-2225309.cloudwaysapps.com/KitchenQuestionApp/即可。
2. 原生页面调用代码
替换你原来的XHR拉取逻辑,改为动态加载静态资源后调用渲染方法即可:
<div class="row clearfix"> <div class="col-md-12" data-noedit data-module="quiz-module" data-module-desc="quiz" > <div id='quizid'> </div> </div> </div> <script> var docReady = function (fn) { var stateCheck = setInterval(function () { if (document.readyState !== 'complete') return; clearInterval(stateCheck); try { fn() } catch (e) { } }, 1); }; docReady(function () { // 加载组件样式 const cssLink = document.createElement('link'); cssLink.rel = 'stylesheet'; cssLink.href = 'http://phpstack-444580-2225309.cloudwaysapps.com/KitchenQuestionApp/static/css/kitchen-quiz.css'; document.head.appendChild(cssLink); // 加载组件脚本 const script = document.createElement('script'); script.src = 'http://phpstack-444580-2225309.cloudwaysapps.com/KitchenQuestionApp/static/js/kitchen-quiz.js'; script.onload = function () { // 加载完成后渲染到quizid容器 window.KitchenQuiz.render('quizid'); } document.body.appendChild(script); }); </script>
注意事项
- 如果你的React组件中使用了
BrowserRouter,请替换为HashRouter,或者给Router设置basename="/KitchenQuestionApp",避免路由和当前页面路径冲突 - 如果原生页面和React组件部署域名不同,需要在React部署的服务器配置静态资源跨域访问权限,避免资源加载被浏览器拦截
内容的提问来源于stack exchange,提问作者maifs
相关产品推荐
相关产品推荐

