เขียนส่วนประกอบ HTML โดยตรงใน Python และคุณมีส่วนผสมที่สวยงาม แต่เป็นที่ถกเถียงกัน
ใช่ การโต้เถียง
หากคุณไม่ชอบให้ละเว้น (แต่คุณสามารถใช้สิ่งนี้ได้โดยไม่ต้องใช้ส่วน HTML-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 ไฟล์ 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 >หากคุณไม่ชอบเขียน HTML ใน Python คุณยังสามารถใช้งานได้:
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 > หากคุณไม่ต้องการใช้สิ่ง HTML-in-Python อย่าเรียกใช้ 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, Forms Simple ...
พูดถึงรูปแบบ ...
ในรายการสิ่งที่ต้องทำเราต้องการเพิ่มสิ่งที่ต้องทำ มันเป็นอินพุตข้อความอย่างง่าย
ดังนั้นเรามาสร้างองค์ประกอบแรกของเรา 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 ลบช่องว่างที่จุดเริ่มต้นและสิ้นสุดของบรรทัด นอกจากนี้ยังลบเส้นเปล่า บรรทัดใหม่ที่อยู่ติดกับแท็กจะถูกลบออก บรรทัดใหม่ที่เกิดขึ้นในช่วงกลางของตัวอักษรสตริงจะควบแน่นเป็นพื้นที่เดียว
ในการเพิ่มพื้นที่หรือสายใหม่คุณสามารถผ่านงูหลามได้ เป็นตัวอย่างให้เพิ่มบรรทัดใหม่ก่อนฉลาก:
#...
< 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 ของคุณสมบัติของคุณสมบัติหรือที่รู้จักกันในชื่อ "อุปกรณ์ประกอบฉาก"
ใน 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 ซึ่งจะต้องเป็นสตริง ( str ) สิ่งนี้ใช้ไวยากรณ์การพิมพ์ Python
และสังเกตว่าเราเปลี่ยนแอตทริบิวต์ action ของแท็ก form ได้อย่างไร ตอนนี้ {self.add_url} แทน "???" -
เมื่อคุณลักษณะถูกส่งผ่านระหว่างวงเล็บปีกกาพวกเขาจะถูกตีความว่าเป็นงูหลามบริสุทธิ์ในเวลาทำงาน ในความเป็นจริงเมื่อตัวแยกวิเคราะห์ mixt จะแปลงไฟล์ทั้งหมดเป็น python บริสุทธิ์ก่อนที่จะปล่อยให้ล่าม Python ทำงานมันจะยังคงเหมือนเดิมเฉพาะ HTML รอบ ๆ เท่านั้นที่จะถูกแปลง ดังนั้นจึงไม่มีการลงโทษที่จะทำเช่นนี้
ดูว่าสิ่งนี้จะเป็นอย่างไรถ้าองค์ประกอบของเราเขียนด้วย Pyran 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
องค์ประกอบ Pure-Python นี้ยังแสดงให้คุณเห็นว่ามันทำงานอย่างไร: อุปกรณ์ประกอบฉากถูกส่งผ่านเป็นอาร์กิวเมนต์ที่มีชื่อไปยังคลาสส่วนประกอบจากนั้นส่วนประกอบนี้จะถูกเรียกโดยผ่านส่วนประกอบเด็กเป็นอาร์กิวเมนต์ตำแหน่งไปยังการโทร:
ComponentClass ( prop1 = "val1" , prop2 = "val2" )(
Children1 (),
Children2 (),
)เด็กคืออะไร? เด็ก ๆ เป็นแท็กในแท็กอื่น ๆ
ใน <div id="divid"><span /><p>foo</p></div> เรามี:
html.Div ที่มี id เสาและเด็กสองคน:html.Span โดยไม่มีลูกhtml.P กับเด็กหนึ่งคน:html.RawHtml พร้อมข้อความ "foo"โปรดทราบว่าคุณสามารถเล่นกับอุปกรณ์ประกอบฉากและเด็ก ๆ รุ่นแรกใน 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 compoments (กำหนดใน mixt.html ) เราสนับสนุนแท็ก HTML ทุกแท็กที่ในขณะที่เขียนจะถูกต้อง (ไม่เลิกใช้) ใน 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' > ) และมันก็เหมือนกันถ้าเราผ่าน True ใน Python
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 ค่าและตัวเลขเหล่านี้ทุกค่าที่ส่งผ่านไปยังแอตทริบิวต์ถือเป็นสตริง แม้ว่าจะไม่มีเครื่องหมายคำพูดเช่นใน HTML ใน HTML5 ซึ่งคำพูดไม่จำเป็นสำหรับสตริงที่ไม่มีอักขระบางตัว (ไม่มีช่องว่างไม่มี / ... )
ในการส่งผ่านอย่างอื่นคุณต้องล้อมรอบค่าในการจัดฟันแบบหยิก (และในกรณีนี้ไม่จำเป็นต้องมีคำพูดรอบ ๆ วงเล็บปีกกา
โอเคตอนนี้เราแน่ใจว่าเรายอมรับเฉพาะสตริง .... แต่ถ้าฉันไม่ผ่านอะไรเลยล่ะ? และ ... "ไม่มีอะไร" คืออะไร?
เริ่มต้นด้วยสตริงเปล่าใน 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 > HUM ใช่นี่ไม่ได้ 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' > ) อะไร? ใช่คิดเกี่ยวกับแอตทริบิวต์ HTML5 ที่ required checked ... พวกเขาจะต้องนำเสนอเป็นแอตทริบิวต์โดยไม่มีค่าที่จะถือว่าเป็น True ดังนั้นเมื่อแอตทริบิวต์ไม่มีค่าใด ๆ มันเป็นบูลีนและเป็น True
นอกเหนือจากการไม่ผ่านค่าแล้ววิธีอื่นอีกสองวิธีที่ใช้ได้ใน HTML5 สำหรับบูลีนโดย True :
required=""required="required"เพื่อความสะดวกของคุณเราได้เพิ่มอีกวิธีหนึ่ง:
True (กรณีไม่สำคัญ) เป็น python หรือเป็นสตริง: required=True , required={True} , required="true" และคู่ของมันจะผ่าน False :
False (กรณีไม่สำคัญ) เป็น python หรือเป็นสตริง: required=False , required={False} , 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 >ดังนั้นเราสามารถสร้างอินสแตนซ์ได้ แต่มันจะล้มเหลวในเวลาแสดงผล แต่มีวิธีป้องกันสิ่งนั้น
โดยค่าเริ่มต้นคุณสมบัติทั้งหมดเป็นทางเลือก และคุณไม่จำเป็นต้องใช้ประเภท Optional จากโมดูล typing Python สำหรับสิ่งนั้นมันจะยุ่งยากที่จะทำสำหรับแต่ละเสา
แต่ 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 ยอมรับฟังก์ชั่นที่จะคำนวณ URL สำหรับเราตาม type Prop แต่เรายังต้องการอนุญาตสตริงและด้วยค่าเริ่มต้น
เราสามารถทำได้ด้วยการพิมพ์ ฟังก์ชั่นของเราจะใช้สตริง type และจะส่งคืนสตริง URL
ดังนั้นไวยากรณ์จึง Callable[[str], str] สำหรับ callable และเราใช้ 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 เนื่องจากเรามีค่าเริ่มต้น:
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-mode อุปกรณ์ประกอบฉากจะได้รับการตรวจสอบเมื่อผ่านไปยังส่วนประกอบ เมื่อคุณไม่ได้อยู่ใน "dev-mode" การตรวจสอบจะถูกข้าม ดังนั้นในการผลิตคุณสามารถปิดการใช้งานโหมด Dev (เราจะดูว่าในหนึ่งนาที) และผ่านอุปกรณ์ประกอบฉากได้อย่างรวดเร็ว:
Choices เป็นจริงหรือไม่ในรายการตัวเลือกrender ของคุณ แต่คุณอาจบอกว่ามันอยู่ในการผลิตว่าการตรวจสอบความถูกต้องเป็นสิ่งสำคัญ อย่างแท้จริง. แต่แน่นอนว่ารหัสของคุณได้รับการคุ้มครองอย่างเต็มที่จากการทดสอบว่าคุณทำงานใน DEV-MODE และในการผลิตคุณไม่จำเป็นต้องมีการตรวจสอบนี้! และโปรดทราบว่ามันเป็นวิธีการทำงานของ React โดยวิธีการด้วย NODE_ENV=production
วิธีเปลี่ยนโหมด dev? เราไม่ได้บังคับใช้ตัวแปรสภาพแวดล้อมใด ๆ แต่เราเสนอฟังก์ชั่นบางอย่าง ขึ้นอยู่กับคุณที่จะโทรหาพวกเขา:
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 () ลองมาลองแบบนี้ด้วย Prop type จำไว้ว่าดูเหมือนว่า:
type : DefaultChoices = [ 'todo' , 'thing' ]เราพยายามผ่านทางเลือกอื่นก่อนในเดฟ-โหมด:
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-mode:
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 จำเป็นต้องใช้เป็นเสา:
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 > ตอนนี้เราต้องการมีรายการ Todos มาสร้างองค์ประกอบ TodoList ที่จะยอมรับเป็นอุปกรณ์ประกอบฉากของ TodoObject
แต่สิ่งที่แตกต่างจากส่วนประกอบอื่น ๆ สองส่วนของเราที่ใช้แท็ก HTML ในวิธี render เท่านั้นตอนนี้เราจะห่อหุ้มส่วนประกอบไว้ในอีก มาดูกันว่า
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 คุณไม่จำเป็นต้องนำเข้าโดยตรง (นำเข้า html ง่าย ๆ จาก mixt ) และโดยการประชุมเราเขียนในกรณีล่าง สำหรับส่วนประกอบปกติคุณต้องนำเข้า (คุณยังสามารถทำได้ 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 >มันขึ้นอยู่กับคุณ: ในตอนท้ายมันเป็นแค่งูหลาม
มาดูกันว่าองค์ประกอบนี้แสดงผลอะไร:
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 >เรามีรายการสิ่งที่ต้องทำทั่วไป แต่ตามประเภทของสิ่งที่ต้องทำเราอาจต้องการ "สิ่งที่ต้องทำ" และ "รายการ"
เรามีรายการสิ่งที่ต้องทำอยู่แล้วเพราะ TodoApp ของเรามีประเภทของ todo ตามค่าเริ่มต้น
ดังนั้นเรามาสร้าง ThingApp
วิธีแรกในการทำเช่นนี้คือการสืบทอดจาก TodoApp ของเรา แต่โดยการสืบทอดเราไม่สามารถลบอุปกรณ์ประกอบฉากออกจากผู้ปกครองได้ (มันไม่เป็นความจริงเราจะเห็นสิ่งนี้ในภายหลัง) ดังนั้นเรายังคงมี type Prop ตามค่าเริ่มต้น แต่เราไม่ต้องการที่จะยอมรับสิ่งอื่นนอกจาก "สิ่ง" ดังนั้นเราจึงสามารถกำหนด 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 prop ที่ถูกบังคับให้ "สิ่ง"
มาทำสิ่งนี้กันเถอะ
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 > มันใช้งานได้และในเวลานี้เราไม่สามารถผ่าน prop type :
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) mixt . exceptions . InvalidPropNameError : < ThingApp > . type : is not an allowed prop สังเกตว่าเราต้องกำหนดประเภทสำหรับอุปกรณ์ประกอบฉาก todos ได้อย่างไร ทั้งใน TodoApp และ 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 และใช้วิธีการคลาส prop_type ของส่วนประกอบเพื่อรับประเภทของเสา:
class ThingApp ( Element ):
class PropTypes :
todos : TodoApp . prop_type ( "todos" )
# ... แต่มันสำคัญหรือไม่ที่จะมีข้อผิดพลาดเกิดขึ้นสำหรับ ThingApp หรือ TodoApp ? เพราะในตอนท้ายมันเป็น TodoApp จริงๆที่ต้องทำการตรวจสอบ
ดังนั้นนี่ควรเป็นวิธีที่จะทำสิ่งนี้ในวิธีทั่วไปมากขึ้น ..
เราเห็นก่อนหน้านี้ว่าส่วนประกอบสามารถเป็นฟังก์ชันเดียวในการแสดงส่วนประกอบ มันต้องส่งคืนส่วนประกอบแท็ก HTML ความแตกต่างอย่างหนึ่งกับส่วนประกอบของคลาสคือไม่มี PropTypes ดังนั้นจึงไม่มีการตรวจสอบความถูกต้อง แต่ ... มันคือสิ่งที่เราต้องการ
เราต้องการให้ ThingApp ของเรายอมรับอุปกรณ์ประกอบฉากบางอย่าง (เสา todos ) และส่งคืน TodoApp ด้วยเสา type เฉพาะ
ดังนั้นเราจึงทำได้:
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 ทั้งสองนี้ทำตัวเหมือนกัน
และคุณไม่สามารถผ่าน Prop type ได้เพราะมันจะเป็นข้อผิดพลาดของ Python เพราะมันจะถูกส่งผ่านสองครั้งไปยัง TodoApp :
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} type = "thing" / > ) TypeError : BaseMetaclass object got multiple values for keyword argument 'type' (ใช่มันพูดถึง BaseMetaclass ซึ่งเป็น metaclass ที่สร้างคลาสส่วนประกอบของเรา)
และอุปกรณ์ประกอบฉากผิดอื่น ๆ จะได้รับการตรวจสอบโดย TodoApp :
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} foo = "bar" / > ) mixt . exceptions . InvalidPropNameError : < TodoApp > . foo : is not an allowed prop ด้วยสิ่งนี้ในใจเราสามารถสร้าง fonction ทั่วไปที่บังคับประเภทขององค์ประกอบใด ๆ ที่ยอมรับ 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 เสาคือ "สิ่ง" และอุปกรณ์ประกอบฉากอื่น ๆ (ที่นี่เฉพาะ todos เท่านั้น) จะถูกส่งผ่านอย่างถูกต้อง
ตอนนี้ขยายแนวคิดนี้ไปยังกรณีทั่วไปมากขึ้น: "ส่วนประกอบที่มีลำดับสูงกว่า" ในการตอบสนอง "องค์ประกอบลำดับสูง" คือ "ฟังก์ชั่นที่ใช้ส่วนประกอบและส่งคืนองค์ประกอบใหม่"
ความคิดคือ:
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'} เพื่อลบ type เสาออกจากสิ่งที่เราสืบทอดมาจาก WrappedComponent.PropTypes ดังนั้นองค์ประกอบที่ส่งคืนจะคาดหวังอุปกรณ์ประกอบฉากเดียวกับที่ห่อหุ้มยกเว้น 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 เวลานี้ฟังก์ชั่น from_data_source ใช้สองอาร์กิวเมนต์นอกเหนือจาก WrappedComponent :
prop_name : มันเป็นชื่อของเสาขององค์ประกอบที่ห่อหุ้มเพื่อเติมเต็มข้อมูลบางอย่างget_source : เป็นฟังก์ชันที่จะถูกเรียกให้รับข้อมูล ดูวิธีที่เราสืบทอด PropTypes จากส่วนประกอบที่ห่อหุ้มและวิธีที่เรายกเว้น prop_name ดังนั้นเราจึงไม่มี (และไม่สามารถ) ส่งข้อมูลไปยังส่วนประกอบใหม่ของเรา
จากนั้นใน render เราตั้งค่าเสาเพื่อส่งผ่านไปยัง WrappedComponent ด้วยผลลัพธ์ของการโทรไปยัง get_source
ดังนั้นเรามาเขียนฟังก์ชั่นที่ง่ายมาก (อาจเป็นวิธีที่ซับซ้อนด้วยการแคชการกรอง ... ) ที่ใช้อุปกรณ์ประกอบฉากและบริบทและส่งคืนข้อมูลบางอย่าง:
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 >มันทำงานได้ตามที่คาดไว้และข้อมูลจะถูกนำมาใช้เฉพาะเมื่อส่วนประกอบจำเป็นต้องแสดงผล
ดังนั้นเรามีรายการสิ่งที่ต้องทำซึ่งสามารถดึงข้อมูลจากแหล่งภายนอก แต่เราอาจต้องการให้ข้อมูลแตกต่างกันไปขึ้นอยู่กับผู้ใช้
สิ่งที่เราสามารถทำได้มันอยู่ในระดับหลักรับผู้ใช้ของเราและส่งผ่านทุกองค์ประกอบเพื่อให้แน่ใจว่าแต่ละองค์ประกอบสามารถรับผู้ใช้เข้าสู่ระบบปัจจุบันได้
มันจะยุ่งยากหรือไม่?
การแก้ปัญหาการใช้งานนี้เป็นจุดประสงค์ที่แน่นอนของแนวคิด Context ที่จัดทำโดย mixt แน่นอนว่าได้รับแรงบันดาลใจจากแนวคิดของบริบทใน 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
ลองกับผู้ใช้ 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
ในกรณีนี้แน่นอนว่าเราสามารถผ่านรหัสผู้ใช้เป็นเสาได้ แต่ลองจินตนาการถึงแอพ Tode ที่ลึกลงไปในต้นไม้ส่วนประกอบมันง่ายกว่ามากที่จะผ่านมันไปด้วยวิธีนี้
แต่ดังที่ได้กล่าวไว้ในเอกสาร React:
อย่าใช้บริบทเพียงเพื่อหลีกเลี่ยงการผ่านอุปกรณ์ประกอบฉากไม่กี่ระดับ ยึดติดกับกรณีที่ต้องเข้าถึงข้อมูลเดียวกันในหลาย ๆ ส่วนประกอบในหลายระดับ
เมื่อไม่มีบริบทอาร์กิวเมนต์ context ของวิธี render ถูกตั้งค่าเป็น EmptyContext และไม่ None ดังนั้นคุณสามารถใช้วิธี has_prop โดยตรงเพื่อตรวจสอบว่ามีเสาผ่านบริบทหรือไม่
มาอัปเดตฟังก์ชั่น get_todos เพื่อส่งคืนรายการวัตถุที่ว่างเปล่าของวัตถุ todo หากไม่มีผู้ใช้ที่ได้รับการรับรองความถูกต้อง
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 สามารถมี CSS และ 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);
}
""" )ในการเริ่มต้นเราจะแสดงข้อความที่ต้องทำใหม่เท่านั้น
ตอนนี้อัปเดตวิธี render ของเราเพื่อส่งคืนจาวาสคริปต์นี้ (โปรดทราบว่าการใช้วิธี 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 ที่จะทำสิ่งนี้ แต่อยู่ในรายการ ดังนั้นเราจะเพิ่ม JS บางส่วนในองค์ประกอบ TodoList : ฟังก์ชั่นที่ใช้ข้อความและสร้างรายการใหม่
สำหรับ 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 ของเราเพื่อเพิ่มแท็ก <script> และ id ไปยังแท็ก ul ที่ใช้ใน JavaScript:
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 > และตอนนี้เราสามารถอัปเดตวิธี render_javascript ขององค์ประกอบ TodoForm เพื่อใช้ฟังก์ชัน 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 ของเราและแทนที่แท็ก script ของเราด้วย JSCollector.Collect tag:
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>
เนื่องจากการใช้ JS/CSS นั้นค่อนข้างธรรมดาในโลก HTML เราได้เพิ่มน้ำตาลเพื่อให้สิ่งเหล่านี้ง่ายขึ้น
หากคุณมีวิธี render_js JSCollector จะรวบรวมผลลัพธ์ของวิธีนี้โดยอัตโนมัติ เช่นเดียวกับ CSSSelector และวิธี render_css
ด้วยสิ่งนี้ไม่จำเป็นต้องมีแท็ก JSCollector.Collect
เพื่อให้งานนี้เป็นตัวอย่างของเราใน TodoForm และ TodoList :
JSCollector.Collect tagsFragment ที่ไม่จำเป็นในขณะนี้render_javascript เป็น render_jshtml.Raw ใน render_js เนื่องจากไม่จำเป็นเมื่อตัวสะสมเรียก 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 โดยไม่มีส่วนของ DOM แน่นอน
คุณสร้างการอ้างอิงส่งผ่านไปยังส่วนประกอบและคุณสามารถใช้งานได้ทุกที่
เรามาอัปเดตรหัสหลักของเราเพื่อทำสิ่งนี้
ก่อนอื่นเราสร้างการอ้างอิง
from mixt import Ref
js_ref = Ref () สิ่งนี้จะสร้างวัตถุใหม่ที่จะอ้างอิงถึงส่วนประกอบ ในส่วนประกอบคุณไม่จำเป็นต้องนำเข้า Ref และสามารถใช้ js_ref = self.add_ref() แต่เราไม่ได้อยู่ในส่วนประกอบที่นี่
เพื่อบันทึกการอ้างอิงเราเพียงส่งผ่านไปยังเสา ref :
< JSCollector ref = { js_ref } > ... < / JSCollector > ขอให้สังเกตว่าเราลบเสา render_position เพราะตอนนี้เราไม่ต้องการให้ JS ถูกวางไว้ก่อนหรือหลังแท็ก แต่ที่อื่น
ในการเข้าถึงส่วนประกอบที่อ้างอิงโดยการอ้างอิงให้ใช้แอตทริบิวต์ current :
js_collector = js_ref . currentแน่นอนว่าสิ่งนี้สามารถทำได้หลังจากการแสดงผล
เราจะใช้สิ่งนี้เพื่อเพิ่มแท็ก script ใน head ของเราได้อย่างไร
อัปเดต 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 : หากมีอะไรเพิ่มลงในต้นไม้เป็นสิ่งที่เรียกได้มันจะถูกเรียกหลังจากการเรนเดอร์เมื่อเปลี่ยนเป็นสตริง
ดังนั้นเราสามารถใช้ตัวอย่างแลมบ์ดา:
# ...
< 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 > Hurray เราทำมัน! คุณสมบัติหลักทั้งหมดของ mixt อธิบาย ตอนนี้คุณสามารถใช้ mixt ในโครงการของคุณเอง
เป็นขั้นตอนต่อไปคุณอาจต้องการอ่านเอกสาร API