直接在Python中写入HTML组件,您会有一种美丽但有争议的混合物。
是的,有争议。
如果您不喜欢它,请忽略它(但是可以在没有html-in-in-python部分的情况下使用它,请参见下文;))
基于pyxl。仅Python 3.6+,并使用键入进行数据验证。
拥有HTML后,您可以对此做任何您想做的事情。将其视为替代您的经典模板引擎。
源代码:https://github.com/twidi/mixt/
文档:https://twidi.github.io/mixt/
PYPI :https://pypi.org/project/mixt/
CI (CircleCi):https://circleci.com/gh/twidi/workflows/mixt/
让我们创建一个文件example.py
# coding: mixt
from mixt import html , Element , Required
class Hello ( Element ):
class PropTypes :
name : Required [ str ]
def render ( self , context ):
return < div > Hello , { self . name } < / div >
print ( < Hello name = "World" / > )并执行它:
$ python example.py
< div > Hello, World < /div >如果您不喜欢在Python中写HTML,您仍然可以使用它:
from mixt import html , Element , Required
class Hello ( Element ):
class PropTypes :
name : Required [ str ]
def render ( self , context ):
return html . Div ()( "Hello, " , self . name )
print ( Hello ( name = "World" ))是的,它的灵感来自React(实际上,主要是JSX),我们借用了一些概念:
我们补充说:
运行这两个命令。第二个将告诉Python如何使用HTML内部理解文件。
pip install mixt
mixt-post-install要检查一切准备就绪,请运行:
python -m mixt.examples.simple您应该有此输出:
< div title =" Greeting " > Hello, World </ div >如果您不想使用python中的html,请不要运行mixt-post-install 。然后使用(具有相同输出)进行测试:
python -m mixt.examples.simple_pure_python然后克隆GIT项目:
make dev要检查一切准备就绪,请运行:
python -m mixt.examples.simple您应该有此输出:
< div title =" Greeting " > Hello, World </ div >完成一些代码后:
make testsmake lint如果您触摸codec目录中的内容,则必须运行make dev (或至少make full-clean )才能清除pyc Python文件。
请注意,我们的CI将检查每个提交都会通过make lint , make tests并make check-doc 。因此,不要忘记为每个提交运行这些。
在推动之前做到这一点的一种方法是:
git rebase develop --exec ' git log -n 1; make checks ' 注意:您可以在src/mixt/examples/user_guide中找到本用户指南的最终代码(您会找到mixt.py和pure_python.py )。
运行它:
python -m mixt.examples.user_guide让我们创建一个...待办事项清单,是的!
但是以前,请记住。这不是反应,它不在浏览器上,这里不涉及JavaScript。我们只谈论渲染一些HTML。
但是您可以做自己想做的事。添加JavaScript处理程序,简单表格...
谈论形式...
在待办事项列表中,我们希望能够添加待办事项。这是一个简单的文本输入。
因此,让我们创建我们的第一个组件TodoForm 。我们想要带有输入文本和按钮的表格。
组件是Element类的子类,并具有您必须编写的render方法。
# coding: mixt
from mixt import Element , html # html is mandatory to resolve html tags
class TodoForm ( Element ):
def render ( self , context ): # Ignore the ``context`` argument for now.
return # The ```` is only for a better indentation below
< form method = "post" action = "???" >
< label > New Todo : < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >请注意,这可以写成一个简单的功能:
# coding: mixt
from mixt import Element , html
def TodoForm ():
return
< form method = "post" action = "???" >
< label > New Todo : < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >打印组件时,这两个将给出相同的结果:
print ( < TodoForm / > ) < form method =" post " action =" ??? " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >注意如何格式化:标签之间没有空间。实际上,就像在JSX中一样:
JSX在一条线的开头和结束时删除了空格。它还删除了空白线。删除与标签相邻的新线路;在字符串文字中间发生的新线条被凝结成一个空间
要添加一个空间或新线,您可以通过一些Python。举例来说,让我们在标签之前添加一个newline:
#...
< form method = "post" action = "???" >
{ ' n ' } < label > New Todo : < / label > < itext name = "todo" / >
#...现在我们有了这个输出:
< form method =" post " action =" /todo/add " >
< label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >现在让我们走得更远。
注意表单的action属性。我们需要通过一些东西。但是硬编码听起来不正确。 WWE需要将其传递给组件。
Mixt像React一样,是属性的概念,也就是“ Props”。
在Mixt中,我们在组件中的类中使用类型定义它们,名为PropTypes :
class TodoForm ( Element ):
class PropTypes :
add_url : str
def render ( self , context ):
return
< form method = "post" action = { self . add_url } >
< label > New Todo : < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >在这里,我们定义了一个名为add_url的prop,它必须是字符串( str )。这使用Python键入语法。
并注意如何更改form标签的action属性。现在是{self.add_url}而不是"???" 。
当属性在卷曲括号之间传递时,它们在运行时被解释为纯Python。实际上,由于mixt解析器将整个文件转换为Pure Python,然后再将运行python解释器运行,因此它将保持不变,只有周围的HTML将被转换。因此,没有任何罚款。
看看如果我们的组件是用纯Python编写的:
from mixt import Element , html
class TodoForm ( Element ):
class PropTypes :
add_url : str
def render ( self , context ):
return html . Form ( method = 'post' , action = self . add_url )(
html . Label ()(
html . Raw ( "New Todo: " )
),
html . InputText ( name = 'todo' ),
html . Button ( type = 'submit' )(
html . Raw ( "Add" ) # or html.Rawhtml(text="Add")
),
)注意如何将道具传递给组件(命名参数)以及如何传递action : action=self.add_url 。
这个纯净的Python组件还向您展示了它的工作原理:Props以命名参数为组件类别,然后称为该组件,将儿童组件作为位置参数传递给呼叫:
ComponentClass ( prop1 = "val1" , prop2 = "val2" )(
Children1 (),
Children2 (),
)什么是孩子?孩子是其他标签内的标签。
在<div id="divid"><span /><p>foo</p></div>中,我们有:
html.Div组件,带有一个道具id和两个孩子:html.Span组件,没有孩子html.P组件:html.RawHtml组件请注意,您可以玩道具和孩子。首先,Pure-Python中的版本显示其工作原理:
def render ( self , context ):
props = { "prop1" : "val1" , "prop2" : "val2" }
children = [ Children1 (), Children2 ()]
return ComponentClass ( ** props )( * children )
# You can pass a list of children to to the call, so this would produce the same result:
# ComponentClass(**props)(children)然后是mixt版本:
def render ( self , context ):
props = { "prop1" : "val1" , "prop2" : "val2" }
children = [ < Children1 / > , < Children2 / > ]
return < ComponentClass { ** props } > { * children } < / ComponentClass >
# or, the same, passing the children as a list:
# return <ComponentClass {**props}>{children}</ComponentClass> 现在让我们回到我们的道具add_url 。
如何将其传递给组件?
我们将属性传递给HTML标签的完全相同的方式:实际上它们是在HTML综合中定义的Prop(在mixt.html中定义)。我们支持每个HTML标签,在撰写本文时,在html5中及其属性(不包括不弃用的)在HTML5中是有效的(未弃用)。
因此,让我们这样做:
print ( < TodoForm add_url = "/todo/add" / > ) < form method =" post " action =" /todo/add " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >好的,我们将道具放在渲染的HTML中。
如果我们不通过字符串怎么办?我们用PropTypes说我们想要一个弦...
让我们尝试一下:
print ( < TodoForm add_url = 1 / > ) < form method =" post " action =" 1 " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >有用!但是...这不是字符串!实际上,有一个特殊情况,您可以将它们作为数字而不是字符串传递,如果需要,它们会被转换为...
因此,让我们尝试一下。
print ( < TodoForm add_url = True / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > )如果我们在python中True
print ( < TodoForm add_url = { True } / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > )好的,让我们欺骗系统并将"True"作为字符串传递。
print ( < TodoForm add_url = "True" / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > )还是一样,但是我们在这里通过了一个字符串!是的,但是总是有4个值对它们似乎是:
None的)将这些值之一传递为字符串的唯一方法是通过python作为字符串传递它们:
print ( < TodoForm add_url = { "True" } / > ) < form method =" post " action =" True " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >除了这4个值和数字外,每个传递给属性的值都被视为字符串。即使没有引号,例如HTML5中的HTML中的引号,在没有某些字符的字符串(没有空格,没有/ ...)的字符串中,引用并不是强制性的。
要通过其他内容,您必须将其围绕卷曲括号中的值包围(在这种情况下,卷曲括号周围不需要引号。
好的,现在我们确定我们只接受字符串。...但是,如果我什么也没通过呢?而且...什么是“一无所有”?
让我们从Python中的一个空字符串开始:
print ( < TodoForm add_url = { "" } / > ) < form method =" post " action ="" > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >好的,它有效,我们想要一个字符串,我们有一个字符串。
现在,让我们直接传递此空字符串:
print ( < TodoForm add_url = "" / > ) < form method =" post " action ="" > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >它仍然有效,因为它仍然是一个字符串。让我们删除报价,看看。
print ( < TodoForm add_url = / > ) mixt . exceptions . GeneralParserError : < mixt parser > Unclosed Tags : < TodoForm >嗯,这不是有效的HTML。因此,让我们删除= :
print ( < TodoForm add_url / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > )什么?是的,请考虑required HTML5属性, checked ...它们只需要作为一个属性而没有价值True存在。因此,当属性没有任何价值时,它是布尔值,这是True 。
除了不通过一个值之外,其他两种方式在html5中有效,对于by by True :
required=""required="required"为了方便您,我们添加了另一种方法:
True (案例无关紧要),如python或字符串: required=True , required={True} , required="true"和它的对应物,通过False :
False required={False}案例无关紧要) required="false"如python或字符串: required=False 可以为布尔属性。这不是我们的情况。我们可以做的最后一件事是完全不设置属性:
print ( < TodoForm / > )
# this is the same: ``print(<TodoForm add_url=NotProvided />)```
# (``NotProvided`` must be imported from ``mixt``) mixt . exceptions . UnsetPropError : < TodoForm > . add_url : prop is not set这是可以理解的:我们尝试访问未设置的道具,当然我们不能使用它。
但是,如果我们不访问它怎么办?如果我们不打印组件,则不会呈现:
< TodoForm / > < TodoForm at 0x7fbd18ea5630 >因此,我们可以创建一个实例,但是它将在渲染时间失败。但是有一种方法可以防止这种情况。
默认情况下,所有属性都是可选的。而且,您不必使用Python typing模块中的Optional类型为此,每种道具都会很麻烦。
取而代之的是, mixt提供了Required类型,您使用的方式与Optionnal相同。
from mixt import Element , Required , html
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
def render ( self , context ):
# ...因此,我们只是说我们想要一根绳子,并且需要它。
让我们再次尝试在没有道具的情况下创建它:
< TodoForm / > mixt . exceptions . RequiredPropError : < TodoForm > . add_url : is a required prop but is not set现在,我们的计划较早提出了例外。
要查看道具中的其他可能性,让我们添加一个新的,以更改文本标签。但是我们不想使其要求,而是具有默认值。
为此,这就像在PropTypes类中为道具添加价值一样容易:
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
label : str = 'New Todo'
def render ( self , context ):
return
< form method = "post" action = { self . add_url } >
< label > { self . label }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >现在让我们在不传递道具的情况下尝试:
print ( < TodoForm add_url = "/todo/add" / > ) < form method =" post " action ="" > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >如果我们通过一个:
print ( < TodoForm add_url = "/todo/add" label = "Thing to do" / > ) < form method =" post " action =" /todo/add " > < label > Thing to do: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >它可以按预期工作。
请注意,在拥有Required道具时,您不能给出默认值。这是没有意义的,因此在构建class的同时,请尽快进行检查:
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
label : Required [ str ] = 'New Todo' mixt . exceptions . PropTypeRequiredError : < TodoForm > . label : a prop with a default value cannot be required当然,默认值必须与该类型匹配!
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
label : str = { 'label' : 'foo' } mixt . exceptions . InvalidPropValueError :
< TodoForm > . label : `{'label': 'foo'}` is not a valid value for this prop ( type : < class 'dict' > , expected : < class 'str' > )我们要在组件中要做的另一件事是让它构造标签,将其传递为“类型”的待办事项,但限制了选择。为此,我们可以使用Choices类型:
from mixt import Choices , Element , Required , html
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
type : Choices = [ 'todo' , 'thing' ]
def render ( self , context ):
return
< form method = "post" action = { self . add_url } >
< label > New { self . type }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >让我们尝试一下:
print ( < TodoForm add_url = "/todo/add" type = "todo" / > )
print ( < TodoForm add_url = "/todo/add" type = "thing" / > ) < form method =" post " action =" /todo/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >
< form method =" post " action =" /todo/add " > < label > New thing: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >如果我们试图通过可用选择以外的其他东西怎么办?它失败了,正如预期的那样:
print ( < TodoForm add_url = "/todo/add" type = "stuff" / > ) mixt . exceptions . InvalidPropChoiceError : < TodoForm > . type : `stuff` is not a valid choice for this prop ( must be in [ 'todo' , 'thing' ])但是,也许我们不想传递它并使用默认值。结果是什么?
print ( < TodoForm add_url = "/todo/add" / > ) mixt . exceptions . UnsetPropError : < TodoForm > . type : prop is not set因此,我们必须根据需要标记type道具:
class PropTypes :
add_url : Required [ str ]
type : Required [ Choices ] = [ 'todo' , 'thing' ]因此,如果我们不通过它,它会较早失败:
print ( < TodoForm add_url = "/todo/add" / > ) mixt . exceptions . RequiredPropError : < TodoForm > . type : is a required prop but is not set但这不是我们想要的,我们想要一个默认值。
实际上,您注意到,对于Choices以外的其他类型,在PropTypes中设置一个值可为我们提供默认值。但是对于Choices ,它是不同的,因为值是选择列表。
为此,我们有DefaultChoices :它与Choices相同,但将列表中的第一个条目作为默认值。当然,与具有默认的其他类型一样, Required它。
让我们尝试一下:
from mixt import DefaultChoices , Element , Required , html
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
type : DefaultChoices = [ 'todo' , 'thing' ] print ( < TodoForm add_url = "/todo/add" / > ) < form method =" post " action =" /todo/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >它可以按预期工作。
在此之前,我们使用了简单的类型,但是您可以使用更复杂的类型。
因此,例如,我们将使add_url Prop接受一个可以根据type Prop为我们计算URL的函数。但是我们也想允许字符串,并具有默认值。
我们可以通过打字来做到这一点。我们的功能将采用字符串, type ,并将返回字符串,即URL。
因此,语法是Callable[[str], str] ,我们使用Union来接受类型Callable或str的事物:
from typing import Union , Callable
from mixt import DefaultChoices , Element , Required , html
class TodoForm ( Element ):
class PropTypes :
add_url : Union [ Callable [[ str ], str ], str ] = "/todo/add"
type : DefaultChoices = [ 'todo' , 'thing' ]
def render ( self , context ):
if callable ( self . add_url ):
add_url = self . add_url ( self . type )
else :
add_url = self . add_url
return
< form method = "post" action = { add_url } >
< label > New { self . type }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >首先,让我们在没有add_url prop的情况下尝试一下,因为我们有一个默认值:
print ( < TodoForm / > ) < form method =" post " action =" /todo/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >如果我们通过一个字符串,它也应该有效:
print ( < TodoForm add_url = "/todolist/add" / > ) < form method =" post " action =" /todolist/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >现在我们可以通过一个函数:
def make_url ( type ):
return f"/ { type } /add"
print ( < TodoForm add_url = { make_url } / > ) mixt . exceptions . InvalidPropValueError : < TodoForm > . add_url :
`<function make_url at 0x7fe2ae87be18>` is not a valid value for this prop ( type : < class 'function' > , expected : Union [ Callable [[ str ], str ], str ])哦?为什么?我传递了一个接受字符串作为参数并返回字符串的函数。是的,但是不要忘记检查类型!因此,我们必须为我们的功能添加类型:
def make_url ( type : str ) -> str :
return f"/ { type } /add"
print ( < TodoForm add_url = { make_url } / > ) < form method =" post " action =" /todo/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >如果我们通过另一种类型,则URL应该相应地更改:
print ( < TodoForm add_url = { make_url } type = "thing" / > ) < form method =" post " action =" /thing/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >我们甚至可以使此功能成为我们的道具的默认值:
from typing import Union , Callable
from mixt import DefaultChoices , Element , Required , html
def make_url ( type : str ) -> str :
return f"/ { type } /add"
class TodoForm ( Element ):
class PropTypes :
add_url : Union [ Callable [[ str ], str ], str ] = make_url
type : DefaultChoices = [ 'todo' , 'thing' ]
def render ( self , context ):
if callable ( self . add_url ):
add_url = self . add_url ( self . type )
else :
add_url = self . add_url
return
< form method = "post" action = { add_url } >
< label > New { self . type }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form > print ( < TodoForm / > ) < form method =" post " action =" /todo/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >现在,您可能会开始想知道... Python打字很麻烦,验证可能会带走我们一些宝贵的时间。
让我回答:
默认情况下, mixt以“ Dev-Mode”运行。在DEV模式中,将Props传递给组件时会进行验证。当您不在“ Dev Mode”中时,验证将跳过。因此,在生产中,您可以停用Dev模式(我们将在一分钟内了解如何),并非常快地通过道具:
Choices道具是否确实是render方法中发生了可以理解的奇怪事情。但是您可能会说验证很重要。的确。但是,当然,您的代码已被测试完全涵盖,您在开发模式下运行,因此在生产中,您不需要此验证!并请注意, NODE_ENV=production是React的工作方式。
如何更改开发模式?我们不执行任何环境变量,但我们提出了一些功能。由您打电话给他们:
from mixt import set_dev_mode , unset_dev_mode , override_dev_mode , in_dev_mode
# by default, dev-mode is active
assert in_dev_mode ()
# you can unset the dev-mode
unset_dev_mode ()
assert not in_dev_mode ()
# and set it back
set_dev_mode ()
assert in_dev_mode ()
# set_dev_mode can take a boolean
set_dev_mode ( False )
assert not in_dev_mode ()
set_dev_mode ( True )
assert in_dev_mode ()
# and we have a context manager to override for a block
with override_dev_mode ( False ):
assert not in_dev_mode ()
with override_dev_mode ( True ):
assert in_dev_mode ()
assert not in_dev_mode ()
assert in_dev_mode ()因此,让我们尝试使用type道具。记住,看起来像:
type : DefaultChoices = [ 'todo' , 'thing' ]我们尝试通过另一个选择,首先是Dev Mode:
with override_dev_mode ( True ):
print ( < TodoForm type = "stuff" / > ) mixt . exceptions . InvalidPropChoiceError : < TodoForm > . type : `stuff` is not a valid choice for this prop ( must be in [ 'todo' , 'thing' ])它是预期的。
现在通过停用Dev模式:
with override_dev_mode ( False ):
print ( < TodoForm type = "stuff" / > ) < form method =" post " action =" /stuff/add " > < label > New stuff: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >它有效,我们有一种不在我们选择的待办事项中,并且也在采取action中。这是您的测试的工作,以确保您永远不会通过无效的道具,因此您可以对生产和停用Dev Mode充满信心。
现在我们有了我们的形式。我们的TODO List应用程序还需要哪些其他组件?
当然,我们需要一种显示待办条目的方法。
但是什么是待办事项?让我们创建一个基本的TodoObject :
class TodoObject :
def __init__ ( self , text ):
self . text = text这是一个非常简单的课程,但是您当然可以使用所需的东西。它可能是Django型号,等等...
因此,我们可以创建我们的Todo组件,使其接受所需的TodoObject作为prop:
class Todo ( Element ):
class PropTypes :
todo : Required [ TodoObject ]
def render ( self , context ):
return < li > { self . todo . text } < / li >我们可以使用它:
todo = TodoObject ( "foo" )
print ( < Todo todo = { todo } / > ) < li > foo </ li >现在,我们想列出戒酒列表。让我们创建一个可以接受作为TodoObject的列表的TodoList组件。
但是,与我们的其他两个组件有什么不同,这些组件仅在其render方法中使用HTML标签,这是现在我们将一个组件封装在另一个组件中。让我们看看如何。
class TodoList ( Element ):
class PropTypes :
todos : Required [ List [ TodoObject ]]
def render ( self , context ):
return < ul > {[ < Todo todo = { todo } / > for todo in self . todos ]} < / ul >是的,这很简单:您将使用<Todo...>用于Todo组件,就像使用html标签一样。唯一的区别是,对于HTML标签,您无需直接导入它们(简单的mixt导入html ),并且按照惯例,我们将其写入较低案例。对于普通组件,您必须导入它们(您仍然可以from mylib import components和<components.MyComponent ...> )并使用确切的情况。
请注意我们如何需要列表,并通过卷曲括号中的列表概念将其传递到<ul>中。
如果愿意,您可以做不同的事情。
就像将列表理解与HTML分开:
def render ( self , context ):
todos = [
< Todo todo = { todo } / >
for todo
in self . todos
]
return < ul > { todos } < / ul >或以专用方法(这对于测试很有用):
def render_todos ( self , todos ):
return [
< Todo todo = { todo } / >
for todo
in todos
]
def render ( self , context ):
return < ul > { self . render_todos ( self . todos )} < / ul >取决于您:最后只是Python。
让我们看看此组件呈现的内容:
todos = [ TodoObject ( "foo" ), TodoObject ( "bar" ), TodoObject ( "baz" )]
print ( < TodoList todos = { todos } / > ) < ul > < li > foo </ li > < li > bar </ li > < li > baz </ li > </ ul >最后,我们拥有封装表单和列表的TodoApp组件:
class TodoApp ( Element ):
class PropTypes :
todos : Required [ List [ TodoObject ]]
type : DefaultChoices = [ 'todo' , 'thing' ]
def render ( self , context ):
return
< div >
< h1 > The "{self.type}" list < / h1 >
< TodoForm type = { self . type } / >
< TodoList todos = { self . todos } / >
< / div > todos = [ TodoObject ( "foo" ), TodoObject ( "bar" ), TodoObject ( "baz" )]
print ( < TodoList todos = { todos } type = "thing" / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > < li > bar </ li > < li > baz </ li > </ ul > </ div >让我们将此HTML传递给HTML美化机:
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul >
< li > foo </ li >
< li > bar </ li >
< li > baz </ li >
</ ul >
</ div >就是这样,我们有我们的待办事项应用程序!要在页面中使用它,只需创建一个将渲染HTML基本标记并将TodoApp组件集成到其中的组件即可。您甚至不需要组件:
todos = [ TodoObject ( "foo" ), TodoObject ( "bar" ), TodoObject ( "baz" )]
print (
< html >
< body >
< TodoApp todos = { todos } type = "thing" / >
< / body >
< / html >
)美化的输出将是:
< html >
< body >
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul >
< li > foo </ li >
< li > bar </ li >
< li > baz </ li >
</ ul >
</ div >
</ body >
</ html >我们有一个通用的待办事项列表,但是遵循可用类型的Todo,我们可能希望拥有一个“ todo list”和“东西列表”。
我们已经有了待办事项列表,因为默认情况下我们的TodoApp具有一种todo 。
因此,让我们创建一个ThingApp 。
这样做的第一种方法是从我们的TodoApp中继承。但是,通过继承我们无法从父母那里删除道具(这不是真的,我们稍后会看到),因此我们默认情况下仍然具有type道具。但是我们不想接受“事物”以外的任何其他东西。因此,我们可以重新定义这样的type道具:
class ThingApp ( TodoApp ):
class PropTypes :
type : DefaultChoices = [ 'thing' ]让我们使用此组件:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form method =" post " action =" /thing/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form > < ul > < li > foo </ li > </ ul > </ div >如果我们试图将“ TODO”用于type道具,它将无法使用:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} type = "todo" / > ) mixt . exceptions . InvalidPropChoiceError :
< ThingApp > . type : `todo` is not a valid choice for this prop ( must be in [ 'thing' ])但是,能够通过一种类型仍然很奇怪。
让我们尝试另一种方式:父组件。一个与孩子一起做事并将其归还的组成部分。我们在这里想要的是一个组件,该TodoApp将返回被迫“事物”的type道具。
让我们这样做
class ThingApp ( Element ):
class PropTypes :
todos : Required [ List [ TodoObject ]]
def render ( self , context ):
return < TodoApp todos = { self . todos } type = "thing" / > print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form method =" post " action =" /thing/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form > < ul > < li > foo </ li > </ ul > </ div >它有效,这一次,我们无法通过type prop:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) mixt . exceptions . InvalidPropNameError : < ThingApp > . type : is not an allowed prop 请注意,我们必须如何定义todos道具的类型。既在TodoApp and TodoThing 。
有很多方法可以处理。
第一个是忽略ThingApp中的类型,因为它将在TodoApp中进行检查。因此,我们将使用Any类型:
from typing import Any
#...
class ThingApp ( Element ):
class PropTypes :
todos : Any
#...让我们尝试使用有效的Todos列表:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > </ ul > </ div >但是,如果我们通过其他事情怎么办?
print ( < ThingApp todos = "foo, bar" / > ) mixt . exceptions . InvalidPropValueError :
< TodoApp > . todos : `foo, bar` is not a valid value for this prop ( type : < class 'str' > , expected : List [ TodoObject ])它可以按预期工作,但是在TodoApp级别报告了错误,这是完全正常的。
另一种方法是在更高级别上定义类型:
TodoObjects = Required [ List [ TodoObject ]]
class TodoApp ( Element ):
class PropTypes :
todos : TodoObjects
# ...
class ThingApp ( Element ):
class PropTypes :
todos : TodoObjects
# ...现在,如果我们通过其他内容,我们将有正确级别报告的错误:
print ( < ThingApp todos = "foo, bar" / > ) mixt . exceptions . InvalidPropValueError :
< TodoThing > . todos : `foo, bar` is not a valid value for this prop ( type : < class 'str' > , expected : List [ TodoObject ])但是,如果您不能或不想这样做,则可以在TodoApp et中保持定义的类型使用组件的prop_type类方法来获取道具的类型:
class ThingApp ( Element ):
class PropTypes :
todos : TodoApp . prop_type ( "todos" )
# ...但是,对于ThingApp或TodoApp提出错误真的很重要吗?因为最后, TodoApp必须进行检查。
因此,这应该是一种以更通用的方式进行此操作的方法。
我们早些时候看到组件可以是渲染组件的单个函数。它只需要返回组件即HTML标签即可。与类组件的不同之处在于,没有PropTypes ,因此没有验证。但是...这正是我们需要的。
我们希望我们的ThingApp接受一些道具( todos Prop),并返回带有特定type道具的TodoApp 。
所以我们可以做:
def ThingApp ( todos ):
return < TodoApp type = "thing" todos = { todos } / >在这里,我们可以看到我们无法将type传递给ThingsApp ,这不是一个有效的参数。
让我们尝试一下:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > </ ul > </ div >在这里,我们只有一个道具可以通过,所以很容易。但是想象一下,如果我们有很多。我们可以使用{**props}语法:
def ThingApp ( ** props ):
return < TodoApp type = "thing" { ** props } / >您可以使用更少的字符(如果很重要):
ThingApp = lambda ** props : < TodoApp type = "thing" { ** props } / >这两个fonctions的行为完全相同。
而且您无法通过type道具,因为它将是一个python错误,因为它将两次传递给TodoApp :
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} type = "thing" / > ) TypeError : BaseMetaclass object got multiple values for keyword argument 'type' (是的,它谈论的是BaseMetaclass ,这是创建我们的组件类的元素)
TodoApp将验证任何其他错误的道具:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} foo = "bar" / > ) mixt . exceptions . InvalidPropNameError : < TodoApp > . foo : is not an allowed prop考虑到这一点,我们本可以创建一个通用的功能,迫使任何组件的类型接受type prop:
Thingify = lambda component , ** props : < component type = "thing" { ** props } / > print ( < Thingify component = { TodoApp } todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > </ ul > </ div >渲染的组件是TodoApp , type prop是“事物”,而其他道具(此处仅是todos )正确传递。
现在将此概念扩展到更通用的情况:“高阶组件”。在React A中,“高阶组件”是“具有组件并返回新组件的函数”。
这个想法是:
EnhancedComponent = higherOrderComponent ( WrappedComponent )这样做的一种经典方法是返回新的组件类:
def higherOrderComponent ( WrappedComponent ):
class HOC ( Element ):
__display_name__ = f"higherOrderComponent( { WrappedComponent . __display_name__ } )"
class PropTypes ( WrappedComponent . PropTypes ):
pass
def render ( self , context ):
return < WrappedComponent { ** self . props } > { self . childre ()} < / WrappedComponent >
return HOC请注意,我们如何将PropTypes类设置为从包装的组件之一继承,以及如何将所有道具与孩子一起传递给包裹的组件。使用返回的组件将接受与包裹的相同类型的相同的道具。
还要注意__display_name__ 。它将在例外使用,以便您现在允许您提出它的组件。在这里,不强迫它,它将被设置为HOC ,这无济于事。相反,我们表明它是传递组件的转换版本。
这是一个无用的函数。
在我们的示例中,我们可以这样做:
def thingify ( WrappedComponent ):
class HOC ( Element ):
__display_name__ = f"thingify( { WrappedComponent . __display_name__ } )"
class PropTypes ( WrappedComponent . PropTypes ):
__exclude__ = { 'type' }
def render ( self , context ):
return < WrappedComponent type = "thing" { ** self . props } > { self . children ()} < / WrappedComponent >
return HOC这里有两个重要的事情:
__exclude__ = {'type'}从我们从WrappedComponent.PropTypes继承的类型prop中删除type prop。因此,返回的组件将期望与包裹的组件完全相同,但type除外。{self.children()} ,因为即使我们实际上知道我们将要包装, TodoApp组件不会带孩子(但可以对他们无能为力),我们也不能事先说出这将是这种情况,而且此较高的组件也不会用来将其他组件用来包裹其他组件,而不是TodoApp组件。因此,最好始终做到这一点。现在,我们可以创建我们的ThingApp :
ThingApp = thingify ( TodoApp )并使用它:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > </ ul > </ div >如果我们尝试通过类型:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} type = "thing" / > ) mixt . exceptions . InvalidPropNameError : < thingify ( TodoApp ) > . type : is not an allowed prop因此,按计划,我们无法通过类型。并注意如何使用__display_name__ 。
让我们考虑一下这有多强大。
假设我们要保留我们的TodoApp TodoObject 。但是我们想从“来源”中获取它们。
我们甚至可以以通用的方式将其直接编写这个新的高阶组件:
def from_data_source ( WrappedComponent , prop_name , get_source ):
class HOC ( Element ):
__display_name__ = f"from_data_source( { WrappedComponent . __display_name__ } )"
class PropTypes ( WrappedComponent . PropTypes ):
__exclude__ = { prop_name }
def render ( self , context ):
props = self . props . copy ()
props [ prop_name ] = get_source ( props , context )
return < WrappedComponent { ** props } > { self . children ()} < / WrappedComponent >
return HOC这次,除WrappedComponent外, from_data_source函数还需要两个参数:
prop_name :这是包装组件的道具的名称,以填充一些数据get_source :这是一个函数,以获取数据看看我们如何从包装的组件中继承了PropTypes以及如何排除prop_name 。因此,我们没有(也不能)将数据传递给我们的新组件。
然后,在render中,我们设置了一个道具,以通过呼叫get_source的结果来WrappedComponent 。
因此,让我们编写一个非常简单的功能(这可能是一个复杂的功能,带有缓存,过滤...),该功能采用道具和上下文,并返回一些数据:
def get_todos ( props , context ):
# here it could be a call to a database
return [
TodoObject ( "fooooo" ),
TodoObject ( "baaaar" ),
]我们可以组成我们的组件:
SourcedTodoApp = from_data_source ( TodoApp , 'todos' , get_todos )
ThingApp = thingify ( SourcedTodoApp )并运行它:
print ( < ThingApp / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > fooooo </ li > < li > baaaar </ li > </ ul > </ div >它可以按预期工作,并且仅在需要渲染组件时获取数据。
因此,我们有一个待办事项列表,可以从外部来源获取数据。但是我们可能希望数据根据用户不同。
我们可以做的是在主要层面上,获取我们的用户并将其传递给每个组件,以确保每个组件能够在用户中登录当前。
不是很麻烦吗?
解决此用例是mixt提供的Context概念的确切目的。当然,这是受反应中背景概念的启发。
正如他们所说:
上下文旨在共享可以将React组件树(例如当前身份验证的用户,主题或首选语言)视为“全局”的数据。
创建上下文就像创建组件一样简单,只是它将从BaseContext继承,并且不需要render方法(它将渲染其子女)。
并且需要一个PropTypes类,该类别定义了上下文将接受并传递树的数据类型。
因此,让我们创建将保留已验证用户的ID的上下文。
from mixt import BaseContext
class UserContext ( BaseContext ):
class PropTypes :
authenticated_user_id : Required [ int ]现在,我们想更新我们的get_todos方法,以将authenticated_user_id计入考虑。
请记住,我们通过了道具和上下文。上下文在这里将很有用:
def get_todos ( props , context ):
return {
1 :[
TodoObject ( "1-1" ),
TodoObject ( "1-2" ),
],
2 : [
TodoObject ( "2-1" ),
TodoObject ( "2-2" ),
]
}[ context . authenticated_user_id ]现在,我们可以通过上下文渲染应用程序:
print (
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / UserContext >
) < div > < h1 > The "thing" list < / h1 > < form > ... < / form > < ul > < li > 1 - 1 < / li > < li > 1 - 2 < / li > < / ul > < / div >我们可以看到用户1的TODO条目。
让我们与用户2:
print (
< UserContext authenticated_user_id = 2 >
< ThingApp / >
< / UserContext >
) < div > < h1 > The "thing" list < / h1 > < form > ... < / form > < ul > < li > 2 - 1 < / li > < li > 2 - 2 < / li > < / ul > < / div >我们可以看到用户2的TODO条目。
在这种情况下,当然,我们本可以将用户ID作为道具传递。但是,想象一下,Todo应用在组件树中深处,以这种方式传递要容易得多。
但是正如React文档中所述:
不要使用上下文只是为了避免将道具传递给几个级别。坚持需要在多个级别的许多组件中访问相同数据的情况。
当没有上下文时, render方法的context参数将设置为EmptyContext而不是None 。因此,您可以直接使用has_prop方法检查是否可以通过上下文可用。
让我们更新get_todos函数,如果没有身份验证的用户,则返回待命对象的空列表。
def get_todos ( props , context ):
if not context . has_prop ( 'authenticated_user_id' ) or not context . authenticated_user_id :
return []
return {
1 :[
TodoObject ( "1-1" ),
TodoObject ( "1-2" ),
],
2 : [
TodoObject ( "2-1" ),
TodoObject ( "2-2" ),
]
}[ context . authenticated_user_id ]让我们尝试一下:
print ( < ThingApp / > ) < div > < h1 > The "thing" list < / h1 > < form > ... < / form > < ul > < / ul > < / div >它仍然在上下文中与用户一起使用:
print (
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / UserContext >
) < div > < h1 > The "thing" list < / h1 > < form > ... < / form > < ul > < li > 1 - 1 < / li > < li > 1 - 2 < / li > < / ul > < / div >关于上下文的重要说明:您可以拥有许多上下文!但是在许多情况下定义相同的道具可能会导致不确定的行为。
每个人都喜欢精美的设计,也许是一些互动。
它很容易可行:我们生成HTML,HTML可以包含一些CS和JS。
让我们首先添加一些交互:在TodoForm中添加项目时,让我们将其添加到列表中。
首先,我们添加了我们的TodoForm组件一种render_javascript方法,该方法将托管我们(不好,我们可以做得更好,但不是重点)JavaScript:
class TodoForm ( Element ):
# ...
def render_javascript ( self , context ):
return html . Raw ( """
function on_todo_add_submit(form) {
var text = form.todo.value;
alert(text);
}
""" )要开始,我们只显示新的TODO文本。
现在更新我们的render方法以返回此JavaScript(请注意,使用render_javascript方法仅是为了单独的关注点,它可能直接在render方法中。
class TodoForm ( Element ):
# ...
def render ( self , context ):
# ...
return
< Fragment >
< script > { self . render_javascript ( context )} < / script >
< form method = "post" action = { add_url } onsubmit = "return on_todo_add_submit(this);" >
< label > New { self . type }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >
< / Fragment >注意Fragment标签。这是一种封装许多要返回的元素的方法,例如React中。这可能是一个简单的列表,但最终有昏迷:
return [
< script > ... < / script > ,
< form >
...
< / form >
]现在,我们要在列表中添加一个项目。做到这一点不是TodoForm的角色,而是列表。因此,我们将在TodoList组件中添加一些JS:一个函数,该函数会使用一些文本并创建一个新的条目。
至于TodoForm ,我们添加了一个render_javascript方法(仍然很糟糕)javascript:
class TodoList ( Element ):
# ...
def render_javascript ( self , context ):
todo_placeholder = < Todo todo = { TodoObject ( text = 'placeholder' )} / >
return html . Raw ( """
TODO_TEMPLATE = "%s";
function add_todo(text) {
var html = TODO_TEMPLATE.replace("placeholder", text);
var ul = document.querySelector('#todo-items');
ul.innerHTML = html + ul.innerHTML;
}
""" % ( todo_placeholder ))然后我们更新render方法以在JavaScript中使用的ul标签添加<script>标签和id :
class TodoList ( Element ):
# ...
def render ( self , context ):
return
< Fragment >
< script > { self . render_javascript ( context )} < / script >
< ul id = "todo-items" > {[ < Todo todo = { todo } / > for todo in self . todos ]} < / ul >
< / Fragment >现在,我们可以更新TodoForm组件的render_javascript方法,以使用我们的新add_toto javascript函数:
class TodoForm ( Element ):
# ...
def render_javascript ( self , context ):
return html . Raw ( """
function on_todo_add_submit(form) {
var text = form.todo.value;
add_todo(text);
}
""" )仅此而已。实际上没什么特别的。
但是,让我们看一下OU TodoApp的输出:
print (
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / User >
)美化的输出是:
< div >
< h1 > The "thing" list </ h1 >
< script >
function on_todo_add_submit ( form ) {
var text = form . todo . value ;
add_todo ( text ) ;
}
</ script >
< form method =" post " action =" /thing/add " onsubmit =" return on_todo_add_submit(this); " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< script >
TODO_TEMPLATE = "<li>placeholder</li>" ;
function add_todo ( text ) {
var html = TODO_TEMPLATE . replace ( "placeholder" , text ) ;
var ul = document . querySelector ( '#todo-items' ) ;
ul . innerHTML = html + ul . innerHTML ;
}
</ script >
< ul id =" todo-items " >
< li > 1-1 </ li >
< li > 1-2 </ li >
</ ul >
</ div >因此,我们有许多script标签。只有一个很棒。
mixt带来了一种“收集”将它们放置在其他地方的方法的方法。我们可以使用两个简单的收藏家,用作组件: JSCollector和CSSCollector 。
这些成分收集了孩子的一部分树。
第一种方法是使用收集器Collect标签。
首先,让我们更改我们的主要呼吁:
from mixt import JSCollector
print (
< JSCollector render_position = "after" >
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / User >
< / JSCollector >
)这将收集所有JSCollector.Collect标签的内容。
让我们更新我们的TodoForm ,并通过JSCollector.Collect替换我们的script标签。收集标签:
class TodoForm ( Element ):
# ...
def render ( self , context ):
if callable ( self . add_url ):
add_url = self . add_url ( self . type )
else :
add_url = self . add_url
return
< JSCollector . Collect > { self . render_javascript ( context )} < / JSCollector . Collect >
< form method = "post" action = { add_url } onsubmit = "return on_todo_add_submit(this);" >
< label > New { self . type }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >
< / Fragment >我们可以对TodoList做同样的事情:
class TodoList ( Element ):
# ...
def render ( self , context ):
return
< Fragment >
< JSCollector . Collect > { self . render_javascript ( context )} < / JSCollector . Collect >
< ul id = "todo-items" > {[ < Todo todo = { todo } / > for todo in self . todos ]} < / ul >
< / Fragment >现在,让我们运行更新的代码:
print (
< JSCollector render_position = "after" >
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / User >
< / JSCollector >
)美化的输出是:
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " onsubmit =" return on_todo_add_submit(this); " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul id =" todo-items " >
< li > 1-1 </ li >
< li > 1-2 </ li >
</ ul >
</ div >
< script type =" text/javascript " >
function on_todo_add_submit ( form ) {
var text = form . todo . value ;
add_todo ( text ) ;
}
TODO_TEMPLATE = "<li>placeholder</li>" ;
function add_todo ( text ) {
var html = TODO_TEMPLATE . replace ( "placeholder" , text ) ;
var ul = document . querySelector ( '#todo-items' ) ;
ul . innerHTML = html + ul . innerHTML ;
}
</ script >如您所见,最后所有脚本都在一个script标签中。更确切地说,在JSCollector标签的末尾,因为我们使用render_position="after" 。另一种可能性是render_position="before"将其放置在JSCollector标签的位置。
所有这些工作都以CSSCollector标签的方式完全相同,其中内容放在<style type="text/css> tag中。
由于在HTML世界中使用JS/CSS非常普遍,因此我们添加了一些糖,以使所有这些更容易做到。
如果您有render_js方法,则JSCollector将自动收集此方法的结果。对于CSSSelector和render_css方法相同。
这样,不需要JSCollector.Collect标签。
为了在我们的示例中进行这项工作,以TodoForm和TodoList方式:
JSCollector.Collect标签Fragment标签render_javascript方法重命名为render_js 。render_js中html.Raw的呼叫,因为当收集器调用render_js本身时不需要它:如果输出是字符串,则将其视为“原始”一个这样,我们的结果完全相同。
它现在起作用,因为我们只有一个带有render_js方法的孩子的实例。
但是,如果我们有很多孩子,每个孩子将被要求使用这种方法。如果事实,它应仅包含非常特定的代码。
要为组件类服务JS/CSS一次,我们必须使用render_js_global或render_css_global (预计将是classmethod )
在收集render_js方法之前,将第一次收集它,并且仅第一次找到一个实例。
因此,在这里,我们可以将render_js更改为render_js_global ,用@classmethod对其进行装饰,并且它仍然可以工作。
现在,我们能够重新组合JavaScript或样式。但是,如果我们想将其放在其他地方,例如在head标签或body标签末端,该怎么办?
参考文献可能是可能的。当然,这是与React相同的上下文。
您创建一个参考,将其传递给组件,然后可以在任何地方使用它。
让我们更新我们的主要代码以执行此操作。
首先,我们创建一个参考。
from mixt import Ref
js_ref = Ref ()这将创建一个新对象,该对象将对组件进行引用。在组件中,您无需导入Ref ,并且可以使用js_ref = self.add_ref() ,但是我们不在此处的组件中。
为了保存参考,我们只需将其传递给ref Prop:
< JSCollector ref = { js_ref } > ... < / JSCollector >请注意,我们删除了render_position Prop,因为现在我们不希望将JS放在标签之前或之后,而是在其他地方放置。
要访问REF引用的组件,请使用current属性:
js_collector = js_ref . current当然,这只能在渲染后完成。
我们如何使用它在我们的head中添加script标签。
首先更新我们的HTML以包括经典的html , head和body标签:
return str (
< html >
< head >
< / head >
< body >
< JSCollector ref = { js_ref } >
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / UserContext >
< / JSCollector >
< / body >
< / html >
)在这一点上,我们在输出中没有任何script标签:
< html >
< head > </ head >
< body >
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " onsubmit =" return on_todo_add_submit(this); " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul id =" todo-items " >
< li > 1-1 </ li >
< li > 1-2 </ li >
</ ul >
</ div >
</ body >
</ html >要知道的第一件事:收藏家能够通过render_collected方法来渲染其收集的所有内容。
并记住它已经包含script标签,我们可能想这样做:
# ...
< head >
{ js_ref . current . render_collected ()}
< / head >
# ...但这无效:
AttributeError : 'NoneType' object has no attribute 'render_collected'这是因为我们尝试在渲染时间访问当前值。必须在此之后完成。
为此,我们可以使用mixt的功能:如果在树上添加了一些东西,则在渲染后,在转换为字符串时会调用。
因此,我们可以使用lambda:
# ...
< head >
{ lambda : js_ref . current . render_collected ()}
< / head >
# ...现在它有效:
< html >
< head >
< script type =" text/javascript " >
function on_todo_add_submit ( form ) {
var text = form . todo . value ;
add_todo ( text ) ;
}
TODO_TEMPLATE = "<li>placeholder</li>" ;
function add_todo ( text ) {
var html = TODO_TEMPLATE . replace ( "placeholder" , text ) ;
var ul = document . querySelector ( '#todo-items' ) ;
ul . innerHTML = html + ul . innerHTML ;
}
</ script >
</ head >
< body >
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " onsubmit =" return on_todo_add_submit(this); " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul id =" todo-items " >
< li > 1-1 </ li >
< li > 1-2 </ li >
</ ul >
</ div >
</ body >
</ html >欢呼我们做到了! mixt的所有主要特征都解释了。现在,您可以在自己的项目中使用mixt 。
作为下一步,您可能需要阅读API文档。