Python中的多页SPA

Python和React之间的桥梁

猫头鹰是可以集成到其他框架中的纳米框架。
来自sova.online的图片,运行3个http服务器:
http://sova.online/-只是猎鹰
http://sova.online:8000/-仅Django
http://sova.online:8001/-仅Python(登录:1,密码:1)
有源代码和安装说明。 那里没有广告。



用Python在React上渲染并创建站点的想法并不新鲜。 有一个很棒的框架https://plot.ly/products/dash/ ,为什么还要做其他事情?
说明:Owl不是为网站开发而设计的。 这是一个用通过浏览器运行的应用程序(桌面应用程序)替换胖客户端的工具。

-什么是网络客户端?
-不 这不是Web客户端。 这是一个浏览器应用程序。
“我不明白。”
-不幸的是,许多开发人员不了解。

总的来说,我积极参与了多个Internet应用程序。

Ugra银行的在线客户(该银行已关闭)。
这是一个很好的应用程序,但它是一个Java applet,即 从浏览器启动胖客户端。 还有乌格拉河岸和过去的小程序。

VTB-24银行在线客户(银行已关闭)。
我是一个人道主义者,但是在创造了这个奇迹之后,残酷的想法开始出现,例如:“强迫开发人员在其中注册1000张薪水”。
而且,作为一个网络客户端,他很漂亮。 动画在手机上打开。 哇! 好酷!
我问一位朋友会计师:您如何与他合作?
她说:太好了! 我以1s加载数据,以1s工作,然后将结果上传回去。

Sberbank在线客户
满意的客户,您可以工作。 当要求我给他评分时,我给他5分中的3分,并给了他评论列表。 这是我每月10笔付款。 每天赚100张账单的人可能会上传信息。

填写付款。

菜单占绿色的区域,占屏幕的20%。 它不只是干扰(位置:固定),还说开发人员不是专业人士。 如果我开始创建付款,则屏幕应该是。 3个按钮:“创建”,“另存为模板”,“取消”。 这些按钮是(出于以下某些原因)。 这不是多页SPA:如果单击菜单项,则表单中的数据将丢失。
这样做的人甚至都不知道这是什么打击:“通常,每个人都这样做,例如图书馆,人们在工作……”。 他是对的。 您需要向项目经理询问,对于经理来说,最主要的是概念模型中的数据库和层。 和形式-我们将聘请男孩,他们会画画。 他们可能为此感到骄傲。

交易大厅(5件,44项联邦法律)
这些是真正的应用程序(不是Web客户端)。 但是我不喜欢现场控制器。
范例:


这是奇怪的对齐方式,字段的宽度明显不足,输入字段中没有自动高度。

另一个例子。 “发布日期”字段中没有dd.mm.yyyy模板,日历错误,日历图标令人恐惧:


在rts-tender上列出:当前条目以彩色突出显示,箭头可以在列表中四处移动,但没有自动滚动(您可以远离屏幕边框),Enter或空格都不打开链接,选项卡未附加到当前记录。 尽管只能用鼠标打开链接,但我给控件加了一个加号。 这样的功能(记住并突出显示当前文档)对于我在mail.ru中是不够的

似乎有些小事。 但是专业应用在细节上与半专业有所不同。 最终用户不会告诉您您拥有什么数据库以及概念模型中的多少层。 它以屏幕形式工作,并具有3个要求:功能性,便捷性,快速性。
不幸的是,系统的选择是由IT专家和老板决定的,他们本身并不使用该系统,也将无法使用。 他们会欣赏速度,在理解功能的同时会欣赏它们的功能,并且他们不会对便利性一窍不通,最重要的是要美观。
Pavel Valeryevich Durov既没有发明社交网络也不是信使。 他方便,美观地完成了用户所需的操作。 人们对此表示赞赏,包括在经济上。

猫头鹰是用于构建专业界面的工具。
这意味着什么,例如EDMS。

有一个EDMS,它具有3个用户组:
上级
文件准备专员
办公室文员。

老板,他们就像孩子。 它们必须简单美观。 理想情况下为1个按钮和1个字段。 并炫耀更多。 一个Web客户端,当然还有一个炫耀的移动客户端。

专家。 网络客户端,跨社交网络/邮件。 不要忘记有很多专家,他们需要接受培训。 环境对他们来说越熟悉,越好。 如果安全服务允许,移动客户端也将派上用场。

