直接在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文檔。