PythonにHTMLコンポーネントを直接書き込むと、美しいが物議を醸す混合物があります。
はい、物議を醸しています。
気に入らない場合は無視してください(ただし、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を作成しましょう
# coding: mixt
from mixt import html , Element , Required
class Hello ( Element ):
class PropTypes :
name : Required [ str ]
def render ( self , context ):
return < div > Hello , { self . name } < / div >
print ( < Hello name = "World" / > )そしてそれを実行します:
$ python example.py
< div > Hello, World < /div >PythonでHTMLを作成したくない場合でも、使用できます。
from mixt import html , Element , Required
class Hello ( Element ):
class PropTypes :
name : Required [ str ]
def render ( self , context ):
return html . Div ()( "Hello, " , self . name )
print ( Hello ( name = "World" ))はい、それはReact(実際、主にJSX)に触発されており、私たちはいくつかの概念を借ります。
そして、私たちは追加しました:
これら2つのコマンドを実行します。 2つ目は、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 Directoryで物事を触れた場合、 pyc Pythonファイルをパージするためにmake dev (または少なくともmake full-clean )を実行する必要があります。
CIは、すべてのコミットがmake lintに合格し、 make tests 、 make check-docことを確認することに注意してください。したがって、各コミットでこれらを実行することを忘れないでください。
プッシュする前にそれを行う1つの方法は次のとおりです。
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... todoリストを作成しましょう、ええ!
しかし、前に、覚えておいてください。これは反応ではなく、ブラウザ上ではなく、ここにはJavaScriptが関係していません。 HTMLのレンダリングについてのみ話します。
しかし、あなたはそれであなたが望むことをすることができます。 JavaScriptハンドラー、シンプルなフォームを追加してください...
フォームについて話す...
TODOリストでは、TODOを追加できるようにしたいと考えています。簡単なテキスト入力です。
それでは、最初のコンポーネントである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 >コンポーネントを印刷すると、これら2つは同じ結果をもたらします。
print ( < TodoForm / > ) < form method =" post " action =" ??? " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >フォーマットされている方法に注意してください:タグ間のスペースはありません。実際、それはJSXのようなものです:
JSXは、ラインの最初と終了時に空白を削除します。また、空白線を削除します。タグに隣接する新しい行が削除されます。文字列リテラルの中央で発生する新しい線は、単一のスペースに凝縮されます
スペース、または新しいラインを追加するには、Pythonを渡すことができます。例として、ラベルの前に新しいラインを追加しましょう。
#...
< 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 >ここでは、string( str )でなければならないadd_urlという名前の小道具を定義しました。これは、Pythonタイピング構文を使用します。
formタグのaction属性をどのように変更したかに注意してください。 "???"の代わりに{self.add_url}になりました。 。
カーリーブレースの間に属性が渡されると、それらは実行時に純粋なPythonと解釈されます。実際、 mixtパーサーはPythonインタープリターを実行する前にファイル全体をPure Pythonに変換するため、同じままになり、HTMLのみが変換されます。したがって、これを行うペナルティはありません。
私たちのコンポーネントが純粋なPythonで書かれている場合、これがどのように見えるかを見てください:
from mixt import Element , html
class TodoForm ( Element ):
class PropTypes :
add_url : str
def render ( self , context ):
return html . Form ( method = 'post' , action = self . add_url )(
html . Label ()(
html . Raw ( "New Todo: " )
),
html . InputText ( name = 'todo' ),
html . Button ( type = 'submit' )(
html . Raw ( "Add" ) # or html.Rawhtml(text="Add")
),
)名前付き引数としてプロップがコンポーネントに渡される方法と、 actionどのように渡されるかに注意してください: action=self.add_url 。
このPure-Pythonコンポーネントは、それがどのように機能するかを示しています。プロップはコンポーネントクラスに引数と呼ばれるものとして渡されます。このコンポーネントは呼び出され、子どもコンポーネントを通話への位置的引数として渡します。
ComponentClass ( prop1 = "val1" , prop2 = "val2" )(
Children1 (),
Children2 (),
)子供は何ですか?子供は他のタグ内のタグです。
in <div id="divid"><span /><p>foo</p></div> 、
idと2人の子供を持つhtml.Divコンポーネント:html.Spanコンポーネントhtml.Pコンポーネント:html.RawHtmlコンポーネント小道具や子供と一緒に遊ぶことができることに注意してください。まず、Pure-Pythonのバージョンがどのように機能するかを示します。
def render ( self , context ):
props = { "prop1" : "val1" , "prop2" : "val2" }
children = [ Children1 (), Children2 ()]
return ComponentClass ( ** props )( * children )
# You can pass a list of children to to the call, so this would produce the same result:
# ComponentClass(**props)(children)次に、 mixtバージョン:
def render ( self , context ):
props = { "prop1" : "val1" , "prop2" : "val2" }
children = [ < Children1 / > , < Children2 / > ]
return < ComponentClass { ** props } > { * children } < / ComponentClass >
# or, the same, passing the children as a list:
# return <ComponentClass {**props}>{children}</ComponentClass> それでは、私たちの小道具add_urlに戻りましょう。
コンポーネントに渡す方法は?
属性をHTMLタグに渡すのとまったく同じ方法:実際、HTMLコンポーネント( mixt.htmlで定義されている)で定義されているプロップです。執筆時点では、HTML5で有効である(非推奨ではない)すべてのHTMLタグをサポートします(控除されたものを除く)。
それで、これをしましょう:
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 >OK、レンダリングされたHTMLには小道具があります。
文字列を渡さない場合はどうなりますか?私たちはPropTypesで、文字列が欲しいと言いました...
試してみましょう:
print ( < TodoForm add_url = 1 / > ) < form method =" post " action =" 1 " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >それは動作します!しかし...それは文字列ではありません!!実際、数字には特別なケースがあります。文字列の代わりに数字として渡すことができ、必要に応じて変換されます...
それでは、何か他のものを試してみましょう。
print ( < TodoForm add_url = True / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > ) PythonでTrueを渡すと同じです
print ( < TodoForm add_url = { True } / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > )わかりました、システムをだまして、 "True"を文字列として渡しましょう。
print ( < TodoForm add_url = "True" / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > )まだ同じですが、ここで私たちは文字列を渡しました!はいですが、それらが見えるものに対して常に評価される4つの値があります。
None )これらの値の1つを文字列として渡す唯一の方法は、文字列としてpythonを介してそれらを渡すことです。
print ( < TodoForm add_url = { "True" } / > ) < form method =" post " action =" True " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >これらの4つの値と数字を除き、属性に渡されるすべての値は、文字列と見なされます。 HTML5のHTMLのように引用符がない場合でも、一部の文字がない文字列に引用符が必須ではありません(スペースなし、no / ...)。
何か他のものを渡すには、巻き毛の装具で価値を囲む必要があります(この場合、巻き毛の中で見積もりは必要ありません。
わかりました、今、私たちは文字列のみを受け入れると確信しています....しかし、何も渡された場合はどうなりますか?そして...「何も」とは何ですか?
Pythonの空の文字列から始めましょう:
print ( < TodoForm add_url = { "" } / > ) < form method =" post " action ="" > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >OKは機能し、文字列が欲しかった、文字列がある。
次に、この空の文字列を直接渡しましょう。
print ( < TodoForm add_url = "" / > ) < form method =" post " action ="" > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >それはまだ文字列であるため、まだ機能します。見積もりを削除してみましょう。
print ( < TodoForm add_url = / > ) mixt . exceptions . GeneralParserError : < mixt parser > Unclosed Tags : < TodoForm >ええ、これは有効なhtmlではありません。では、 =削除しましょう。
print ( < TodoForm add_url / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > )何?はい、 requiredなHTML5属性について考えてみTrueくださいcheckedしたがって、属性に価値がない場合、それはブール値であり、それはTrue 。
値を渡さないことに加えて、これら2つの他の方法は、html5でブール値がTrueに及ぶために有効です。
required=""required="required"あなたの便宜のために、私たちは別の方法を追加しました:
True (ケースは重要ではありません)、pythonまたは文字列として: required=True 、 required={True} 、 required="true"そして、そのカウンターパート、 Falseを渡す:
False (ケースは重要ではありません): 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 >そのため、インスタンスを作成できますが、レンダリング時に失敗します。しかし、それを防ぐ方法があります。
デフォルトでは、すべてのプロパティはオプションです。そして、そのためにPython typingモジュールのOptionalタイプを使用する必要はありません。各プロップに対してそれを行うのは面倒です。
代わりに、 mixt Optionnalと同じ方法で使用するRequired名前の型を提供します。
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' > )コンポーネントでやりたいと思うもう1つのことは、ラベルを構築して、TODOの「タイプ」を渡すが、選択肢を制限することです。このためには、 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プロップを作成して、 typeプロップに基づいてURLを計算する関数を受け入れます。ただし、文字列を許可し、デフォルト値を使用してもらいたいと考えています。
タイピングでそれを行うことができます。私たちの関数は、文字列、 typeを取り、文字列、URLを返します。
Union 、構文は呼び出しCallable Callable[[str], str]で呼ば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モードでは、コンポーネントに渡すと小道具が検証されます。 「dev-mode」にない場合、検証はスキップされます。したがって、生産中に、開発者モードを無効にすることができます(1分でどのように見てみましょう)。
Choices小道具が実際に選択肢のリストにあるかどうかを確認しませんrender方法では理解できる奇妙なことが起こります。しかし、あなたはそれが生産中であると言うかもしれません。確かに。しかし、もちろん、あなたのコードはテストで完全にカバーされており、Dev-Modeで実行されているため、本番環境ではこの検証は必要ありません。ちなみに、 NODE_ENV=productionで反応がどのように機能するかに注意してください。
Dev-Modeを変更する方法は?環境変数を強制しませんが、いくつかの機能を提案します。それらを呼ぶのはあなた次第です:
from mixt import set_dev_mode , unset_dev_mode , override_dev_mode , in_dev_mode
# by default, dev-mode is active
assert in_dev_mode ()
# you can unset the dev-mode
unset_dev_mode ()
assert not in_dev_mode ()
# and set it back
set_dev_mode ()
assert in_dev_mode ()
# set_dev_mode can take a boolean
set_dev_mode ( False )
assert not in_dev_mode ()
set_dev_mode ( True )
assert in_dev_mode ()
# and we have a context manager to override for a block
with override_dev_mode ( False ):
assert not in_dev_mode ()
with override_dev_mode ( True ):
assert in_dev_mode ()
assert not in_dev_mode ()
assert in_dev_mode ()それでは、 type Propでこれを試してみましょう。覚えておいてください、それは次のように見えます:
type : DefaultChoices = [ 'todo' , 'thing' ]私たちは、最初にDev-Modeで別の選択肢に合格しようとします。
with override_dev_mode ( True ):
print ( < TodoForm type = "stuff" / > ) mixt . exceptions . InvalidPropChoiceError : < TodoForm > . type : `stuff` is not a valid choice for this prop ( must be in [ 'todo' , 'thing' ])予想通りに失敗します。
そして今、Dev-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にもあるTODOタイプがあります。それはあなたが無効な小道具を決して渡さないようにするためのあなたのテストの作業です。したがって、あなたは生産に自信を持ち、開発モードを非アクティブ化することができます。
今、私たちは私たちのフォームを持っています。 TODOリストアプリには、他にどのようなコンポーネントが必要ですか?
もちろん、TODOエントリを表示する方法が必要です。
しかし、TODOエントリとは何ですか?基本的な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 >今、私たちはトドスのリストを望んでいます。 TodoObjectのリストを提案として受け入れるTodoListコンポーネントを作成しましょう。
しかし、 render方法でHTMLタグのみを使用する他の2つのコンポーネントとは異なるものは、コンポーネントを別のコンポーネントにカプセル化するようになります。方法を見てみましょう。
class TodoList ( Element ):
class PropTypes :
todos : Required [ List [ TodoObject ]]
def render ( self , context ):
return < ul > {[ < Todo todo = { todo } / > for todo in self . todos ]} < / ul >はい、それはそれと同じくらい簡単です:htmlタグを使用するように、 Todoコンポーネントに<Todo...>を使用します。唯一の違いは、HTMLタグの場合、それらを直接インポートする必要がないこと( mixtからの単純なhtmlをインポート)であり、慣習により小文字で書きます。通常のコンポーネントの場合、それらをインポートする必要があります( from mylib import componentsと<components.MyComponent ...> )。正確なケースを使用する必要があります。
リストをどのように要求し、巻き毛のリストを介して<ul>に渡したかに注目してください。
必要に応じて違ったやり方で行うことができます。
リストの理解をHTMLから分離するように:
def render ( self , context ):
todos = [
< Todo todo = { todo } / >
for todo
in self . todos
]
return < ul > { todos } < / ul >または専用の方法で(テストに役立ちます):
def render_todos ( self , todos ):
return [
< Todo todo = { todo } / >
for todo
in todos
]
def render ( self , context ):
return < ul > { self . render_todos ( self . todos )} < / ul >それはあなた次第です:最後にそれはただのパイソンです。
このコンポーネントによってレンダリングされているものを見てみましょう。
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 >そして、それだけです、私たちは私たちのTodoListアプリを持っています!ページで使用するには、HTMLベースマークアップをレンダリングし、 TodoAppコンポーネントを統合するコンポーネントを作成するだけです。コンポーネントさえ必要ありません:
todos = [ TodoObject ( "foo" ), TodoObject ( "bar" ), TodoObject ( "baz" )]
print (
< html >
< body >
< TodoApp todos = { todos } type = "thing" / >
< / body >
< / html >
)美化された出力は次のとおりです。
< html >
< body >
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul >
< li > foo </ li >
< li > bar </ li >
< li > baz </ li >
</ ul >
</ div >
</ body >
</ html >一般的なTODOリストがありますが、利用可能なタイプのTODOに従って、「TodoList」と「Thing-List」が必要になる場合があります。
私たちのTodoAppはデフォルトでtodoの一種があるため、すでにTODOリストがあります。
それでは、 ThingAppを作成しましょう。
これを行う最初の方法は、 TodoAppから継承することです。しかし、継承することにより、親から小道具を削除することはできません(それは本当に真実ではありません、後でこれを見るでしょう)ので、デフォルトではまだtype小道具があります。しかし、私たちは「物」以外を受け入れたくありません。したがって、このようなtype小道具を再定義できます。
class ThingApp ( TodoApp ):
class PropTypes :
type : DefaultChoices = [ 'thing' ]このコンポーネントを使用しましょう。
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form method =" post " action =" /thing/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form > < ul > < li > foo </ li > </ ul > </ div > type小道具に「todo」に合格しようとすると、それは機能しません。
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' ])しかし、それでも、タイプを渡すことができるのは奇妙です。
別の方法を試してみましょう:親コンポーネント。子供と一緒に物事をしてそれを返すことを他に何もしないコンポーネント。ここで私たちが望んでいるのは、「物」を強制されたtype小道具でTodoAppを返すコンポーネントです。
これをしましょう
class ThingApp ( Element ):
class PropTypes :
todos : Required [ List [ TodoObject ]]
def render ( self , context ):
return < TodoApp todos = { self . todos } type = "thing" / > print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form method =" post " action =" /thing/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form > < ul > < li > foo </ li > </ ul > </ div >それは機能します、そして今回は、私たちはtypeプロップに合格することはできません:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) mixt . exceptions . InvalidPropNameError : < ThingApp > . type : is not an allowed prop todos小道具のタイプをどのように定義しなければならなかったかに注目してください。 TodoAppとTodoThingの両方。
それを処理する方法はたくさんあります。
最初のものは、 TodoAppでチェックされるため、 ThingAppのタイプを無視することです。したがって、 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タグを返すだけです。クラスコンポーネントの1つの違いは、 PropTypesがないため、検証がないことです。しかし...それはまさに私たちが必要とするものです。
私たちのThingAppいくつかの小道具( todos Prop)を受け入れ、特定のtype PropでTodoAppを返してほしい。
だから私たちはできました:
def ThingApp ( todos ):
return < TodoApp type = "thing" todos = { todos } / >ここでは、 typeをThingsAppに渡すことができないことがわかりますが、それは有効な引数ではありません。
試してみましょう:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > </ ul > </ div >ここでは、パスするプロップが1つしかないので、簡単です。しかし、私たちがたくさん持っていると想像してください。 {**props}構文を使用できます。
def ThingApp ( ** props ):
return < TodoApp type = "thing" { ** props } / >そして、あなたはさらに少ない文字でできることをすることができます(それが重要な場合):
ThingApp = lambda ** props : < TodoApp type = "thing" { ** props } / >これらの2つのフォネーションはまったく同じ動作をします。
Pythonエラーになるため、 TodoAppに2回渡されるため、 typeプロップに合格することはできません。
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} type = "thing" / > ) TypeError : BaseMetaclass object got multiple values for keyword argument 'type' (はい、それは私たちのコンポーネントクラスを作成するメタラスであるBaseMetaclassについて語っています)
そして、他の間違った小道具はTodoAppによって検証されます。
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} foo = "bar" / > ) mixt . exceptions . InvalidPropNameError : < TodoApp > . foo : is not an allowed propこれを念頭に置いて、 typeプロップを受け入れるコンポーネントのタイプを強制する一般的な機能を作成できたはずです。
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のみ)が正しく渡されます。
この概念をより一般的なケース「高次コンポーネント」に拡張します。 Reactの「高次コンポーネント」は、「コンポーネントを取り、新しいコンポーネントを返す関数」です。
アイデアは次のとおりです。
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ここで2つの重要なこと:
__exclude__ = {'type'}使用方法に注意して、 WrappedComponent.PropTypesから継承するものからtypeプロップを削除します。したがって、返されたコンポーネントは、 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に加えて2つの引数を取ります。
prop_name :いくつかのデータを入力するのは、ラップされたコンポーネントの小道具の名前ですget_source :それはデータを取得するために呼び出される関数ですラップされたコンポーネントからPropTypesどのように継承したか、およびprop_nameを除外する方法を見てください。したがって、データを新しいコンポーネントに渡すことはありません(そしてできません)。
そして、 renderでは、 get_sourceへの呼び出しの結果、 WrappedComponentに渡すプロップを設定します。
それでは、小道具とコンテキストを取り、データを返す非常に単純な機能(これはキャッシュ、フィルタリング...)を記述しましょう。
def get_todos ( props , context ):
# here it could be a call to a database
return [
TodoObject ( "fooooo" ),
TodoObject ( "baaaar" ),
]そして、私たちは自分のコンポーネントを構成することができます:
SourcedTodoApp = from_data_source ( TodoApp , 'todos' , get_todos )
ThingApp = thingify ( SourcedTodoApp )そしてそれを実行します:
print ( < ThingApp / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > fooooo </ li > < li > baaaar </ li > </ ul > </ div >予想通りに機能し、データはコンポーネントをレンダリングする必要がある場合にのみフェッチされます。
そのため、外部ソースからデータを取得できるTODOリストがあります。ただし、ユーザーによってデータを異なる場合があります。
私たちにできることは、メインレベルにあり、ユーザーを取得し、すべてのコンポーネントを渡して、各コンポーネントが現在のログインをユーザーに取得できるようにします。
面倒ではないでしょうか?
このユースケースを解決することは、 mixtが提供するContextコンセプトの正確な目的です。もちろん、それは反応の文脈の概念に触発されています。
そして彼らが言ったように:
コンテキストは、現在の認証されたユーザー、テーマ、優先言語など、反応コンポーネントのツリーについて「グローバル」と見なすことができるデータを共有するように設計されています。
コンテキストを作成することは、コンポーネントを作成するのと同じくらい簡単ですが、 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 >ユーザーのTODOエントリを見ることができます1。
ユーザー2で試してみましょう2:
print (
< UserContext authenticated_user_id = 2 >
< ThingApp / >
< / UserContext >
) < div > < h1 > The "thing" list < / h1 > < form > ... < / form > < ul > < li > 2 - 1 < / li > < li > 2 - 2 < / li > < / ul > < / div >ユーザー2のTODOエントリを見ることができます2。
この場合、もちろん、ユーザーIDを小道具として渡すことができました。しかし、TODOアプリがコンポーネントツリーの奥深くにあることを想像してください。この方法で合格する方がずっと簡単です。
しかし、Reactのドキュメントで述べたように:
小道具を数レベル下げないようにコンテキストを使用しないでください。複数のレベルの多くのコンポーネントで同じデータにアクセスする必要がある場合に固執します。
コンテキストがない場合、 renderメソッドのcontext引数は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にアイテムを追加するときは、リストに追加しましょう。
最初に、 TodoFormコンポーネントにrender_javascriptメソッドを追加します(悪い、私たちはより良くすることができますが、それはポイントではありません)JavaScript:
class TodoForm ( Element ):
# ...
def render_javascript ( self , context ):
return html . Raw ( """
function on_todo_add_submit(form) {
var text = form.todo.value;
alert(text);
}
""" )開始するには、新しいTODOテキストのみを表示します。
このJavaScriptを返すように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タグに注意してください。それは、反応のように、返される多くの要素をカプセル化する方法です。それは簡単なリストかもしれませんが、最後にcom睡状態があります:
return [
< script > ... < / script > ,
< form >
...
< / form >
]次に、リストにアイテムを追加したいと思います。これを行うのはTodoFormの役割ではなく、リストにあります。そのため、 TodoListコンポーネントにJSを追加します。テキストを取り、新しいエントリを作成する関数です。
TodoFormについては、(まだ悪い)JavaScriptを使用してrender_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 )) javaScriptで使用されるulタグに<script>タグとidを追加するように、 render方法を更新します。
class TodoList ( Element ):
# ...
def render ( self , context ):
return
< Fragment >
< script > { self . render_javascript ( context )} < / script >
< ul id = "todo-items" > {[ < Todo todo = { todo } / > for todo in self . todos ]} < / ul >
< / Fragment >そして、 TodoFormコンポーネントのrender_javascriptメソッドを更新して、新しいadd_toto javascript関数を使用できます。
class TodoForm ( Element ):
# ...
def render_javascript ( self , context ):
return html . Raw ( """
function on_todo_add_submit(form) {
var text = form.todo.value;
add_todo(text);
}
""" )そして、それだけです。実際、特別なことは何もありません。
しかし、OU TodoAppの出力を見てみましょう。
print (
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / User >
)美化された出力は次のとおりです。
< div >
< h1 > The "thing" list </ h1 >
< script >
function on_todo_add_submit ( form ) {
var text = form . todo . value ;
add_todo ( text ) ;
}
</ script >
< form method =" post " action =" /thing/add " onsubmit =" return on_todo_add_submit(this); " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< script >
TODO_TEMPLATE = "<li>placeholder</li>" ;
function add_todo ( text ) {
var html = TODO_TEMPLATE . replace ( "placeholder" , text ) ;
var ul = document . querySelector ( '#todo-items' ) ;
ul . innerHTML = html + ul . innerHTML ;
}
</ script >
< ul id =" todo-items " >
< li > 1-1 </ li >
< li > 1-2 </ li >
</ ul >
</ div >したがって、 scriptタグがたくさんあります。 1つしか持っていないのは素晴らしいことかもしれません。
mixt 、レンダリングされたものの一部を他の場所に置く方法を「収集」する方法があります。コンポーネントとして使用される2つのシンプルなコレクターがあります: 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タグに置き換えましょう。
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"使用したためです。別の可能性は、 JSCollector render_position="before"です。
このすべては、 CSSCollectorタグとまったく同じように機能します。このタグでは、コンテンツが<style type="text/css>タグに入れられます。
JS/CSSを使用することはHTMLの世界では非常に一般的であるため、これらすべてをさらに簡単にするために砂糖を追加しました。
render_jsメソッドがある場合、 JSCollectorこのメソッドの結果を自動的に収集します。 CSSSelectorおよびrender_cssメソッドについても同じです。
これにより、 JSCollector.Collectタグは必要ありません。
私たちの例で、この作品をTodoFormとTodoListで行うために:
JSCollector.Collectタグを削除しますFragmentタグを削除しますrender_javascriptメソッドをrender_jsに変更します。render_js自体を呼び出すときは必要ないため、 render_jsでhtml.Rawへの呼び出しを削除します。出力が文字列の場合、「生」のものと見なされますこれにより、まったく同じ結果が得られます。
render_jsメソッドを持つ子供のインスタンスが1つしかないため、今では機能します。
しかし、多くの子供がいる場合、この方法は各子供に対して呼び出されます。実際の場合、このインスタンスに非常に固有のコードのみを含める必要があります。
コンポーネントクラスでJS/CSSを1回だけ提供するには、 render_js_globalまたはrender_css_global ( classmethodになると予想される)を使用する必要があります。
render_jsメソッドを収集する前に、初めて収集され、初めてのインスタンスが見つかります。
したがって、ここでは、 render_js render_js_globalに変更し、 @classmethodで飾ることができますが、それでも同じように機能します。
JavaScriptまたはスタイルを再編成することができます。しかし、 headやbodyタグの終わりのように、他の場所に置きたい場合はどうでしょうか?
参照、別名「参照」で可能です。もちろん、DOMの一部なしでは、Reactと同じコンテキストです。
refを作成し、コンポーネントに渡すと、どこでも使用できます。
これを行うためにメインコードを更新しましょう。
最初にrefを作成します。
from mixt import Ref
js_ref = Ref ()これにより、コンポーネントへの参照を保持する新しいオブジェクトが作成されます。コンポーネントでは、 Refをインポートする必要はなく、 js_ref = self.add_ref()を使用できますが、ここではコンポーネントにはありません。
refを節約するために、それをref propに渡すだけです:
< JSCollector ref = { js_ref } > ... < / JSCollector > render_position小道具を削除したことに注意してください。これは、タグの前後にJSを配置したくないので、他の場所ではありません。
refが参照するコンポーネントにアクセスするには、 current属性を使用します。
js_collector = js_ref . currentもちろん、これはレンダリング後にのみ行うことができます。
これを使用して、 headにscriptタグを追加するにはどうすればよいですか。
最初にHTMLを更新して、クラシックhtml 、 head 、 bodyタグを含めます。
return str (
< html >
< head >
< / head >
< body >
< JSCollector ref = { js_ref } >
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / UserContext >
< / JSCollector >
< / body >
< / html >
)この時点で、出力にscriptタグがありません。
< html >
< head > </ head >
< body >
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " onsubmit =" return on_todo_add_submit(this); " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul id =" todo-items " >
< li > 1-1 </ li >
< li > 1-2 </ li >
</ ul >
</ div >
</ body >
</ html >最初に知っておくべきこと:コレクターは、 render_collectedメソッドを呼び出すことで収集したすべてのものをレンダリングすることができます。
そして、それにはすでにscriptタグが含まれていることを思い出してください、私たちはこれを行いたいかもしれません:
# ...
< head >
{ js_ref . current . render_collected ()}
< / head >
# ...しかし、これはうまくいきません:
AttributeError : 'NoneType' object has no attribute 'render_collected'それは、レンダリング時に現在の値にアクセスしようとするためです。それは後に行う必要があります。
このためには、 mixtの機能を使用できます。ツリーに追加されたものが呼ばれる場合、文字列に変換するときにレンダリング後に呼び出されます。
したがって、たとえばラムダを使用できます。
# ...
< head >
{ lambda : js_ref . current . render_collected ()}
< / head >
# ...そして今、それは動作します:
< html >
< head >
< script type =" text/javascript " >
function on_todo_add_submit ( form ) {
var text = form . todo . value ;
add_todo ( text ) ;
}
TODO_TEMPLATE = "<li>placeholder</li>" ;
function add_todo ( text ) {
var html = TODO_TEMPLATE . replace ( "placeholder" , text ) ;
var ul = document . querySelector ( '#todo-items' ) ;
ul . innerHTML = html + ul . innerHTML ;
}
</ script >
</ head >
< body >
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " onsubmit =" return on_todo_add_submit(this); " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul id =" todo-items " >
< li > 1-1 </ li >
< li > 1-2 </ li >
</ ul >
</ div >
</ body >
</ html >私たちはそれを作りました! mixtの主な機能はすべて説明しました。これで、 mixt自分のプロジェクトで使用できます。
次のステップとして、APIドキュメントをお読みください。