IDOM从React那里得到启发,并尽可能尝试与它直接复制的功能实现对等。没有比这经常被称赞的“挂钩”版本更明显的了。 Python中的IDOMimplements。
乍一看,IDOM和React之间的相似性非常惊人。下面是一个React组件,该组件定义了一个简单的Counter,用于显示单击按钮的次数:
从" react"导入React,{useState} ;从" react-dom"导入ReactDOM ;函数Counter(){const [count,setCount] = useState(0); return(< div><按钮onClick = {()=> setCount(count + 1)}>单击我!< /按钮>< p> {`单击计数:$ {count} `}< / p>< / div>); } ReactDOM。渲染(< Counter /&gt ;, document。getElementById(" root"));
导入idom @idom。组件def Counter():count,set_count = idom。钩子。 use_state(0)返回idom。 html。 div(idom .html。button({" onClick":lambda event:set_count(count + 1)}," Click me!"),idom .html。p(f& #34;点击计数:{count}")))idom。跑(柜台)
在过去的5年中,前端开发人员似乎得出结论,以声明式风格或框架编写的程序比强制性编写的程序更易于理解和维护。简而言之,程序中的可变状态会很快导致不可持续的复杂性。这种趋势在很大程度上由诸如Vue和React这样的Javascript框架的兴起所证实,它们描述了计算逻辑而没有明确说明其控制流程。
那么,这与Python和IDOM有什么关系?好吧,因为浏览器是互联网上事实上的操作系统,所以即使像Python这样的后端语言也不得不寻找与之集成的聪明方法。尽管标准REST API非常适合使用HTML模板构建的应用程序,但是现代浏览器用户期望比仅凭此功能就可以实现的交互程度更高。
限制性生态系统-为一个框架开发的UI组件无法轻松移植到其他框架,因为它们的API过于复杂,没有文档说明或结构上无法使用。
命令式范例-IPyWidgets和Bokeh尚未采用前端开发人员所倡导的相同声明式设计原则。另一方面,Streamlit和Dash是声明性的,但没有实现React或Vue提供的功能。
有限的布局-最初,这些库的开发人员受到数据科学家可视化需求的驱动,因此创建复杂的UI布局的能力可能不是主要的工程目标。
以后的文章将针对与上述每个项目的具体比较,但是现在,我们仅关注IDOM及其对这些问题的解决方案。
IDOM具有一组灵活的核心抽象,可使其与同级接口。在撰写本文时,Jupyter和Dash均受支持,而Streamlit和Bokehare则在工作中:
通过提供定义良好的接口和直观的协议,IDOM使您可以轻松地通过备用实现交换掉堆栈的任何部分。例如,如果您的应用程序需要其他Web服务器,则IDOM已经有3个选项可供选择,或者用作创建自己的蓝图:
您甚至可以使用IDOM的Javascript React客户端库在生产级应用程序中定位IDOM的使用。只需将其安装在您的前端应用中,然后连接到为IDOM模型提供服务的后端Websocket。 IDOM自己的文档是该目标用法的主要示例-页面的大部分是静态HTML,但是嵌入其中的是交互式示例,这些示例具有从Web套接字提供的实时视图:
IDOM通过采用React的挂钩设计模式,继承了其许多美学和功能特征。对于那些不熟悉钩子的用户,用户界面由基本的HTML元素组成,这些元素由称为" components"的特殊函数构造和返回。然后,通过挂钩的魔术,可以使那些组件功能具有状态。考虑下面的组件,该组件显示与门的基本表示形式:
导入idom @idom。组件def AndGate():input_1,toggle_1 = use_toggle()输入_2,toggle_2 = use_toggle()返回idom。 html。 div(idom .html。input({" type":" checkbox"," onClick":lambda event:toggle_1()}),idom .html。input ({" type":" checkbox"," onClick":lambda event:toggle_2()}),idom。html。pre(f" { input_1} AND {input_2} = {input_1 and input_2}"),)def use_toggle():state,set_state = idom。钩子。 use_state(False)def toggle_state():set_state(lambda old_state:not old_state)返回状态,toggle_state idom。运行(AndGate)
这是其工作原理的非常高级的摘要...第一次呈现以上组件的视图时,将调用AndGate函数,其中input_1和input_2的初始状态为False。然后,该函数返回一系列HTMLelements,这些HTMLelements带有响应客户端事件的回调。幕后的机器随后意识到该声明,并显示两个复选框按钮,其文本为False AND False = False。稍后,当用户单击现在可见的复选框按钮时,将触发客户端事件,相关联的回调将旧状态从False转换为True进行响应,并计划重新渲染组件。重新渲染时,将再次调用该函数,但这一次,其中input_1和input_2已更新以反映新状态,从而导致显示的文本发生更改。
在上面的代码中,请考虑以下事实:事件发生时,它从未明确描述如何演化前端视图。相反,它声明给定特定状态,这就是视图的外观。然后,IDOM有责任弄清楚如何使该声明成为现实。定义结果而不说明实现结果的手段的这种行为是使IDOM和React" declarative"中的组件产生作用的原因。为了进行比较,一种用于定义同一接口的假设性和更强制性的方法可能类似于以下内容:
layout = Layout()def make_and_gate():状态= {" input_1" :False," input_2" :False} output_text = html。 pre()update_output_text(output_text,state)def toggle_input(index):状态[f" input_ {index}" ] =不声明[f" input_ {index}" ] update_output_text(输出文本,状态)返回html。 div(html。input({" type":" checkbox"," onClick":lambda event:toggle_input(1)}),html.input({ #34; type":" checkbox"," onClick":lambda事件:toggle_input(2)}),output_text)def update_output_text(text,state):text。 update(children =" {input_1} AND {input_2} = {output}"。format(input_1 =状态[" input_1"],input_2 =状态[" input_2&# 34;],输出=状态[" input_1"]和状态[" input_2"],))布局。 add_element(make_and_gate())布局。跑 ()
重构是困难的-函数在make_and_gate中更加专门于其特定用途,因此不能轻易地泛化。相比之下,声明性实现中的use_toggle可以适用于布尔指示器打开和关闭的任何情况。
没有明确的静态关系-没有一段代码可以识别视图的基本结构和行为。我们必须从两个不同的位置调用update_output_text的事实说明了此问题。一次出现在make_and_gate的主体中,再次出现在回调toggle_input的主体中。这意味着,要了解output_text可能包含的内容,我们还必须了解其周围的所有业务逻辑。
引用链接会导致复杂性-要发展视图,各种回调必须保存对它们将更新的所有元素的引用。从一开始,这使编写程序变得很困难,因为必须在需要的地方在调用堆栈上下传递元素。然而,进一步考虑,这还意味着在调用堆栈中分层的功能可能会意外或有意影响程序表面上不相关的部分的行为。
为了在其后端Python服务器和Javascript客户端之间进行通信,IDOM专家采用了一种与Model-View-Controllerdesign模式非常接近的方法-控制器位于服务器端(尽管并非总是如此),该模型是什么39;在服务器和客户端之间进行同步,并且该视图在Javascript的客户端运行。绘制出来可能看起来像这样:
相比之下,IDOM使用称为虚拟文档对象模型(VDOM)的东西来构造视图的表示。 VDOM是在Python旁边的组件上构造的,然后随着它的发展,IDOM的布局会计算VDOM差异并将其连接到其Javascript客户端,并最终在其中显示:
除了大大降低复杂性之外,此过程还意味着仅具有一点点HTML和CSS知识的Python开发人员就可以轻松创建elabortateinterface,因为他们可以完全控制视图。当然,许多用户可能不在乎细节,只想要高级组件,但是对于那些这样做的人来说,很容易将其作品分发给其他人以供Python软件包使用。
如果您正在认真考虑IDOM对虚拟DOM的使用,那么您可能已经想到...
是否将视图的虚拟表示连接到客户端,即使其差异很大,价格也不菲?
是的,尽管IDOM的性能足以满足大多数用例,但在不可避免的情况下这可能是一个问题。值得庆幸的是,就像它的同辈一样,IDOM使得似乎可以集成Javascript组件成为可能。它们可以针对您的用例进行定制构建,或者您无需任何额外的工作就可以利用现有的Javascript生态系统:
import json导入idom material_ui = idom。安装(" @ material-ui / core",fallback =" loading ...")@idom。组件def DisplaySliderEvents():event,set_event = idom。钩子。 use_state(None)返回idom。 html。 div(material_ui。Slider({" color":" primary"," step":10," min":0,&#34 ; max":100," defaultValue":50," valueLabelDisplay":" auto"," onChange":lambda *事件: set_event(event),}),idom.html.pre(json.dumps(event,indent = 2))))。运行(DisplaySliderEvents)
过去,以Python开发人员的身份构建高度交互的Web应用程序一直是一个巨大的挑战。 但是IDOM改变了这一点。 您只需要基本的HTML,CSS和Python,就可以将幻灯片放到仪表板中,并在任何需要的地方使用它,无论是在JupyterNotebook还是现有的Web应用程序中。