建立无需build的react单页面SPA框架

news2024/11/16 15:56:11

vue、react这种前端渲染的框架,比较适合做SPA。如果用ejs做SPA(Single Page Application),js代码控制好全局变量冲突不算严重,但dom元素用jquery操作会遇到很多的名称上的冲突(tag、id、name)。

SPA要解决的问题:

(1)业务组件用什么文件格式?如果使用*.jsx文件,需要在部署前build转换。本来js的初心就是“即改即用”,我不太喜欢ts,jsx这些需要build的东西,前端加一个babel来转换。

(2)业务组件如何加载?业务组件不可能写的时候全部知道(根据用户权限决定),也不可能一次性全部加载(影响首屏效率),应该是需要的时候,才从服务器加载。加载的jsx文件经过babel转换成js后,用eval函数执行。


demo.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Acro Multi-Lang Demo</title>
    <script src="/js/jquery-1.11.1/jquery-1.11.1.min.js"></script>
    <script src="/src/acroMulti.Resources.js"></script>
    <!-- <script src="/src/acroMulti.HTML.TagMethod.js"></script>
    <script src="/src/acroMulti.HTML.TagMethod.Register.js"></script>
    <script src="/src/acroMulti.HTML.Replacer.js"></script> -->
    <script src="/src/acroMulti.DD.js"></script>
    <script src="/src/acroMulti.CSVText.js"></script>
    <script src="/src/acroMulti.DD.CSVText.js"></script>
    <!-- <script src="/src/acroMulti.Locale.js"></script> -->
    <script src="/src/acroMulti.Culture.js"></script>
    <script src="/src/acroMulti.Utils.js"></script>
    <script src="/dd/dd.unicode.lng.base64.js"></script>
    <script src="/src/acroMulti.Browser.Engine.js"></script>
    <script src="/src/acroMulti.Tool.Chinese.js"></script>
    <!-- <link rel="stylesheet" type="text/css" href="/jsx/src/css.main.css"/> -->
    <!-- <link rel="stylesheet" type="text/css" href='/js/rc-easyui-1.2.9/dist/themes/default/easyui.css'>
    <link rel="stylesheet" type="text/css" href='/js/rc-easyui-1.2.9/themes/icon.css'>
    <link rel="stylesheet" type="text/css" href='/js/rc-easyui-1.2.9/themes/react.css'> -->
    <script type="importmap">
      {
        "imports": {
          "react": "/js/react-18.1.0/react.development.js",
          "easyui":"/js/rc-easyui-1.2.9/dist/rc-easyui-min.js"
        }
      }
    </script>
    <style>
      @import '/js/rc-easyui-1.2.9/dist/themes/default/easyui.css';
      @import '/js/rc-easyui-1.2.9/dist/themes/icon.css';
      @import '/js/rc-easyui-1.2.9/dist/themes/react.css';
    </style>
  </head>
  <body>
    <div>
      <img src="/img/AcroMultiLanguage4.1.gif"/>
    </div>
    <div id="div_main"></div>
    <script src="/js/react-18.1.0/react.development.js"></script>
    <script src="/js/react-18.1.0/react-dom.development.js"></script>
    <script src="/js/babel-7.17.11/babel.min.js"></script>

    <script>
      let importMap=$('script[type="importmap"]').text();
      //console.log(importMap);
      importMap=JSON.parse(importMap).imports;
      function parseURI(url) {
        var m = String(url).replace(/^\s+|\s+$/g, '').match(/^([^:\/?#]+:)?(\/\/(?:[^:@]*(?::[^:@]*)?@)?(([^:\/?#]*)(?::(\d*))?))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?/);
        // authority = '//' + user + ':' + pass '@' + hostname + ':' port
        return (m ? {
          href     : m[0] || '',
          protocol : m[1] || '',
          authority: m[2] || '',
          host     : m[3] || '',
          hostname : m[4] || '',
          port     : m[5] || '',
          pathname : m[6] || '',
          search   : m[7] || '',
          hash     : m[8] || ''
        } : null);
      }
      
      function absolutizeURI(base, href) {// RFC 3986
        function removeDotSegments(input) {
          var output = [];
          input.replace(/^(\.\.?(\/|$))+/, '')
              .replace(/\/(\.(\/|$))+/g, '/')
              .replace(/\/\.\.$/, '/../')
              .replace(/\/?[^\/]*/g, function (p) {
            if (p === '/..') {
              output.pop();
            } else {
              output.push(p);
            }
          });
          return output.join('').replace(/^\//, input.charAt(0) === '/' ? '/' : '');
        }
      
        href = parseURI(href || '');
        base = parseURI(base || '');
      
        return !href || !base ? null : (href.protocol || base.protocol) +
              (href.protocol || href.authority ? href.authority : base.authority) +
              removeDotSegments(href.protocol || href.authority || href.pathname.charAt(0) === '/' ? href.pathname : (href.pathname ? ((base.authority && !base.pathname ? '/' : '') + base.pathname.slice(0, base.pathname.lastIndexOf('/') + 1) + href.pathname) : base.pathname)) +
              (href.protocol || href.authority || href.pathname ? href.search : (href.search || base.search)) +
              href.hash;
      }

      function invokeCode(file,rawCode){
        // console.log(file);
        // if (invokeCode.caller) console.log(invokeCode.caller.arguments);
        let code=rawCode;
        if (file.substr(file.length-4).toLowerCase()=='.jsx'){
          code = Babel.transform(code,{presets: ['es2015','react']}).code;
          //console.log(code); 
        }
        //用hook模式支持jsx文件中的exports
        window.exports = {};
        window.module={exports:{}};
        window.eval(code);
        //console.log(window.exports);
        //console.log(window.module);
        let obj;
        if (window.exports.default)
          obj=window.exports.default;
        else
          obj=window.module.exports;
        //let obj=g_eval(code);//全局作用域
        //let obj=eval.call(this,code);
        //let obj=g_eval('('+ code + ')');
        //let obj=window.Function('"use strict";return (' + code + ')')();
        // console.log('code3:',module);
        // console.log(obj);
        return obj;
      }
      //babel.min.js处理import指令需要require函数
      //js的import函数不能加载jsx文件
      window.require=function(file){
        //console.log('1.raw:',file);
        if (importMap[file]){
          file=importMap[file];
        }
        //处理相对路径
        let root;
        if (require.caller==invokeCode){
          root=require.caller.arguments[0];
        }
        else{
          root=window.location.pathname;
        }
        //console.log('2.root:',root);
        file=absolutizeURI(root,file);
        //console.log('3.absolute:',file);

        let xhr = new XMLHttpRequest();
        xhr.open("GET", file, false);
        xhr.send();
        if(xhr.status != 200) {
          throw new Error(file+",require error: http status " + xhr.status);
        }
        let code=xhr.responseText;
        //console.log(code);
        return invokeCode(file,code);
      }
      /*
      //require要求同步函数,fetch是异步函数无法使用
      window.require=async function(module){
        console.log(module);
        let res=await fetch(module);
        console.log(res);
        let code=await res.text();
        console.log(code);
        return invokeCode(module,code);
      }
      */
    </script>
    <script type="text/babel">
      import Com_Main from './com.main.jsx';
      let root_main,el_main,div_main;
      function render_main(){
        if (!root_main){
          div_main =$('#div_main')[0];
          root_main = ReactDOM.createRoot(div_main);
        }
        el_main=React.createElement(Com_Main);
        root_main.render(el_main);
      }

      acroMulti.engine.switchLanguage=function(){
        render_main();
        // acroMulti.engine.replaceElements($('title'));
      }
      acroMulti.engine.switchLanguage();
    </script>
  </body>
</html>

babel需要require函数,浏览器没有这个函数,必须是同步函数,浏览器原生fetch函数是异步的不可用。我们自己写一个require函数来加载jsx业务组件文件。用了函数的caller来处理相对路径问题。用了importmap来处理组件加载名称问题。

页面划分为上中下三层,中间划分为左右两部分,左边是功能树,右边是功能区。

com.main.jsx

import Com_Header from './com.header.jsx';
import Com_Left from './com.left.jsx';
import Com_Right from './com.right.jsx';
import Com_Language_Engine from './com.language.engine.jsx';
import {Resizable} from 'easyui';
let t=acroMulti.t;
class Com_Main extends React.Component {
  constructor(props){
    super(props);
    this.switchTab=this.switchTab.bind(this);
    this.ref_right = React.createRef(null);
  }
  switchTab(name,file){
    this.ref_right.current.switchTab(name,file);
  }
  render() {
    return (
        <div>
            <a href="/">{t('Home')}</a>
            <h1>{t('Demo:translate at frontend browser,translate needed(React+jsx)')}</h1>
            <span>SPA:Single Page Application</span>
            <div className='layout-header' style={{backgroundColor:'bisque'}}>
                <Com_Header></Com_Header>
            </div>
            <div className='layout-middle'>
                <Resizable minWidth='200' handles='e'>
                  <div className='layout-left' style={{width:'200px',float:'left',overflow: 'hidden',backgroundColor:'aquamarine'}}>
                      <Com_Left switchTab={this.switchTab}></Com_Left>
                  </div>
                </Resizable>
                <div className='layout-right' style={{marginLeft:'200px',overflow: 'hidden'}}>
                  <Com_Right ref={this.ref_right}></Com_Right> 
                </div>
                <div style={{clear:'both'}}></div>
            </div>
            <div className='layout-footer' style={{backgroundColor:'brown',textAlign:'center'}}>
                <span>copyright© Acroprise Inc. 2001-2023</span>
            </div>
            <Com_Language_Engine></Com_Language_Engine>
      </div>
    );
  }
}
export default Com_Main;

com.left.jsx

class Com_Left extends React.Component {
  constructor(props) {
    super(props);
    //this.state = {};
    this.menu_click = this.menu_click.bind(this);
  }
  menu_click(e){
    //console.log(e);
    e.preventDefault();
    //root_right.render();
    let name=e.target.innerHTML;
    let file=e.target.getAttribute('file');
    this.props.switchTab(name,file);
  }
  render() {
    console.log('render left');
    return (
      <div>
        <a href='/'>{acroMulti.t('Home')}</a><br/>
        <a href='/DDEditor' onClick={this.menu_click} file='/react/app/DDEditor/page.ddeditor.jsx'>{acroMulti.t('Data Dictionary Editor')}</a><br/>
        <a href='/likeButton' onClick={this.menu_click} file='/react/app/likeButton/page.likeButton.jsx'>{acroMulti.t('Like Button')}</a><br/>
        <a href='/About' onClick={this.menu_click} file=''>{acroMulti.t('&About')}</a>
      </div>
    );
  }
}
export default Com_Left;

com.right.jsx

import {Tabs,TabPanel} from 'easyui';
import Com_bizCom from './com.bizCom.jsx';

class Com_Right extends React.Component {
    constructor(props){
      console.log('Com_Right constructor');
        super(props);
        this.state={
          tabs:[],
          tabIndex:0,
          tabSelected:''
        }
        this.ref_tabs=React.createRef(null);
        this.ref_tabItems=React.createRef(null);
        this.onTabClose=this.onTabClose.bind(this);
        this.onTabSelect=this.onTabSelect.bind(this);
    }
    switchTab(name,file){
        console.log(name,file);
        console.log(this.state.tabs);
        console.log(this.ref_tabs.current);
        //this.setState({file:file});
        //this.state.file=file;
        let tab=null;
        for(let i=0;i<this.state.tabs.length;i++){
          if (this.state.tabs[i].name==name){
            tab=this.state.tabs[i];
            this.ref_tabs.current.select(i);
            break;
          }
        }
        if (!tab){
          this.state.tabs.push({name,file});
          this.state.tabIndex=this.state.tabs.length-1;
          this.state.tabSelected=name;
          this.setState(this.state);
          //不能切换到新的tab,应该是个bug
          let self=this;
          //self.ref_tabs.current.replaceProps({selctedIndex:self.state.tabs.length-1})
          // this.forceUpdate(function(){
          //   self.ref_tabs.current.select(self.state.tabs.length-1);
          // });
        
          //my god,只有延迟1秒有效
          // setTimeout(function(){
          //   self.ref_tabs.current.select(self.state.tabs.length-1);
          // }, 1000);
        }
        //this.forceUpdate();
        //this.ref_tabs.current.forceUpdate();
        //this.ref_right.current.setState({file:file});
        //this.ref_right.current.forceUpdate();
    }
    onTabSelect(tab){
        console.log('onTabSelect',tab);
        console.log(this.ref_tabs.current);
        for(let i=0;i<this.state.tabs.length;i++){
          if (this.state.tabs[i].name==tab.props.title){
            this.state.tabIndex=i;
            this.state.tabSelected=tab.props.title;
            break;
          }
        }
    }
    onTabClose(tab){
      console.log(tab);
      for(let i=0;i<this.state.tabs.length;i++){
        if (this.state.tabs[i].name==tab.props.title){
          this.state.tabs.splice(i,1);
          console.log(this.state.tabs);
          this.setState(this.state);
          break;
        }
      }
    }
    componentDidUpdate(e){
        //不起作用
        console.log('componentDidUpdate',e,this.state.tabIndex);
        //this.ref_tabs.current.select(this.state.tabIndex);
    }
    
    render(){
      let self=this;
      let tabs=this.state.tabs.map(function(tab){
          return (
            <TabPanel title={tab.name} closable='true' key={tab.name} selected={self.state.tabSelected==tab.name}>
              <Com_bizCom file={tab.file}></Com_bizCom>
            </TabPanel>
          )
      });
        
      return(
          <Tabs ref={this.ref_tabs} onTabSelect={this.onTabSelect}
            
            plain='true' scrollable="true" onTabClose={this.onTabClose}>
            {tabs}
          </Tabs>
      );
    }
}

export default Com_Right;

com.bizCom.jsx

class Com_bizCom extends React.Component {
  constructor(props) {
    super(props);
  }
  shouldComponentUpdate(nextProps, nextState) {
    //console.log(nextProps);
    //文件相同时不要再渲染
    if (nextProps.file && (nextProps.file === this.props.file)) return false;
    return true;
  }
  render() {
    //console.log('Com_bizCom',this.props);
    if (!this.props.file) return null;
    /*
    //import函数不能加载jsx文件
    import(this.state.file).then(function(res){
      console.log(res);
    });
    return;
    */
    let Obj=window.require(this.props.file);
    //console.log(Obj);
    let com=React.createElement(Obj);
    return com;
  }
}
export default Com_bizCom;

效果如下图:

react版本的easyui的tabs元件,可能有bug,新增加的tabPanel不会被选中,无论用tabs的select函数,还是用tabs的selectedIndex属性,或者tabPanel的selected属性,都没搞定。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/696546.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PyTorch Lightning基础入门

Lightning in 15 minutes Lightning in 15 minutes — PyTorch Lightning 2.0.4 documentation 安装 PyTorch Lightning pip install lightning conda install lightning -c conda-forge 定义一个LightningModule LightningModule可以让pytorch的nn.Module可以整合一些训…

一般人不要轻易去学习网络安全(黑客)

笔者本人 17 年就读于一所普通的本科学校&#xff0c;20 年 6 月在三年经验的时候顺利通过校招实习面试进入大厂&#xff0c;现就职于某大厂安全联合实验室。 我为啥说自学黑客&#xff0c;一般人我还是劝你算了吧&#xff01;因为我就是那个不一般的人。 首先我谈下对黑客&a…

Nacos报9848

问题&#xff1a; Connection refused: no further information: localhost/0:0:0:0:0:0:0:1:9848 解决&#xff1a; 这里是springboot集成springcloud后&#xff0c;把application.yml改成bootstrap.yml就可以了&#xff0c;不然读取不到nacos配置。

Java调用scala中map转换问题处理

网上代码 把Javamap转为scala的map代码 scala.collection.mutable.Map<String, String> scalaMap JavaConverters.mapAsScalaMapConverter(map).asScala();Object objMap Map$.MODULE$.<String, String>newBuilder().$plus$plus$eq(scalaMap.toSeq());Object Bui…

再以汇编代码分析c++的右值引用

汇编分析c语言的执行结果最为准确。 可见&#xff0c;右值引用其实还是引用&#xff0c; bb 和 cc 都是对 aa 的引用&#xff0c;其内存里存储了 aa 的地址。 而且还有一个很奇特的现象&#xff0c;bb无法给cc赋值&#xff0c;右值引用无法给右值赋值。 同样是调用std:: move…

更换电脑背景为纯黑色(真的一点其他颜色都没有)

1步2步可合并为第一步 1、控制面板 2、优化视觉显示 3、选中确定即可 查看电脑背景图片&#xff0c;纯黑色&#xff0c;无敌呦&#xff01; 你就说牛不牛 吧

惠普232dw/233dw激光打印机手机WIFI连接实操、初始化

0.首先建议&#xff0c;不要买这台垃圾&#xff0c;经常断网&#xff0c;链接不便 1.手机下载 惠普smart 2.长按i键 3.等到x和↓闪烁&#xff0c; 同时按住wifi和x键持续5秒以上&#xff0c;松开时开始自动初始化&#xff0c;等待一段时间&#xff0c;初始化完成&#xff0c…

零知识证明的示例

目录 零知识证明的示例 怎样通过零知识证明验证联邦学习中模型的真伪 零知识证明的示例 1、零知识洞穴 如图表示一个简单的迷宫&#xff0c;C与D之间有一道门&#xff0c;需要知道秘密口令才能将其打开。P向V证明自己能打开这道门&#xff0c;但又不愿向V泄露秘密口令。 可采…

DAY33:回溯算法(九)解数独(棋盘问题,二维递归)

37.解数独 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#xff08;请参考示例图&#xff09;…

日本知名汽车零部件公司巡礼系列之株式会社168

株式会社168 业务内容&#xff1a; 汽车用防振橡胶制造 公司简介&#xff1a; 代表&#xff1a;片冈幸浩 资本金&#xff1a;4亿9500日元 员工数&#xff1a;&#xff1a;140名 成立时间&#xff1a;2015年4月 强项领域&#xff1a; 住友理工的防振橡胶制品&#xff0…

【IP Phone】网络杂谈(5)之什么是IP Phone?

涉及知识点 什么是 IP Phone&#xff0c;IP Phone简介&#xff0c;网络电话&#xff0c;IP电话的理解&#xff0c; IP Phone的基本原理。深入了解IP Phone关键技术。 原创于&#xff1a;CSDN博主-《拄杖盲学轻声码》&#xff0c;更多内容可去其主页关注下哈&#xff0c;不胜感…

ROCKCHIP ~ 查看NPU/GPU/CPU 频率/使用率

输入以下命令挂载 debug&#xff0c;只有挂载 debug 才可以查看 NPU/GPU/CPU 频率使用率。 mount -t debugfs debugfs /sys/kernel/debugmount | grep debugNPU 输入以下命令查看 NPU 频率&#xff1a; cat /sys/kernel/debug/clk/clk_scmi_npu/clk_rateGPU 查看 GPU 频率&…

【力扣难题图解】25. K 个一组翻转链表-头插法

class Solution { public:ListNode* reverseKGroup(ListNode* head, int k) {ListNode* dummyHead new ListNode(0);dummyHead->next head;ListNode* pre dummyHead;ListNode* cur dummyHead;int count 0;while(cur){// 寻找K-Group链表段count;cur cur->next;// K…

CSDN上最最全的黑客工具软件大全(共100个)

黑客工具软件大全100套 1 Nessus&#xff1a;最好的UNIX漏洞扫描工具 Nessus 是最好的免费网络漏洞扫描器&#xff0c;它可以运行于几乎所有的UNIX平台之上。它不止永久升级&#xff0c;还免费提供多达11000种插件&#xff08;但需要注册并接受EULA-acceptance–终端用户授权协…

4、动手学深度学习——多层感知机:模型选择、欠拟合和过拟合

1、训练误差和泛化误差 训练误差&#xff08;training error&#xff09;是指&#xff0c; 模型在训练数据集上计算得到的误差。 泛化误差&#xff08;generalization error&#xff09;是指&#xff0c; 模型对位置数据项预测的误差&#xff0c;泛化误差体现出了模型的泛化能…

基于Python所写的超级画板设计

点击以下链接获取源码资源&#xff1a; https://download.csdn.net/download/qq_64505944/87959096?spm1001.2014.3001.5503 《超级画板》程序使用说明 在PyCharm中运行《超级画板》即可进入如图1所示的系统主界面。在该界面中&#xff0c;通过左侧的工具栏可以选择所要进行的…

DAY32——贪心part2

1. class Solution {public int maxProfit(int[] prices) {//贪心法 收集相隔两天的利润int res 0;for(int i0;i<prices.length-1;i){//System.out.println(i);int price prices[i1] - prices[i];if(price > 0){res res price;}}return res; } } 2. 代码随想录 (p…

LeetCode·每日一题·2485. 找出中枢整数·前缀和

作者&#xff1a;小迅 链接&#xff1a;https://leetcode.cn/problems/find-the-pivot-integer/solutions/2320800/qian-zhui-he-zhu-shi-chao-ji-xiang-xi-by-e4yp/ 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 著作权归作者所有。商业转载请联系作者获得授权&…

轻量服务器带宽流量和云服务器带宽流量有什么区别?

轻量服务器带宽流量和云服务器带宽流量有什么区别?虽然轻量服务器是轻量化云服务器&#xff0c;但与云服务器的差别还是有一些的&#xff0c;比如这令很多人好奇的轻量服务器带宽和流量和云服务器的区别在哪。下面我们就仔细聊聊关于轻量服务器和云服务器各自的带宽流量差异&a…

K8S集群安装

文章目录 一、环境初始化1、检查操作系统的版本2、主机名解析3、时间同步4、禁用iptables和firewalld服务5、禁用selinux6、禁用swap分区7、修改linux的内核参数8、配置ipvs功能9、重启服务器 二、安装Docker1、切换镜像源2、查看当前镜像源中支持的docker版本3、安装特定版本的…