办公室文员。 这是猫头鹰派上用场的地方。 办公室文员是一群形成系统的用户。 其他所有人都会生病/休假/停止使用它-EDMS将起作用。 如果注册停止,一切都会结束。
文书工作是一条传送带,在这里一切都很重要,任何琐碎的事情:字体,半色调,自动填充,检查值,易于输入等。
EDS“案例”。 表演者的办公室是在网络客户端上建立的,办公室是一个胖客户端。 一切都很好,但是它将一直有效,直到政府禁止政府机构使用Windows。 我喜欢Win 7,但如果我是统治者,那么IT市场就会充满新订单,而MS仍然记忆犹新。 顺便说一句,在12月6日,安东·席拉扬诺夫(Anton Siluanov)签署了向家用软件过渡的指令

网上在线

猫头鹰如何打开表格。
没有多页。
猫头鹰的中心元素是文档组件。 通过在起始页上按ctrl-U,您将看到创建Document类的对象所需的一切:
-数据库的数据字段;
-要显示的表单的网址;
-dbAlias,unid-用于处理数据库;
-还有其他东西。

在某种程度上,Document是Redux格式的类似物。
表单作为JSON字符串加载,然后前一个字典成为具有样式,className和元素数组(列表)的对象。 数组将以以下形式插入到id = root的元素中
<div style className>……</div> 

数组元素是描述标签的对象。
 <div>, <a>, <img>, <button> 
