赞
踩
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:{}};
- let obj=window.eval(code);
- //console.log(window.exports);
- //console.log(window.module);
- if (obj===true){
- 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文件\
- //或者用https://www.npmjs.com/package/breq这个改造一下
- 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,function(){
- tabs不能切换到新的tab,应该是个bug,改用panel
- //self.ref_tabs.current.select(self.state.tabs.length-1);
- let panel=this.ref_tabs.current.panels[this.ref_tabs.current.panels.length-1];
- panel.select();
- });
-
- 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属性,都没搞定。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。