Tulis komponen HTML secara langsung di Python dan Anda memiliki campuran yang indah namun kontroversial.
Ya, kontroversial .
Jika Anda tidak menyukainya, abaikan saja (tetapi Anda dapat menggunakan ini tanpa bagian html-in-python, lihat di bawah;))
Berdasarkan PYXL. Python 3.6+ saja, dan gunakan pengetikan untuk validasi data.
Setelah Anda memiliki HTML, Anda dapat melakukan apa pun yang Anda inginkan dengannya. Anggap saja sebagai pengganti mesin template klasik Anda.
Kode Sumber : https://github.com/twidi/Mixt/
Dokumentasi : https://twidi.github.io/Mixt/
PYPI : https://pypi.org/project/mixt/
CI (Circleci): https://circleci.com/gh/twidi/workflows/mixt/
Mari kita buat file 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" / > )Dan jalankan:
$ python example.py
< div > Hello, World < /div >Jika Anda tidak suka menulis HTML di Python, Anda masih dapat menggunakannya:
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" ))Ya itu terinspirasi oleh React (sebenarnya, terutama JSX) dan kami meminjam beberapa konsep:
Dan kami menambahkan:
Jalankan dua perintah ini. Yang kedua akan memberi tahu Python cara memahami file dengan HTML di dalam.
pip install mixt
mixt-post-installUntuk memeriksa apakah semuanya sudah siap, jalankan:
python -m mixt.examples.simpleAnda harus memiliki output ini:
< div title =" Greeting " > Hello, World </ div > Jika Anda tidak ingin menggunakan barang-barang HTML-in-python, jangan jalankan mixt-post-install . Dan kemudian diuji dengan (untuk memiliki output yang sama):
python -m mixt.examples.simple_pure_pythonKloning proyek git kemudian:
make devUntuk memeriksa apakah semuanya sudah siap, jalankan:
python -m mixt.examples.simpleAnda harus memiliki output ini:
< div title =" Greeting " > Hello, World </ div >Setelah melakukan beberapa kode:
make testsmake lint Jika Anda menyentuh hal-hal di direktori codec , Anda harus menjalankan make dev (atau setidaknya make full-clean ) untuk membersihkan file pyc Python.
Perhatikan bahwa CI kami akan memeriksa apakah setiap komit lulus make lint , make tests dan make check-doc . Jadi jangan lupa untuk menjalankan ini untuk setiap komit.
Salah satu cara untuk melakukannya sebelum mendorong adalah:
git rebase develop --exec ' git log -n 1; make checks ' Catatan: Anda dapat menemukan kode akhir dari panduan pengguna ini di src/mixt/examples/user_guide (Anda akan menemukan mixt.py dan pure_python.py ).
Jalankan dengan:
python -m mixt.examples.user_guideMari kita buat ... daftar todo, ya!
Tapi sebelumnya, ingat. Ini bukan bereaksi, ini tidak ada di browser dan tidak ada javascript yang terlibat di sini. Kami hanya berbicara tentang memberikan beberapa HTML.
Tetapi Anda dapat melakukan apa yang Anda inginkan dengannya. Tambahkan penangan JavaScript, bentuk sederhana ...
Berbicara tentang formulir ...
Dalam daftar Todo, kami ingin dapat menambahkan TODO. Ini adalah input teks sederhana.
Jadi mari kita buat komponen pertama kita, TodoForm . Kami menginginkan formulir dengan teks input dan tombol.
Komponen adalah subclass dari kelas Element , dengan metode render yang harus Anda tulis.
# 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 >Perhatikan bahwa ini bisa ditulis sebagai fungsi sederhana:
# 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 >Saat mencetak komponen, keduanya akan memberikan hasil yang sama:
print ( < TodoForm / > ) < form method =" post " action =" ??? " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >Perhatikan bagaimana itu diformat: tidak ada ruang di antara tag. Faktanya, ini seperti di JSX:
JSX menghapus spasi putih di awal dan akhir dari sebuah garis. Ini juga menghilangkan garis kosong. Garis baru yang berdekatan dengan tag dihapus; Garis baru yang terjadi di tengah -tengah string literal dikondensasi menjadi satu ruang
Untuk menambahkan ruang, atau garis baru, Anda dapat melewati ular surut. Mari, sebagai contoh, tambahkan newline sebelum label:
#...
< form method = "post" action = "???" >
{ ' n ' } < label > New Todo : < / label > < itext name = "todo" / >
#...Sekarang kami memiliki output ini:
< form method =" post " action =" /todo/add " >
< label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >Sekarang mari kita melangkah lebih jauh.
Perhatikan atribut action formulir. Kita perlu melewati sesuatu. Tapi coding keras itu tidak terdengar benar. WWE harus meneruskannya ke komponen.
Mixt , seperti React , konsep properti, alias "alat peraga".
Dalam Mixt , kami mendefinisikannya dengan jenis, di kelas di dalam komponen kami, bernama 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 > Di sini kami mendefinisikan prop bernama add_url yang harus berupa string ( str ). Ini menggunakan sintaks pengetikan Python.
Dan perhatikan bagaimana kami mengubah atribut action dari tag form . Sekarang {self.add_url} bukan "???" .
Ketika atribut dilewatkan di antara kawat gigi keriting, mereka ditafsirkan sebagai ular ular murni saat run-time. Faktanya, karena parser mixt akan mengubah seluruh file menjadi ular python murni sebelum membiarkan juru bahasa Python menjalankannya, itu akan tetap sama, hanya HTML di sekitar yang akan dikonversi. Jadi tidak ada penalti untuk melakukan ini.
Lihat bagaimana ini akan terlihat jika komponen kami ditulis dalam Python murni:
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")
),
) Perhatikan bagaimana alat peraga diteruskan ke komponen sebagai argumen bernama dan bagaimana action dilewati: action=self.add_url .
Komponen murni-python ini juga menunjukkan cara kerjanya: alat peraga dilewatkan sebagai argumen bernama ke kelas komponen, maka komponen ini disebut, melewati komponen anak-anak sebagai argumen posisi ke panggilan:
ComponentClass ( prop1 = "val1" , prop2 = "val2" )(
Children1 (),
Children2 (),
)Apa itu anak -anak? Anak -anak adalah tag di dalam tag lain.
Dalam <div id="divid"><span /><p>foo</p></div> , kami memiliki:
html.Div , dengan id prop dan dua anak:html.Span , tanpa anakhtml.P dengan satu anak:html.RawHtml dengan teks "foo"Perhatikan bahwa Anda dapat bermain dengan alat peraga dan anak -anak. Pertama-tama versi di Pure-Python untuk menunjukkan cara kerjanya:
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) Kemudian versi 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> Sekarang mari kita kembali ke alat peraga kami add_url .
Bagaimana cara meneruskannya ke komponen?
Cara yang sama persis kami meneruskan atribut ke tag HTML: mereka sebenarnya adalah alat peraga yang didefinisikan dalam kompomen HTML (didefinisikan dalam mixt.html ). Kami mendukung setiap tag HTML yang, pada saat penulisan, valid (tidak sudah usang) di HTML5, dengan atribut mereka (tidak termasuk yang sudah usang).
Jadi mari kita lakukan ini:
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 >Oke, kami memiliki prop kami hadir di HTML yang diberikan.
Bagaimana jika kita tidak melewati string? Kami mengatakan dalam PropTypes bahwa kami menginginkan string ...
Ayo coba:
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 >Itu berhasil! Tapi ... itu bukan string !! Bahkan, ada kasus khusus untuk angka, Anda dapat meneruskannya sebagai angka, bukan string dan mereka dikonversi jika diperlukan ...
Jadi mari kita coba sesuatu yang lain.
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' > ) Dan itu sama jika kita True dengan 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' > ) Oke, mari kita menipu sistem dan lulus "True" , sebagai string.
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' > )Masih sama, tapi di sini kami melewati string! Ya tetapi ada 4 nilai yang selalu dievaluasi dengan apa yang tampaknya:
None )Satu -satunya cara untuk meneruskan salah satu nilai ini sebagai string, adalah dengan melewatinya melalui Python, sebagai string:
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 > Kecuali 4 nilai ini, dan angka, setiap nilai yang diteruskan ke atribut dianggap sebagai string. Bahkan jika tidak ada kutipan, seperti dalam html di html5, di mana kutipan tidak wajib untuk string tanpa beberapa karakter (tidak ada spasi, tidak / ...).
Untuk melewati sesuatu yang lain, Anda harus mengelilingi nilai dalam kawat gigi keriting (dan dalam kasus ini tidak perlu kutipan di sekitar kawat gigi keriting.
Oke, sekarang kami yakin bahwa kami hanya menerima string .... tapi bagaimana jika saya tidak memberikan apa -apa? Dan ... apa itu "tidak ada"?
Mari kita mulai dengan string kosong di Python:
print ( < TodoForm add_url = { "" } / > ) < form method =" post " action ="" > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >Oke, kami menginginkan string, kami memiliki string.
Sekarang mari kita lewati string kosong ini secara langsung:
print ( < TodoForm add_url = "" / > ) < form method =" post " action ="" > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >Itu masih berfungsi, karena masih string. Mari kita hapus kutipan, untuk melihat.
print ( < TodoForm add_url = / > ) mixt . exceptions . GeneralParserError : < mixt parser > Unclosed Tags : < TodoForm > HUM Ya, ini bukan HTML yang valid. Jadi mari kita hapus = :
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' > ) APA? Ya, pikirkan tentang atribut HTML5 seperti required , checked ... mereka hanya perlu hadir sebagai atribut, tanpa nilai, untuk dianggap True . Jadi ketika atribut tidak memiliki nilai, itu adalah boolean, dan itu True .
Selain tidak memberikan nilai, kedua cara lain itu valid dalam HTML5 untuk boolean oleh True :
required=""required="required"Untuk kenyamanan Anda, kami menambahkan cara lain:
True (case tidak masalah), sebagai python atau sebagai string: required=True , required={True} , required="true" Dan mitranya, untuk lulus False :
False (case tidak masalah), sebagai python atau sebagai string: required=False , required={False} , required="false" Oke untuk atribut Boolean. Ini bukan kasus kami. Hal terakhir yang bisa kita lakukan adalah tidak mengatur atribut sama sekali:
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 setIni bisa dimengerti: kami mencoba mengakses prop yang tidak diatur, tentu saja kami tidak dapat menggunakannya.
Tapi bagaimana jika kita tidak mengaksesnya? Jika kami tidak mencetak komponen, itu tidak akan diterjemahkan:
< TodoForm / > < TodoForm at 0x7fbd18ea5630 >Jadi kita dapat membuat instance tetapi akan gagal pada waktu render. Tapi ada cara untuk mencegahnya.
Secara default, semua properti opsional. Dan Anda tidak perlu menggunakan tipe Optional dari modul typing Python untuk itu, akan rumit untuk melakukannya untuk setiap penyangga.
Sebagai gantinya, mixt menyediakan jenis bernama Required bahwa Anda menggunakan cara yang sama dari Optionnal .
from mixt import Element , Required , html
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
def render ( self , context ):
# ...Jadi kami hanya mengatakan kami menginginkan string, dan itu diperlukan.
Mari kita coba lagi untuk membuatnya tanpa prop:
< TodoForm / > mixt . exceptions . RequiredPropError : < TodoForm > . add_url : is a required prop but is not setSekarang kami memiliki pengecualian yang diangkat sebelumnya dalam program kami.
Untuk melihat kemungkinan lain dalam alat peraga, mari tambahkan yang baru untuk mengubah label teks. Tetapi kami tidak ingin membuatnya diperlukan, dan sebaliknya memiliki nilai default.
Untuk ini, semudah menambahkan nilai pada prop di kelas 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 >Sekarang mari kita coba tanpa melewati prop:
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 >Dan jika kita lulus satu:
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 >Itu berfungsi seperti yang diharapkan.
Perhatikan bahwa Anda tidak dapat memberikan nilai default saat memiliki prop Required . Tidak masuk akal, jadi diperiksa sesegera mungkin, saat class dibangun:
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 requiredDan tentu saja nilai default harus cocok dengan jenisnya!
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' > ) Hal lain yang ingin kami lakukan dalam komponen kami adalah membiarkannya membangun label, melewatkannya "tipe" TODO, tetapi membatasi pilihan. Untuk ini kita dapat menggunakan jenis 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 >Ayo coba:
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 >Dan bagaimana jika kita mencoba melewati sesuatu yang lain selain pilihan yang tersedia? Itu gagal, seperti yang diharapkan:
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' ])Tapi mungkin kami tidak ingin melewatinya dan menggunakan nilai default. Apa hasilnya?
print ( < TodoForm add_url = "/todo/add" / > ) mixt . exceptions . UnsetPropError : < TodoForm > . type : prop is not set Jadi kita harus menandai type prop sesuai kebutuhan:
class PropTypes :
add_url : Required [ str ]
type : Required [ Choices ] = [ 'todo' , 'thing' ]Jadi jika kita tidak melewatinya, itu gagal sebelumnya:
print ( < TodoForm add_url = "/todo/add" / > ) mixt . exceptions . RequiredPropError : < TodoForm > . type : is a required prop but is not setTapi bukan itu yang kami inginkan, kami menginginkan nilai default.
Bahkan, Anda memperhatikan bahwa untuk jenis selain Choices , menetapkan nilai dalam PropTypes memberi kami nilai default. Tetapi untuk Choices itu berbeda, karena nilainya adalah daftar pilihan.
Untuk ini, kami memiliki DefaultChoices : ia bekerja sama dengan Choices , tetapi gunakan entri pertama dalam daftar sebagai nilai default. Dan tentu saja, seperti halnya jenis lain yang memiliki default, itu tidak dapat Required .
Ayo coba:
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 >Itu berfungsi seperti yang diharapkan.
Sampai saat itu, kami menggunakan tipe sederhana, tetapi Anda dapat menggunakan yang lebih rumit.
Jadi misalnya, kami akan membuat prop add_url untuk menerima fungsi yang akan menghitung URL untuk kami berdasarkan type prop. Tetapi kami juga ingin mengizinkan string, dan dengan nilai default.
Kita bisa melakukannya, dengan mengetik. Fungsi kami akan mengambil string, type dan akan mengembalikan string, URL.
Jadi sintaksnya Callable[[str], str] untuk yang dapat dipanggil, dan kami menggunakan Union untuk menerima hal -hal yang Callable atau 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 > Pertama, mari kita coba tanpa prop add_url , karena kita memiliki default:
print ( < TodoForm / > ) < form method =" post " action =" /todo/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >Itu harus bekerja juga jika kita melewati string:
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 >Dan sekarang kita dapat melewati suatu fungsi:
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 ])Oh? Mengapa? Saya melewati fungsi yang menerima string sebagai argumen dan mengembalikan string. Ya, tapi jangan lupa bahwa tipe diperiksa! Jadi kita harus menambahkan jenis ke fungsi kita:
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 >Dan jika kita melewati jenis lain, URL harus berubah sesuai:
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 >Kami bahkan dapat menjadikan fungsi ini nilai default untuk prop kami:
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 >Sekarang Anda mungkin mulai bertanya -tanya ... pengetikan Python rumit dan validasi dapat menghilangkan sebagian waktu berharga kami.
Mari saya menjawabnya:
Secara default, mixt Run in "Dev-Mode". Dan dalam mode dev, alat peraga divalidasi ketika diteruskan ke komponen. Ketika Anda tidak berada di "dev-mode", validasi dilewati. Jadi dalam produksi, Anda dapat menonaktifkan mode dev (kita akan lihat bagaimana dalam satu menit) dan lulus alat peraga dengan sangat cepat:
Choices , memang, dalam daftar pilihanrender Anda. Tetapi Anda dapat mengatakan bahwa dalam produksi bahwa validasi itu penting. Memang. Tetapi tentu saja kode Anda sepenuhnya dicakup oleh tes, yang Anda jalankan dalam mode dev, dan dalam produksi, Anda tidak memerlukan validasi ini! Dan perhatikan bahwa ini adalah cara kerja reaksi, dengan cara, dengan NODE_ENV=production .
Bagaimana cara mengubah mode dev? Kami tidak menegakkan variabel lingkungan apa pun tetapi kami mengusulkan beberapa fungsi. Terserah Anda untuk menelepon mereka:
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 () Jadi mari kita coba ini dengan type prop. Ingat, sepertinya:
type : DefaultChoices = [ 'todo' , 'thing' ]Kami mencoba melewati pilihan lain, pertama-tama dalam mode dev:
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' ])Gagal seperti yang diharapkan.
Dan sekarang dengan menonaktifkan 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 > Ini berhasil, kami memiliki tipe TODO yang tidak ada dalam pilihan kami yang digunakan, dan juga action . Ini adalah pekerjaan tes Anda untuk memastikan bahwa Anda tidak pernah lulus alat peraga yang tidak valid, sehingga Anda dapat percaya diri dalam produksi dan menonaktifkan mode dev.
Sekarang kami memiliki formulir kami. Komponen apa lagi yang kita butuhkan untuk aplikasi daftar TODO kita?
Tentu saja, kita membutuhkan cara untuk menampilkan entri TODO.
Tapi apa itu entri TODO? Mari Buat TodoObject Dasar:
class TodoObject :
def __init__ ( self , text ):
self . text = textIni adalah kelas yang sangat sederhana, tetapi Anda dapat menggunakan apa yang Anda inginkan, tentu saja. Bisa jadi model django, dll ...
Jadi kami dapat membuat komponen Todo kami, membuatnya menerima TodoObject yang diperlukan sebagai prop:
class Todo ( Element ):
class PropTypes :
todo : Required [ TodoObject ]
def render ( self , context ):
return < li > { self . todo . text } < / li >Dan kita bisa menggunakannya:
todo = TodoObject ( "foo" )
print ( < Todo todo = { todo } / > ) < li > foo </ li > Sekarang kami ingin memiliki daftar Todos. Mari kita buat komponen TodoList yang akan menerima sebagai alat peraga daftar TodoObject .
Tetapi apa yang berbeda dari dua komponen kami yang lain, yang hanya menggunakan tag HTML dalam metode render mereka, sekarang kami akan merangkum komponen ke yang lain. Mari kita lihat caranya.
class TodoList ( Element ):
class PropTypes :
todos : Required [ List [ TodoObject ]]
def render ( self , context ):
return < ul > {[ < Todo todo = { todo } / > for todo in self . todos ]} < / ul > Ya, sesederhana itu: Anda menggunakan <Todo...> untuk komponen Todo karena Anda akan menggunakan tag HTML. Satu-satunya perbedaan adalah bahwa untuk tag HTML, Anda tidak perlu mengimpornya secara langsung (impor sederhana html dari mixt ), dan dengan konvensi kami menulisnya dalam kasus rendah. Untuk komponen normal, Anda harus mengimpornya (Anda masih dapat melakukannya from mylib import components dan <components.MyComponent ...> ) dan gunakan casing yang tepat.
Perhatikan bagaimana kami membutuhkan daftar, dan meneruskannya ke <ul> melalui daftar-komprehensi di kawat gigi keriting.
Anda dapat melakukan sesuatu secara berbeda jika Anda mau.
Seperti memisahkan pemahaman daftar dari html:
def render ( self , context ):
todos = [
< Todo todo = { todo } / >
for todo
in self . todos
]
return < ul > { todos } < / ul >Atau dalam metode khusus (itu akan berguna untuk pengujian):
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 >Terserah Anda: Pada akhirnya itu hanya Python.
Mari kita lihat apa yang diberikan oleh komponen ini:
todos = [ TodoObject ( "foo" ), TodoObject ( "bar" ), TodoObject ( "baz" )]
print ( < TodoList todos = { todos } / > ) < ul > < li > foo </ li > < li > bar </ li > < li > baz </ li > </ ul > Dan akhirnya kami memiliki komponen TodoApp kami yang merangkum formulir dan daftar:
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 >Ayo berikan html ini ke html beautifier:
< 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 > Dan hanya itu, kami memiliki aplikasi daftar todo kami! Untuk menggunakannya di halaman, cukup buat komponen yang akan membuat markup dasar HTML dan mengintegrasikan komponen TodoApp di dalamnya. Anda bahkan tidak membutuhkan komponen:
todos = [ TodoObject ( "foo" ), TodoObject ( "bar" ), TodoObject ( "baz" )]
print (
< html >
< body >
< TodoApp todos = { todos } type = "thing" / >
< / body >
< / html >
)Output yang dipercantik adalah:
< 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 >Kami memiliki daftar todo generik, tetapi mengikuti jenis TODO yang tersedia, kami mungkin ingin memiliki "daftar todo" dan "daftar hal".
Kami sudah memiliki daftar TODO karena TodoApp kami memiliki jenis todo secara default.
Jadi mari kita buat ThingApp .
Cara pertama melakukan ini adalah mewarisi dari TodoApp kami. Tetapi dengan mewarisi kami tidak dapat menghapus alat peraga dari induk (itu tidak benar -benar benar, kita akan melihatnya nanti), jadi kita masih memiliki type prop secara default. Tapi kami tidak ingin menerima apa pun selain "hal". Jadi kita dapat mendefinisikan kembali type prop seperti ini:
class ThingApp ( TodoApp ):
class PropTypes :
type : DefaultChoices = [ 'thing' ]Mari kita gunakan komponen ini:
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 > Jika kami mencoba lulus "Todo" untuk type alat peraga, itu tidak akan berhasil:
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' ])Tapi tetap saja, aneh bisa melewati tipe.
Mari kita coba cara lain: komponen induk. Komponen yang tidak melakukan hal lain yang melakukan sesuatu dengan anak -anaknya dan mengembalikannya. Yang kami inginkan di sini, adalah komponen yang akan mengembalikan TodoApp dengan type prop yang dipaksa untuk "benda".
Ayo lakukan ini
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 > Ini berhasil, dan kali ini, kita tidak dapat melewati type prop:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) mixt . exceptions . InvalidPropNameError : < ThingApp > . type : is not an allowed prop Perhatikan bagaimana kami harus mendefinisikan jenis untuk alat peraga todos . Baik di TodoApp dan TodoThing .
Ada banyak cara untuk menanganinya.
Yang pertama adalah mengabaikan jenis di ThingApp karena akan diperiksa di TodoApp . Jadi kami akan menggunakan tipe Any :
from typing import Any
#...
class ThingApp ( Element ):
class PropTypes :
todos : Any
#...Mari kita coba dengan daftar Todos yang valid:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > </ ul > </ div >Tapi bagaimana jika kita melewati sesuatu yang lain?
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 ]) Ini berfungsi seperti yang diharapkan tetapi kesalahan dilaporkan pada tingkat TodoApp , yang sangat normal.
Cara lain adalah dengan menentukan tipe pada tingkat yang lebih tinggi:
TodoObjects = Required [ List [ TodoObject ]]
class TodoApp ( Element ):
class PropTypes :
todos : TodoObjects
# ...
class ThingApp ( Element ):
class PropTypes :
todos : TodoObjects
# ...Sekarang jika kita melewati sesuatu yang lain, kita memiliki kesalahan yang dilaporkan pada level yang benar:
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 ]) Tetapi jika Anda tidak dapat atau tidak ingin melakukan itu, Anda dapat menjaga tipe yang ditentukan dalam TodoApp ET Gunakan metode kelas prop_type dari suatu komponen untuk mendapatkan jenis prop:
class ThingApp ( Element ):
class PropTypes :
todos : TodoApp . prop_type ( "todos" )
# ... Tetapi apakah benar -benar penting jika kesalahan dinaikkan untuk ThingApp atau TodoApp ? Karena pada akhirnya, itu benar -benar TodoApp yang harus melakukan cek.
Jadi ini harus menjadi cara untuk melakukan ini dengan cara yang lebih umum ..
Kami melihat sebelumnya bahwa komponen dapat menjadi fungsi tunggal untuk membuat komponen. Itu hanya harus mengembalikan komponen, tag HTML. Satu perbedaan dengan komponen kelas adalah bahwa tidak ada PropTypes sehingga tidak ada validasi. Tapi ... persis seperti yang kita butuhkan.
Kami ingin ThingApp kami menerima beberapa alat peraga (prop todos ), dan mengembalikan TodoApp dengan type prop tertentu.
Jadi kami bisa melakukannya:
def ThingApp ( todos ):
return < TodoApp type = "thing" todos = { todos } / > Di sini kita dapat melihat bahwa kita tidak dapat meneruskan type ke ThingsApp , itu bukan argumen yang valid.
Ayo coba:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > </ ul > </ div > Di sini kita hanya memiliki satu penyangga untuk dilewati jadi mudah. Tapi bayangkan jika kita memiliki banyak orang. Kita dapat menggunakan sintaks {**props} :
def ThingApp ( ** props ):
return < TodoApp type = "thing" { ** props } / >Dan Anda dapat melakukannya dengan lebih sedikit karakter (jika diperhitungkan):
ThingApp = lambda ** props : < TodoApp type = "thing" { ** props } / >Kedua perampasan ini berperilaku persis sama.
Dan Anda tidak dapat melewati type prop karena itu akan menjadi kesalahan Python, karena akan diteruskan dua kali ke TodoApp :
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} type = "thing" / > ) TypeError : BaseMetaclass object got multiple values for keyword argument 'type' (ya itu berbicara tentang BaseMetaclass yang merupakan metaclass yang menciptakan kelas komponen kami)
Dan alat peraga lain yang salah akan divalidasi oleh TodoApp :
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} foo = "bar" / > ) mixt . exceptions . InvalidPropNameError : < TodoApp > . foo : is not an allowed prop Dengan pemikiran ini, kita bisa menciptakan fonction generik yang memaksa jenis komponen apa pun yang menerima 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 > Komponen yang diberikan adalah TodoApp , type prop adalah "benda" dan alat peraga lainnya (di sini hanya todos ) yang dilewati dengan benar.
Sekarang memperluas konsep ini ke kasus yang lebih umum: "komponen tingkat tinggi". Dalam bereaksi "komponen orde tinggi", adalah "fungsi yang mengambil komponen dan mengembalikan komponen baru."
Idenya adalah:
EnhancedComponent = higherOrderComponent ( WrappedComponent )Cara klasik untuk melakukannya adalah mengembalikan kelas komponen baru:
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 Perhatikan bagaimana kami mengatur kelas PropTypes untuk mewarisi dari salah satu komponen yang dibungkus, dan bagaimana kami meneruskan semua alat peraga ke komponen yang dibungkus, bersama dengan anak -anak. Dengan komponen yang dikembalikan akan menerima alat peraga yang sama, dengan tipe yang sama, seperti yang dibungkus.
Dan juga perhatikan __display_name__ . Ini akan digunakan dalam pengecualian untuk membiarkan Anda sekarang komponen yang mengangkatnya. Di sini, tanpa memaksanya, itu akan diatur ke HOC , yang tidak membantu. Sebaliknya, kami menunjukkan bahwa itu adalah versi yang diubah dari komponen yang diteruskan.
Ini adalah fungsi yang tidak bermanfaat.
Dalam contoh kami, kami bisa melakukan ini:
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 HOCDua hal penting di sini:
__exclude__ = {'type'} untuk menghapus type prop dari yang kami warisi dari WrappedComponent.PropTypes . Jadi komponen yang dikembalikan akan mengharapkan alat peraga yang sama persis dengan yang dibungkus, kecuali untuk type .{self.children()} dalam komponen yang dibungkus yang diberikan, karena bahkan jika kita benar-benar tahu bahwa komponen yang akan kita bungkus, TodoApp , tidak mengambil anak (itu bisa tetapi tidak melakukan apa pun dengan mereka), kita tidak dapat mengatakan sebelumnya bahwa itu akan selalu terjadi, dan juga bahwa komponen orde tinggi ini tidak akan digunakan untuk membungkus komponen lain daripada TodoApp . Jadi lebih baik selalu melakukan ini. Dan sekarang kita dapat membuat ThingApp kita:
ThingApp = thingify ( TodoApp )Dan menggunakannya:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > </ ul > </ div >Jika kami mencoba lulus tipe:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} type = "thing" / > ) mixt . exceptions . InvalidPropNameError : < thingify ( TodoApp ) > . type : is not an allowed prop Jadi seperti yang direncanakan, kita tidak dapat melewati jenisnya. Dan perhatikan bagaimana __display_name__ digunakan.
Mari kita pikirkan betapa kuatnya ini.
Katakanlah kita ingin membuat TodoApp kita mengambil daftar TodoObject . Tapi kami ingin mendapatkannya dari "sumber".
Kita bahkan dapat secara langsung menulisnya komponen tingkat tinggi baru ini dengan cara umum:
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 Kali ini, fungsi from_data_source mengambil dua argumen selain WrappedComponent :
prop_name : Ini nama prop komponen yang dibungkus untuk diisi dengan beberapa dataget_source : Ini adalah fungsi yang akan dipanggil untuk mendapatkan data Lihatlah bagaimana kami mewarisi PropTypes dari komponen yang dibungkus dan bagaimana kami mengecualikan prop_name . Jadi kami tidak memiliki (dan tidak dapat) meneruskan data ke komponen baru kami.
Dan kemudian di render , kami menetapkan prop untuk dilewati ke WrappedComponent dengan hasil panggilan untuk get_source .
Jadi mari kita tulis fungsi yang sangat sederhana (ini bisa menjadi rumit dengan caching, penyaringan ...) yang mengambil alat peraga dan konteksnya, dan mengembalikan beberapa data:
def get_todos ( props , context ):
# here it could be a call to a database
return [
TodoObject ( "fooooo" ),
TodoObject ( "baaaar" ),
]Dan kami dapat menyusun komponen kami:
SourcedTodoApp = from_data_source ( TodoApp , 'todos' , get_todos )
ThingApp = thingify ( SourcedTodoApp )Dan jalankan:
print ( < ThingApp / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > fooooo </ li > < li > baaaar </ li > </ ul > </ div >Ini berfungsi seperti yang diharapkan, dan data diambil hanya ketika komponen perlu diterjemahkan.
Jadi, kami memiliki daftar TODO, yang dapat mengambil data dari sumber eksternal. Tetapi kami mungkin ingin data berbeda tergantung pada pengguna.
Apa yang bisa kami lakukan, itu ada di tingkat utama, dapatkan pengguna kami dan meneruskannya pada setiap komponen untuk memastikan bahwa setiap komponen dapat memperoleh pengguna yang masuk saat ini.
Bukankah itu rumit?
Memecahkan kasus penggunaan ini adalah tujuan pasti dari konsep Context yang disediakan oleh mixt . Ini, tentu saja, terinspirasi oleh konsep konteks dalam React.
Dan seperti yang mereka katakan:
Konteks dirancang untuk berbagi data yang dapat dianggap "global" untuk pohon komponen reaksi, seperti pengguna, tema, atau bahasa yang diotentikasi saat ini.
Membuat konteks sesederhana membuat komponen, kecuali bahwa itu akan mewarisi dari BaseContext dan tidak memerlukan metode render (itu akan membuat anak -anaknya).
Dan dibutuhkan kelas PropTypes , yang menentukan jenis data yang akan diterima dan diturunkan oleh konteksnya.
Jadi mari kita buat konteks kita yang akan menahan ID pengguna yang diautentikasi.
from mixt import BaseContext
class UserContext ( BaseContext ):
class PropTypes :
authenticated_user_id : Required [ int ] Sekarang, kami ingin memperbarui metode get_todos kami untuk memperhitungkan authenticated_user_id .
Ingat, kami melewatinya alat peraga dan konteksnya. Konteksnya akan berguna di sini:
def get_todos ( props , context ):
return {
1 :[
TodoObject ( "1-1" ),
TodoObject ( "1-2" ),
],
2 : [
TodoObject ( "2-1" ),
TodoObject ( "2-2" ),
]
}[ context . authenticated_user_id ]Dan sekarang kita dapat membuat aplikasi kita dengan konteksnya:
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 >Kita dapat melihat entri TODO untuk pengguna 1.
Mari kita coba dengan pengguna 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 >Kita dapat melihat entri TODO untuk pengguna 2.
Dalam hal ini tentu saja kami bisa lulus ID pengguna sebagai penyangga. Tapi bayangkan aplikasi Todo berada jauh di dalam pohon komponen, jauh lebih mudah untuk melewatinya dengan cara ini.
Tetapi seperti yang dikatakan dalam dokumentasi React:
Jangan gunakan konteks hanya untuk menghindari lewat alat peraga beberapa level turun. Tetap berpegang pada kasus di mana data yang sama perlu diakses di banyak komponen di berbagai tingkatan.
Ketika tidak ada konteks, argumen context dari metode render diatur ke EmptyContext dan tidak untuk None . Jadi Anda dapat secara langsung menggunakan metode has_prop untuk memeriksa apakah prop tersedia melalui konteks.
Mari kita perbarui fungsi get_todos untuk mengembalikan daftar objek TODO yang kosong jika tidak ada pengguna yang diautentikasi.
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 ]Mari kita coba ini:
print ( < ThingApp / > ) < div > < h1 > The "thing" list < / h1 > < form > ... < / form > < ul > < / ul > < / div >Dan itu masih berfungsi dengan pengguna dalam konteks:
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 >Catatan penting tentang konteks : Anda dapat memiliki banyak konteks! Tetapi mendefinisikan prop yang sama dalam banyak konteks dapat menyebabkan perilaku yang tidak ditentukan.
Semua orang menyukai desain yang indah, dan mungkin beberapa interaksi.
Mudah dilakukan: kami menghasilkan HTML dan HTML dapat berisi beberapa CS dan JS.
Mari kita tambahkan beberapa interaksi terlebih dahulu: Saat menambahkan item di TodoForm , mari kita tambahkan ke daftar.
Pertama, kami menambahkan komponen TodoForm kami metode render_javascript yang akan meng -host kami (buruk, kami bisa melakukan lebih baik tetapi itu bukan intinya) 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);
}
""" )Untuk memulai, kami hanya menampilkan teks TODO baru.
Sekarang perbarui metode render kami untuk mengembalikan JavaScript ini (perhatikan bahwa penggunaan metode render_javascript hanya untuk memisahkan kekhawatiran, itu bisa dalam metode render secara langsung.
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 > Perhatikan tag Fragment . Ini adalah cara untuk merangkum banyak elemen untuk dikembalikan, seperti dalam React. Itu bisa menjadi daftar sederhana tetapi dengan koma di akhir:
return [
< script > ... < / script > ,
< form >
...
< / form >
] Sekarang kami ingin menambahkan item ke daftar. Bukan peran TodoForm untuk melakukan ini, tetapi dalam daftar. Jadi kami akan menambahkan beberapa JS di komponen TodoList : fungsi yang mengambil beberapa teks dan membuat entri baru.
Adapun TodoForm , kami menambahkan metode render_javascript dengan (masih buruk) 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 )) Dan kami memperbarui metode render kami untuk menambahkan tag <script> dan id ke tag ul , digunakan dalam 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 > Dan sekarang kita dapat memperbarui metode render_javascript dari komponen TodoForm untuk menggunakan fungsi add_toto JavaScript baru kami:
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);
}
""" )Dan itu saja. Sebenarnya tidak ada yang istimewa.
Tapi mari kita lihat output ou TodoApp :
print (
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / User >
)Output yang dipercantik adalah:
< 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 > Jadi kami memiliki banyak tag script . Mungkin bagus untuk hanya memiliki satu.
mixt datang dengan cara untuk "mengumpulkan" bagian -bagian dari apa yang diberikan untuk menempatkan mereka di tempat lain. Kami memiliki dua kolektor sederhana, untuk digunakan sebagai komponen: JSCollector dan CSSCollector .
Komponen -komponen ini mengumpulkan bagian -bagian dari pohon anak -anak mereka.
Cara pertama adalah dengan menggunakan tag Collect kolektor.
Pertama mari kita ubah panggilan utama kita:
from mixt import JSCollector
print (
< JSCollector render_position = "after" >
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / User >
< / JSCollector >
) Ini akan mengumpulkan konten semua tag JSCollector.Collect .
Mari kita perbarui TodoForm kami dan ganti tag script kami dengan tag JSCollector.Collect :
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 > Kita dapat melakukan hal yang sama dengan 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 >Sekarang mari kita jalankan kode kami yang diperbarui:
print (
< JSCollector render_position = "after" >
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / User >
< / JSCollector >
)Output yang dipercantik adalah:
< 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 > Seperti yang Anda lihat, semua skrip ada dalam satu tag script , di akhir. Lebih tepatnya, di akhir di mana tag JSCollector berada, karena kami menggunakan render_position="after" . Kemungkinan lain adalah render_position="before" untuk menempatkan ini di mana tag JSCollector dimulai.
Semua ini berfungsi dengan cara yang persis sama untuk tag CSSCollector , di mana konten dimasukkan ke dalam tag <style type="text/css> .
Karena menggunakan JS/CSS cukup umum di dunia HTML, kami menambahkan beberapa gula untuk membuat semua ini lebih mudah dilakukan.
Jika Anda memiliki metode render_js , JSCollector akan secara otomatis mengumpulkan hasil metode ini. Sama untuk CSSSelector dan metode render_css .
Dengan ini, tidak perlu untuk tag JSCollector.Collect .
Untuk membuat ini bekerja dalam contoh kami, dalam TodoForm dan TodoList :
JSCollector.CollectFragment yang sekarang tidak dibutuhkanrender_javascript menjadi render_js .html.Raw di render_js karena tidak diperlukan ketika kolektor memanggil render_js sendiri: jika outputnya adalah string, itu dianggap sebagai "mentah"Dengan cara ini kami memiliki hasil yang persis sama.
Ini berfungsi sekarang karena kami hanya memiliki satu contoh anak dengan metode render_js .
Tetapi jika kita memiliki banyak anak, metode ini akan dipanggil untuk setiap anak. Jika fakta, itu hanya boleh berisi kode yang sangat spesifik untuk contoh ini.
Untuk melayani JS/CSS hanya sekali untuk kelas komponen, kita harus menggunakan render_js_global atau render_css_global (diharapkan menjadi classmethod )
Ini akan dikumpulkan pertama kali, dan hanya pertama kalinya, sebuah instance ditemukan, sebelum mengumpulkan metode render_js .
Jadi di sini, kita dapat mengubah render_js kita menjadi render_js_global , menghiasnya dengan @classmethod dan itu masih akan bekerja sama.
Kami sekarang dapat menyusun kembali javascript atau gaya. Tetapi bagaimana jika kita ingin meletakkannya di tempat lain, seperti di tag head atau di ujung tag body ?
Itu mungkin dengan referensi, alias "referensi". Ini adalah konteks yang sama seperti di React, tanpa bagian dom tentu saja.
Anda membuat ref, meneruskannya ke komponen, dan Anda dapat menggunakannya di mana saja.
Mari kita perbarui kode utama kami untuk melakukan ini.
Pertama kami membuat ref.
from mixt import Ref
js_ref = Ref () Ini akan membuat objek baru yang akan menyimpan referensi ke komponen. Dalam komponen, Anda tidak perlu mengimpor Ref dan dapat menggunakan js_ref = self.add_ref() , tetapi kami tidak berada dalam komponen di sini.
Untuk menyimpan referensi, kami cukup meneruskannya ke Prop ref :
< JSCollector ref = { js_ref } > ... < / JSCollector > Perhatikan bahwa kami menghapus prop render_position , karena sekarang kami tidak ingin JS diletakkan sebelum atau sesudah tag, tetapi di tempat lain.
Untuk mengakses komponen yang dirujuk oleh REF, gunakan atribut current :
js_collector = js_ref . currentTentu saja ini dapat dilakukan hanya setelah rendering.
Bagaimana kita bisa menggunakan ini untuk menambahkan tag script di head kita.
Pertama perbarui HTML kami untuk memasukkan html klasik, tag head dan body :
return str (
< html >
< head >
< / head >
< body >
< JSCollector ref = { js_ref } >
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / UserContext >
< / JSCollector >
< / body >
< / html >
) Pada titik ini kami tidak memiliki tag script di output:
< 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 > Hal pertama yang perlu diketahui: Seorang kolektor dapat membuat semua hal yang dikumpulkannya dengan memanggil metode render_collected -nya.
Dan mengingat bahwa itu sudah termasuk tag script , kami mungkin ingin melakukan ini:
# ...
< head >
{ js_ref . current . render_collected ()}
< / head >
# ...Tapi ini tidak berhasil:
AttributeError : 'NoneType' object has no attribute 'render_collected'Itu karena kami mencoba mengakses nilai saat ini pada waktu render. Itu harus dilakukan setelahnya.
Untuk ini, kita dapat menggunakan fitur mixt : jika sesuatu yang ditambahkan ke pohon dapat dipanggil, itu akan dipanggil setelah rendering, saat mengonversi ke string.
Jadi kita dapat menggunakan misalnya lambda:
# ...
< head >
{ lambda : js_ref . current . render_collected ()}
< / head >
# ...Dan sekarang berhasil:
< 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 > Hore kami berhasil! Semua fitur utama dari mixt dijelaskan. Anda sekarang dapat menggunakan mixt dalam proyek Anda sendiri.
Sebagai langkah selanjutnya, Anda mungkin ingin membaca dokumentasi API.