,数组或组件。
装箱功能负责解析数组。 如果遇到包含数组的元素,它将递归调用自身。
地球的肚脐当然是一个div。
在最简单的情况下,这是一行:dict(div ='Hello',className ='h2')
但是可能会有一个数组(array of arrays):
 def style(**par): return {'style': {**par}} dict( #     sova.online style(position='relative'), readOnly = 1, div = [ dict( style(width=1000, margin='auto', paddingTop=20), div=[ { 'div': subFormTop.panel() }, { 'div': [subFormLeft.panel(), subFormRight.panel()], 'className': 'row' }, # { 'div': [subFormDown.panel()] }, ]), ] ) 


有3个面板(每个面板在一个单独的文件中:subFormTop.py等)。
subFormTop.panel()返回一个数组以构建顶部面板。
subFormLeft.panel()和subFormRight.panel()组合成一个字符串(“ className”:“ row”),并描述左右面板。
subFormDown.panel()已被注释掉(无用)。

这似乎很复杂。 但这是Python:一切都可以简化。
期刊“报告”中表格的示例。 labField函数(标签,DB_field_name)返回一个包含两个字典的数组(表行):第一个字典是{'div':label},第二个字典是{'field':[DB_field_name,'tx']}。
 div = [ docTitle(''), dict ( wl='40mm', className='cellbg-green', div=_table( labField('', 'nodafd'), labField(' ', '_STARTINGTIME'), labField('', '_ENDTIME'), labField('', 'CREATOR'), labField('', 'REPORTCAT'), labField('', 'REPORTNAME'), labField('', 'REPORTTITLE'), labField(' ', 'dt1'), labField(' ', 'dt2'), labField('  2', 'dt3'), labField('  2', 'dt4'), labField('', 'LBYEARS'), labField('', 'GRGROUP'), labField(' ', 'QUERYMAIN'), labField('', 'NOTES'), )), sent(), ] 




来自sova / api / forms / home / top.py的示例(从sova.online开始):

python字典
{'a':'React v16','href':'https://reactjs.org'}
产生清晰的React组件
 <a href={'https://reactjs.org'}>React v16</a> 


Img比标准聪明-在道具中,您可以指定href和目标:

Python:
dict(img ='image?react.ico',style = {'width':16},href ='https://reactjs.org')

将对象数组转换为组件的解析器的一部分(boxing.js):
 if ( td.img ) { // td –    let img = <img src={td.img} _/>; return td.href ? <a href={td.href} key={i} target={td.target}>{img}</a> : img; } 


输入搜索引擎“反应组件库”。 结果是可以预见的-很多。 但是,所有这些丰富的都是针对网站的,而不是针对应用程序的:
智能文本区域也许是唯一适合我的控件。
React-select-简化并重新放置下拉列表
数据选择器/日历-找不到合适的东西。 他以内置的G.Chrome为例,编写了自己的文章。
上传/下载-没有合适的东西,写了我自己的。

恕我直言:网站的前景可悲。 在不久的将来,绝大多数用户将停止使用浏览器(或已经停止使用)。 手机将与平板电脑一起成长,十个应用程序中的一小部分将完全满足需求。
我已经遇到两次程序员,他们不知道如何正确编写电子邮件地址。 他们为什么要记住自己不使用的东西。 世界在变化。

在Owl中,控制器并不是完美的,但它们是为操作员而非Web用户设计的。
例如,“转让商标”表格。 一种相当通用的形式,用于有老板的地方。 屏幕快照中以红色圈出了用于控制隐藏的字段。 如果在执行部分中有一些针对不同表演者的不同条款的说明,则其他决议会在填充时自动打开。 每组两个学期:第一学期至第一表演者,第二学期至共同执行者。


您可以在此处触摸表格

控制器是与数据库字段关联的React组件。
可以检查其操作的控制器的详细说明在Sova.online上。
注意rtf和json类型。 Rtf显示为文本,但是如果文本中存在{_ {object} _}构造,Owl将对此构造执行json.parse并将结果添加到表单中。 json类型的字段应存储标记元素数组的描述:[{ele1},{ele2},...]。 json.parse在渲染之前执行。
这些类型的字段允许您将标记存储在数据库或文件中。 对于报告和编写文档很有用。

所有类型的字段的控制器列表(controllers.js):
 export const controller = prop => { switch (prop.type) { //  case 'chb': return <Checkbox {...prop}/>; case 'lbse': // listbox single enable (    ) case 'lbme': // listbox multivalue enable // lbse/lbme -     ,   case 'tx': return <Text {...prop}/>; // smart-textarea case 'lbsd': // listbox single disables (    ) case 'lbmd': return <ListBox {...prop}/>; case 'dt': return (prop.readOnly ? <Text {...prop} xValue={Util.dtRus(prop.xValue)} /> : <Datepicker {...prop}/>); case 'fd': return <ForDisplayOnly {...prop}/>; case 'table': case 'gr': return <Table {...prop}/>; case 'rtf': return <RTF {...prop}/>; case 'json': return <JsonArea {...prop}/>; case 'list': return <List {...prop}/>; case 'view': return <View {...prop}/>; default: console.warn('  ', prop.xName, prop.type); return <Text {...prop}/>; }; }; 


应用程序需要一种用于操纵控制器的机制。
在猫头鹰中,所有文档控制器都存储在一个文档变量中
此注册
我不敢使用裁判,因为有传闻说编辑人员会取消它。
控制器可能具有以下接口:
getValue(参数)
setValue(值,参数)
setFocus()
changeDropList()

为了访问所需的字段,有一些文档方法
getField(fieldName,param)
setField(fieldName,value,param)
changeDropList(fieldName,参数)
setFocus(fieldName)
对于FileShow类型的字段,有一个方法fileShow ['FILES1 _']。HasAtt(),其中FILES1_是文件区域的名称。 如果有附件,则返回true。 在此类区域的转移标记中2。

控制器可以生成一个recalc事件。 如果为该字段注册了处理程序,它将执行。 处理程序位于可加载的js文件中。
一个示例和稍微简化的描述:
有一种形式“转让商标”(o.py)。 它包含已加载的o.js文件
在o.js中,处理程序已注册
 recalc: { PROJECTO: doc => doc.forceUpdate(), WHOPRJ2: doc => doc.forceUpdate(), WHOPRJ3: doc => doc.forceUpdate(), …   } 

,还指定了隐藏条件(project,op,prj1,prj2 ... prj5是divs描述中的“名称”属性):
 hide: { project: doc => !doc.getField('projectO'), // ,   PROJECTO  op: doc => doc.getField('projectO'), // ,   PROJECTO   prj1: doc => !doc.getField('projectO'), prj2: doc => !doc.getField('projectO'), prj3: doc => !doc.getField('projectO') || (!doc.getField('whoPrj2') && !doc.getField('whoPrj3')), prj4: doc => !doc.getField('projectO') || (!doc.getField('whoPrj3') && !doc.getField('whoPrj4')), prj5: doc => !doc.getField('projectO') || (!doc.getField('whoPrj4') && !doc.getField('whoPrj5')), }, 


工作原理:PROJECTO字段是一个复选框,当值更改时,控制器将生成一个recalc事件,文档将调用recalc.PROJECTO(此)处理程序。
处理程序仅调用forceUpdate()重绘文档。
重绘时,将检查props中的组件是否具有名称,该名称是否具有hide [props.name]函数以及是否返回true。
prj3:doc =>!doc.getField('projectO')|| (!doc.getField('whoPrj2')&& !! doc.getField('whoPrj3'))
如果“ projectO”复选框为OFF或在决议2和决议3的字段中未输入执行者(“ whoPrj2”和“ whoPrj3”字段均为空),则隐藏第三个决议(具有props.name ==='prj3'的区域)。
调用函数时,字段名称不区分大小写。
WHOPRJ2是一个组合框;当您选择一个值时,控制器还将生成一个recalc事件,这也将导致重画。 通过选择第二个分辨率的艺术家,您将因此打开第三个分辨率。

在加载的js文件中,您可以:
-管理隐藏;
-仅管理阅读;
-应对现场变化;
-执行按钮命令;
-在保存之前对字段和表单进行验证;

下载表格“ fo”的文件:
 window.sovaActions = window.sovaActions || {}; window.sovaActions.fo = { // fo –      recalc: { //           PROJECTO: doc => doc.forceUpdate(), }, hide: { //   ,  true project: doc => !doc.getField('projectO'), }, readOnly: { //     ,  true who: doc => doc.getField('SENTFROMDB'), }, validate: { //         who: doc => doc.getField('who') ? '' : '   " "', form: doc => new Promise( (yes, no) => { let disableAutoOrder = false; for (let i = 1; i <= 5; i++) { let val = doc.getField('RESPRJ' + i); disableAutoOrder |= /  /.test(val); } disableAutoOrder && doc.setField('AUTOORDER', ''); yes(); }), }, cmd: { //    logoff: doc => { window.location.href = '/logoff' }, }, } 


字段验证-如果一切正常,则返回空的函数,或者有关错误的消息。 猫头鹰会将焦点放在无效字段上。
表格验证-承诺。 在该示例中,没有检查(总是调用“是”),只是在发送到服务器之前完成了一些操作。
在redux形式中,验证是通过trow进行的-一种野性。

对于那些不了解诺言的人,最简单的例子是:
 const confirmDlg = msg => new Promise((ok, cancel) => confirm(msg) ? ok('  ') : cancel('  cancel')); confirmDlg('  ') .then( s => console.log(s)) .catch( s => console.log(s)); 


Document类具有几个可在按钮中使用的预定义命令:

编辑:切换到表单编辑模式
保存:保存表格
关闭:关闭表格
saveClose:保存并关闭表单

prn:使用选择的打印模板打印表格
docOpen:打开文件
dbOpen:打开日志
xopen:打开网址
newDoc:使用所需的表单创建一个新文档

Redux形式的api更丰富-在Owl中只有必需的。

多页。

Document类创建一个嵌入到元素中的对象(窗体)。
 <div id="root"></div> 

我们将其称为“根文档”。 如果您将元素添加到根文档中
<div style = {{position:'absolute',zIndex:999}} />,您还可以在其中插入另一个Document对象。
如何处理可加载命令处理程序? 很简单:每个表单都有自己的处理程序(自己的js),并且根文档应加载可能需要的处理程序。
起始页示例sova.online(home.py)
为了演示多页性质,home.py表单使用“ rkckg”,“ outlet”,“ outlet.gru”,“ o”形式打开文档。
为了使所有表单正常工作,您需要在home.py中注册这些表单的脚本:
 javaScriptUrl = ['jsv?api/forms/rkckg/rkckg.js', 'jsv?api/forms/outlet_gru/outlet_gru.js', 'jsv?api/forms/outlet/outlet.js', 'jsv?api/forms/o/o.js', 'jsv?api/forms/home/home.js'] 


由于调用处理程序的任何功能时,第一个参数会将链接传递到文档,因此将对所需文档执行操作。

OOP,没有奇迹。

反应-不是反应
我已经描述了报告表格。 它从报告管理器打开(箭头“ React”),并描述用于收集报告的参数。

报告本身(箭头“ not React”)以html附件的形式存储在下级文档中,形式为“ rreport”。 当React不存在时,我们就参与了报告的开发,“报告”表单很简单(html的20行和js的15行),为什么要改变8年的工作原理。

打开报表管理器。

rreport表单包含4个按钮和一个iframe。 打开文档之前,猫头鹰用url行替换src =“”,以在iframe中下载html附件,其余的由浏览器完成。
EXCEL / WORD按钮相似:将要下载的url按钮以文件名“ report.html.xls”或“ report.html.doc”以及相应的mime类型插入正确的位置。 其余的工作由Excel / Word完成(“这些聪明的动物完全理解它们想要从它们那里得到的一切”)。
从do_get.py:

 downloadUrl = '/download/' + fn + '?' + '&'.join([d.db.alias, d.unid, idbl, fsName, fzip, ctype, flen]) excel = '/download/%s.xls?%s' % (fn, '&'.join([d.db.alias, d.unid, idbl, fsName, fzip, 'application/x-excel', flen])) word = '/download/%s.doc?%s' % (fn, '&'.join([d.db.alias, d.unid, idbl, fsName, fzip, 'application/msword', flen])) html = html.replace('src=""', 'src="%s"' % downloadUrl).replace('openExcel', excel).replace('openWord', word) 


在Excel / Word中打开html时,与浏览器有所不同,但差异很小。 这篇文章不是关于这个的。

从头开始制作形状。

源数据:
有3个功能
def snd(* msg,cat ='snd'):
def err(* msg,cat ='all'):
def dbg(* msg,cat ='snd'):
,它们或多或少均匀地分布在整个代码中,并将错误消息和其他内容写入日志文件。
消息格式通过以下方式传递给logging.Formatter:
'%(asctime)s%(levelname)s [%(name)s]%(message)s'
该文件充满了消息
...
09/02/2018 17:50:07 DEBUG [http-server] addr('127.0.0.1',49964),“ GET / arm HTTP / 1.1” 200-
09/02/2018 17:54:07信息[可用空间]附件保存在“。\ DB \文件”中免费68557 Mb
2018年9月2日17:58:07错误[do_get.py] getScript:[Errno 2]没有这样的文件或目录:'sova / api / forms / o / oo.js'
...
日期时间,然后是级别,然后在方括号中的级别中是类别,然后是消息。

挑战:
制作页面以查看日志文件。 什么类型


我们将表单称为“ lm”,它将由api /forms/lm.py模块中的页面函数形成
 def page(dbAlias, mode, userName, multiPage): return dict( style(background='url(/image?bg51.jpg)', backgroundSize='100% 100%'), div=[ dict( style(width='200px', float='left', background='rgba(210,218,203, 0.5)', padding='0 5px'), div=[ _field('type', 'list', [' |all', '|err', '|info', '|debug'], saveAlias=1, **style(margin='10px auto', width=170, height=110) ), _field('cat', 'list', 'TYPE_ALIAS|||api.get?loadDropList&logger|keys_{FIELD}', listItemClassName='repName', listItemSelClassName='repNameSel', **style(height='calc(100vh - 133px)', overflow='auto') ) ], ), _field('msg', 'fd', br=1, **style(overflow='auto', height='100vh', font='bold 12px Courier', background='rgba(255,255,255, 0.8)') ), ] ) 


左侧有2个字段,均具有列表类型:type和cat(消息类型和类别)。
右边是一个类型为fd(forDisplayOnly)的msg字段。
消息类型写在字段描述中(['All log | all','Errors | err',...),
xhr通过棘手的url调用将类别从全局词典中拉出:
api.get?loadDropList&logger | keys_err将以json格式返回全局词典中类别的数组(列表)。 很像(“ logger”,“ keys_err”)。
通过lm.py中的queryOpen函数打开文档时会生成消息
 def queryOpen(d, mode, ground): logParser() ls = well('logger_all', 'AL L') s = '\n'.join(reversed(ls)) d.msg = s d.type_alias = 'all' 

logParser读取并分析日志文件。 它将结果分解成几个数组,并将它们保存在全局字典中。 没什么有趣的:2个简单的re和Iterator循环。
用于全局字典的函数:
toWell(o,key1,[key2])-将对象“ o”保存在全局字典中
好(key1,[key2])-通过键(通过两个键)从全局词典中获取一个对象。
这对于第一张图就足够了。 为了能够显示所需类型和类别的消息,必须制作可加载的js。
在lm.py中添加该行
javaScriptUrl ='jsv?api /表格/lm/lm.js'
并创建lm.js:
 window.sovaActions = window.sovaActions || {}; window.sovaActions.lm = { //   "lm" init: doc => doc.changeDropList('CAT'), recalc: { TYPE: (doc, label, alias) => { doc.changeDropList('CAT'); getLogData(doc, alias + '|AL L'); }, CAT: (doc, label) => getLogData(doc, doc.getField('type_alias') + '|' + label), }, }; // *** *** *** let getLogData = (doc, keys) => { fetch('api.get?getLogData&' + keys, {method: 'get', credentials: 'include'}) .then( response => response.text() ) .then( txt => doc.setField('msg', txt) ) .catch( err => doc.setField('msg', err.message) ); }; 


getLogData从服务器中提取所需类型和类别的消息:
 def getLogData(par, un): lg, _, cat = par.partition('|') msg = well('logger_' + lg, cat) return 200, 'text/html; charset=UTF-8', '\n'.join(reversed(msg)) 


您可以在这里享受表格。
最初,记录是根据标准记录模块完成的
使用logging.FileHandler,.addHandler和其他getLogger和setFormatter。
怎么教。 但与此同时,这是越野车。 您可以扔石头,但是当我扔日志并开始写文件时,代码变得更短,更易于理解,并且故障消失了。

包含具有Digest授权的自写多线程wsgi服务器。 这不适用于网站。 为什么根本需要他?
客户有40 jur。 人员,大多数情况下,1-2-3人会使用该系统。 禁止在Internet上存储数据。 全部获胜7。要求易于安装和配置。
解决方案:使用cx-Freeze和Inno Setup,我们进行安装,在最负责任的计算机上运行该安装程序,并从Windows服务开始为本地网络获取mini-http服务器。 没什么 您不能使用Python内置的内置wsgiref.simple_server或wsgi_Werkzeug,因为 它们是单线程的:当一个请求失败时,其他请求将等待。
报告内置Django WSGIServer / 0.2 CPython / 3.5.3的速度比自编写的Python快好几倍,这让我感到惊讶的可能性不大。 但这无关紧要-表单和目录缓存在客户端上,只有数据库数据通过本地网络非常快速地传输。
还有一个原因:桌面应用程序可以访问计算机资源(数字签名,文件,扫描仪...)。 为了从浏览器获得相同的访问权限,您必须在服务中编写插件或挂断小型http服务器,这些服务器可以从主服务器上嗅探并在本地执行必要的操作。

猫头鹰不使用框架工具来处理数据库。 在dbToolkit目录中,其结构类似于SQLite3中的MongoDB(或Lotus Notes):
图书类-数据库(使用MongoDB和Lotus Notes术语)
Class DocumentCollection-书籍中的文档集合
Document类是一个文档(一个包含任意多个字段的对象)。

安装方式:
从sova.online下载owl.zip

归档文件包含owl目录,您可以从中运行来自django,falcon或没有框架的Owl。

下载,解压缩。
安装Python3(3.5+)

1.猫头鹰-没有框架。 注意! 登录名:1,密码:1

Linux:
cd ./猫头鹰
python3 wsgi_sova.py

或在单独的窗口中
屏幕-Udm python3 wsgi_server.py

Windows:
cd ./猫头鹰
wsgi_sova.py

2. Django

Linux:
安装django:
pip3安装Django
cd ./猫头鹰
python3 manage.py运行服务器

或在单独的窗口中
屏幕-Udm python3 manage.py runserver 127.0.0.1:8000

Windows:
安装django:
pip安装Django
cd ./猫头鹰
manage.py运行服务器

3.猎鹰

Linux:
pip3安装猎鹰
cd ./猫头鹰
python3 wsgi_sova.py falcon应用:api 8001 log_falcon / falcon

Windows:
点安装猎鹰
cd ./猫头鹰
wsgi_sova.py falcon应用程序:api 8001 log_falcon / falcon

**********************

-文章标题很奇怪,您了解什么是“ Multipage SPA”吗?
-正常的营销策略
-为什么没有Redux? 每个人都使用Redux。
-我不喜欢“减速器”一词
-但是认真吗? ombineReducers在任何层次的层次上……是如此的美丽
多页,宝贝。 命令处理程序应位于表单内部,而不像鹿角
-为什么还要写文章?
-公关

Source: https://habr.com/ru/post/zh-CN424779/


All Articles