اكتب مكونات 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 >إذا كنت لا ترغب في كتابة HTML في Python ، فلا يزال بإمكانك استخدامه:
from mixt import html , Element , Required
class Hello ( Element ):
class PropTypes :
name : Required [ str ]
def render ( self , context ):
return html . Div ()( "Hello, " , self . name )
print ( Hello ( name = "World" ))نعم ، إنها مستوحاة من React (في الواقع ، بشكل أساسي JSX) ونقترض بعض المفهوم:
وأضفنا:
تشغيل هذين الأوامر. والثاني سيخبر Python كيفية فهم الملفات مع HTML في الداخل.
pip install mixt
mixt-post-installللتحقق من أن كل شيء جاهز ، قم بالتشغيل:
python -m mixt.examples.simpleيجب أن يكون لديك هذا المخرج:
< div title =" Greeting " > Hello, World </ div > إذا كنت لا ترغب في استخدام الأشياء HTML-in-Python ، فلا تقوم بتشغيل mixt-post-install . ثم اختبر مع (للحصول على نفس الإخراج):
python -m mixt.examples.simple_pure_pythonاستنساخ مشروع git ثم:
make devللتحقق من أن كل شيء جاهز ، قم بالتشغيل:
python -m mixt.examples.simpleيجب أن يكون لديك هذا المخرج:
< div title =" Greeting " > Hello, World </ div >بعد القيام ببعض الكود:
make testsmake lint إذا لم تلمس الأشياء في دليل codec ، فسيتعين عليك تشغيل make dev (أو على الأقل make full-clean ) لتطهير ملفات pyc Python.
لاحظ أن CI سوف تحقق من أن كل الالتزام يمرر make lint ، make tests make check-doc . لذلك لا تنس تشغيل هذه لكل الالتزام.
طريقة واحدة للقيام بذلك قبل الدفع هي:
git rebase develop --exec ' git log -n 1; make checks ' ملاحظة: يمكنك العثور على الكود النهائي لدليل المستخدم هذا في src/mixt/examples/user_guide (ستجد mixt.py و pure_python.py ).
قم بتشغيله مع:
python -m mixt.examples.user_guideدعنا ننشئ قائمة ... نعم!
ولكن من قبل ، تذكر. هذا ليس رد فعل ، إنه ليس على المتصفح ولا يوجد جافا سكريبت هنا. نتحدث فقط عن تقديم بعض 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 >عند طباعة المكون ، سيعطي هذان الشخصان نفس النتيجة:
print ( < TodoForm / > ) < form method =" post " action =" ??? " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >لاحظ كيف يتم تنسيقه: لا توجد مساحة بين العلامات. في الواقع ، يبدو الأمر كما في JSX:
JSX يزيل المسافة البيضاء في بداية وإنهاء الخط. كما أنه يزيل خطوط فارغة. تتم إزالة الخطوط الجديدة المجاورة للعلامات ؛ يتم تكثيف الخطوط الجديدة التي تحدث في منتصف الأسلاك الحرفية إلى مساحة واحدة
لإضافة مساحة ، أو سطر جديد ، يمكنك تمرير بعض الثعبان. دعنا ، على سبيل المثال ، أضف سطرًا جديدًا قبل الملصق:
#...
< form method = "post" action = "???" >
{ ' n ' } < label > New Todo : < / label > < itext name = "todo" / >
#...الآن لدينا هذا المخرج:
< form method =" post " action =" /todo/add " >
< label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >الآن دعنا نذهب إلى أبعد من ذلك.
لاحظ سمة action للنموذج. نحن بحاجة إلى تمرير شيء ما. لكن الترميز الصلب لا يبدو صحيحًا. WWE تحتاج إلى تمريره إلى المكون.
يحتوي Mixt ، مثل React ، على مفهوم الخصائص ، ويعرف أيضًا باسم "الدعائم".
في Mixt ، نحددها بنوع ، في فئة داخل مكوننا ، المسماة PropTypes :
class TodoForm ( Element ):
class PropTypes :
add_url : str
def render ( self , context ):
return
< form method = "post" action = { self . add_url } >
< label > New Todo : < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form > هنا حددنا دعامة تسمى add_url والتي يجب أن تكون سلسلة ( str ). هذا يستخدم بناء جملة طباعة بيثون.
ولاحظ كيف غيرنا سمة action لعلامة form . إنه الآن {self.add_url} بدلاً من "???" .
عندما يتم تمرير السمات بين الأقواس المجعد ، يتم تفسيرها على أنها ثعبان نقي في وقت التشغيل. في الواقع ، نظرًا لأن محلل 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 .
يوضح لك هذا المكون النقي Python أيضًا كيف يعمل: يتم تمرير الدعائم كوسيطة مسماة إلى فئة المكون ، ثم يُطلق على هذا المكون ، تمرير مكونات الأطفال كوسائط موضعية إلى المكالمة:
ComponentClass ( prop1 = "val1" , prop2 = "val2" )(
Children1 (),
Children2 (),
)ما هم الأطفال؟ الأطفال علامات داخل علامات أخرى.
في <div id="divid"><span /><p>foo</p></div> ، لدينا:
html.Div ، مع معرف id وطفلين:html.Span ، بدون أطفالhtml.P مع طفل واحد:html.RawHtml مع النص "foo"لاحظ أنه يمكنك اللعب مع الدعائم والأطفال. أولا الإصدار في Pure-Python لإظهار كيف يعمل:
def render ( self , context ):
props = { "prop1" : "val1" , "prop2" : "val2" }
children = [ Children1 (), Children2 ()]
return ComponentClass ( ** props )( * children )
# You can pass a list of children to to the call, so this would produce the same result:
# ComponentClass(**props)(children) ثم نسخة mixt :
def render ( self , context ):
props = { "prop1" : "val1" , "prop2" : "val2" }
children = [ < Children1 / > , < Children2 / > ]
return < ComponentClass { ** props } > { * children } < / ComponentClass >
# or, the same, passing the children as a list:
# return <ComponentClass {**props}>{children}</ComponentClass> الآن دعنا نعود إلى الدعائم لدينا add_url .
كيف تمررها إلى المكون؟
بنفس الطريقة التي مررنا بها بالسمات إلى علامات HTML: فهي في الواقع محددة في كومبومات HTML (محددة في mixt.html ). نحن ندعم كل علامات HTML التي ، في وقت الكتابة ، صالحة (غير مستغلة) في HTML5 ، بسماتها (باستثناء تلك التي تم إهمالها).
لذلك دعونا نفعل هذا:
print ( < TodoForm add_url = "/todo/add" / > ) < form method =" post " action =" /todo/add " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >حسنًا ، لدينا دعامة لدينا في HTML المقدمة.
ماذا لو لم نمر سلسلة؟ قلنا في PropTypes أننا أردنا سلسلة ...
لنجربها:
print ( < TodoForm add_url = 1 / > ) < form method =" post " action =" 1 " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >إنها تعمل! لكن ... إنها ليست سلسلة !! في الواقع ، هناك حالة خاصة للأرقام ، يمكنك تمريرها كأرقام بدلاً من الأوتار ويتم تحويلها إذا لزم الأمر ...
لذلك دعونا نجرب شيئًا آخر.
print ( < TodoForm add_url = True / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > ) وهذا هو نفسه إذا True في بيثون
print ( < TodoForm add_url = { True } / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > ) حسنًا ، دعنا نخدع النظام ونمر "True" ، كسلسلة.
print ( < TodoForm add_url = "True" / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > )لا يزال نفس الشيء ، ولكن هنا مررنا سلسلة! نعم ولكن هناك 4 قيم يتم تقييمها دائمًا على ما يبدو أنها:
None )الطريقة الوحيدة لتمرير إحدى هذه القيم كسلسلة ، هي تمريرها عبر Python ، كسلسلة:
print ( < TodoForm add_url = { "True" } / > ) < form method =" post " action =" True " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form > باستثناء هذه القيم الأربعة والأرقام ، تعتبر كل قيمة يتم تمريرها إلى سمة سلسلة. حتى لو لم يكن هناك عروض أسعار ، كما هو الحال في HTML في HTML5 ، حيث لا تكون الاقتباسات إلزامية للسلاسل بدون بعض الشخصيات (لا توجد مسافات ، لا / ...).
لتمرير شيء آخر ، يجب أن تحيط بالقيمة في أقواس مجعد (وفي هذه الحالات ليست هناك حاجة إلى عروض أسعار حول الأقواس المجعد.
حسنًا ، نحن الآن متأكدون من أننا نقبل فقط السلسلة .... ولكن ماذا لو لم أقم بأي شيء؟ و ... ما هو "لا شيء"؟
لنبدأ بسلسلة فارغة في بيثون:
print ( < TodoForm add_url = { "" } / > ) < form method =" post " action ="" > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >حسنًا ، لقد أردنا سلسلة ، لدينا سلسلة.
الآن دعنا نمر هذه السلسلة الفارغة مباشرة:
print ( < TodoForm add_url = "" / > ) < form method =" post " action ="" > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >لا يزال يعمل ، لأنه لا يزال سلسلة. دعنا نزيل الاقتباسات ، لنرى.
print ( < TodoForm add_url = / > ) mixt . exceptions . GeneralParserError : < mixt parser > Unclosed Tags : < TodoForm > همهمة نعم ، هذا غير صالح HTML. لذلك دعونا نزيل = :
print ( < TodoForm add_url / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > ) ماذا؟ نعم ، فكر في سمات HTML5 مثل required ، checked ... فهي بحاجة فقط إلى أن تكون حاضرة كسمة ، دون قيمة ، لاعتبارها True . لذلك عندما لا يكون للسمة أي قيمة ، فهي منطقية ، وهذا True .
بالإضافة إلى عدم تمرير قيمة ، فإن هاتين الطريقتين الأخريين صالحة في HTML5 لـ Boolean by True :
required=""required="required"لراحتك ، أضفنا طريقة أخرى:
True (لا يهم الحالة) ، مثل python أو كسلسلة: required=True ، required={True} ، required="true" ونظيره ، لتمرير False :
False (لا يهم الحالة) ، مثل python أو كسلسلة: required=False ، required={False} ، required="false" موافق للسمات المنطقية. إنها ليست حالتنا. آخر شيء يمكننا القيام به هو عدم تعيين السمة على الإطلاق:
print ( < TodoForm / > )
# this is the same: ``print(<TodoForm add_url=NotProvided />)```
# (``NotProvided`` must be imported from ``mixt``) mixt . exceptions . UnsetPropError : < TodoForm > . add_url : prop is not setإنه أمر مفهوم: نحاول الوصول إلى دعامة لم يتم تعيينها ، بالطبع لا يمكننا استخدامه.
ولكن ماذا لو لم نصل إليه؟ إذا لم نطبع المكون ، فلن يتم تقديمه:
< TodoForm / > < TodoForm at 0x7fbd18ea5630 >حتى نتمكن من إنشاء مثيل ولكنه سيفشل في وقت العرض. ولكن هناك طريقة لمنع ذلك.
بشكل افتراضي ، جميع الخصائص اختيارية. وليس عليك استخدام النوع Optional من وحدة typing Python لذلك ، سيكون من المرهق القيام بذلك لكل دعامة.
بدلاً من ذلك ، يوفر mixt نوعًا مسماً Required استخدامك بنفس الطريقة من Optionnal .
from mixt import Element , Required , html
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
def render ( self , context ):
# ...لذلك قلنا للتو أننا أردنا سلسلة ، وأنه مطلوب.
لنحاول مرة أخرى إنشاءه بدون الدعامة:
< TodoForm / > mixt . exceptions . RequiredPropError : < TodoForm > . add_url : is a required prop but is not setالآن لدينا الاستثناء الذي أثير في وقت سابق في برنامجنا.
لرؤية إمكانيات أخرى في الدعائم ، دعنا نضيف واحدة جديدة لتغيير ملصق النص. لكننا لا نريد أن نجعلها مطلوبة ، وبدلاً من ذلك لدينا قيمة افتراضية.
لهذا ، يكون الأمر سهلاً مثل إضافة قيمة إلى الدعامة في فئة PropTypes :
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
label : str = 'New Todo'
def render ( self , context ):
return
< form method = "post" action = { self . add_url } >
< label > { self . label }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >الآن دعنا نجربها دون تمرير الدعامة:
print ( < TodoForm add_url = "/todo/add" / > ) < form method =" post " action ="" > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >وإذا مررنا واحدة:
print ( < TodoForm add_url = "/todo/add" label = "Thing to do" / > ) < form method =" post " action =" /todo/add " > < label > Thing to do: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >إنه يعمل كما هو متوقع.
لاحظ أنه لا يمكنك إعطاء قيمة افتراضية أثناء وجود دعامة Required . لا معنى له ، لذلك يتم فحصه في أقرب وقت ممكن ، بينما يتم إنشاء class :
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
label : Required [ str ] = 'New Todo' mixt . exceptions . PropTypeRequiredError : < TodoForm > . label : a prop with a default value cannot be requiredوبالطبع يجب أن تتطابق القيمة الافتراضية مع النوع!
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
label : str = { 'label' : 'foo' } mixt . exceptions . InvalidPropValueError :
< TodoForm > . label : `{'label': 'foo'}` is not a valid value for this prop ( type : < class 'dict' > , expected : < class 'str' > ) شيء آخر نريد القيام به في مكوننا هو السماح لها ببناء الملصق ، وتمريره "نوع" من 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 لقبول وظيفة من شأنها حساب عنوان URL لنا استنادًا إلى type Prop. لكننا نريد أيضًا السماح بسلاسل ، وذات قيمة افتراضية.
يمكننا أن نفعل ذلك ، مع الكتابة. ستأخذ وظيفتنا سلسلة ، type وسوف تُرجع سلسلة ، عنوان URL.
لذا فإن بناء الجملة Callable[[str], str] للاستدعاء ، ونحن نستخدم Union لقبول الأشياء من النوع Callable أو str :
from typing import Union , Callable
from mixt import DefaultChoices , Element , Required , html
class TodoForm ( Element ):
class PropTypes :
add_url : Union [ Callable [[ str ], str ], str ] = "/todo/add"
type : DefaultChoices = [ 'todo' , 'thing' ]
def render ( self , context ):
if callable ( self . add_url ):
add_url = self . add_url ( self . type )
else :
add_url = self . add_url
return
< form method = "post" action = { add_url } >
< label > New { self . type }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form > أولاً ، دعنا نجربها بدون Prop 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" ، يتم تخطي التحقق من الصحة. لذا في الإنتاج ، يمكنك إلغاء تنشيط وضع dev (سنرى كيف في دقيقة واحدة) ونمر الدعائم بسرعة كبيرة:
Choices الدعامة هي ، في الواقع ، في قائمة الخياراتrender الخاصة بك. ولكن قد تقول أنه في الإنتاج أن التحقق من الصحة أمر مهم. بالفعل. ولكن بالطبع ، يتم تغطية الكود الخاص بك بالكامل باختبارات ، وتديرها في وضع dev-mode ، وبالتالي في الإنتاج ، لا تحتاج إلى هذا التحقق من الصحة! ولاحظ أن كيف يعمل React ، بالمناسبة ، باستخدام NODE_ENV=production .
كيفية تغيير وضع dev؟ نحن لا نفرض أي متغير بيئة ولكننا نقترح بعض الوظائف. الأمر متروك لك للاتصال بهم:
from mixt import set_dev_mode , unset_dev_mode , override_dev_mode , in_dev_mode
# by default, dev-mode is active
assert in_dev_mode ()
# you can unset the dev-mode
unset_dev_mode ()
assert not in_dev_mode ()
# and set it back
set_dev_mode ()
assert in_dev_mode ()
# set_dev_mode can take a boolean
set_dev_mode ( False )
assert not in_dev_mode ()
set_dev_mode ( True )
assert in_dev_mode ()
# and we have a context manager to override for a block
with override_dev_mode ( False ):
assert not in_dev_mode ()
with override_dev_mode ( True ):
assert in_dev_mode ()
assert not in_dev_mode ()
assert in_dev_mode () لذلك دعونا نجرب هذا مع 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 > إنه يعمل ، ولدينا نوع todo لم يكن في خياراتنا المستخدمة ، وهو في action أيضًا. إنه عمل اختباراتك للتأكد من أنك لا تمر أبدًا بدعائم غير صالحة ، بحيث يمكنك أن تكون واثقًا في الإنتاج وإلغاء تنشيط وضع dev.
الآن لدينا شكلنا. ما هي المكونات الأخرى التي نحتاجها لتطبيق قائمة 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 > الآن نريد أن يكون لدينا قائمة من Todos. دعنا ننشئ مكونًا TodoList والذي سيقبل كحصول على قائمة من TodoObject .
ولكن ما يختلف عن مكوننا الآخرين ، اللذين يستخدمان علامات HTML فقط في طريقة render الخاصة بهما ، فإننا الآن سنقوم بتغليف مكون إلى آخر. دعونا نرى كيف.
class TodoList ( Element ):
class PropTypes :
todos : Required [ List [ TodoObject ]]
def render ( self , context ):
return < ul > {[ < Todo todo = { todo } / > for todo in self . todos ]} < / ul > نعم ، الأمر بسيط مثل ذلك: يمكنك استخدام <Todo...> لمكون Todo حيث يمكنك استخدام علامة HTML. الفرق الوحيد هو أنه بالنسبة لعلامات HTML ، لا تحتاج إلى استيرادها مباشرةً (استيراد html البسيط من mixt ) ، وبنتيجة الاتفاقية في حالة أقل. بالنسبة للمكونات العادية ، يجب عليك استيرادها (لا يزال بإمكانك القيام به from mylib import components و <components.MyComponent ...> ) واستخدام الحالة الدقيقة.
لاحظ كيف احتجنا إلى قائمة ، وقمنا بإرسالها إلى <ul> عبر القائمة الفخمة في العشرين المجعد.
يمكنك أن تفعل الأشياء بشكل مختلف إذا أردت.
مثل فصل القائمة الفهم عن HTML:
def render ( self , context ):
todos = [
< Todo todo = { todo } / >
for todo
in self . todos
]
return < ul > { todos } < / ul >أو بطريقة مخصصة (قد تكون مفيدة للاختبار):
def render_todos ( self , todos ):
return [
< Todo todo = { todo } / >
for todo
in todos
]
def render ( self , context ):
return < ul > { self . render_todos ( self . todos )} < / ul >الأمر متروك لك: في النهاية ، إنه مجرد بيثون.
دعونا نرى ما يقدمه هذا المكون:
todos = [ TodoObject ( "foo" ), TodoObject ( "bar" ), TodoObject ( "baz" )]
print ( < TodoList todos = { todos } / > ) < ul > < li > foo </ li > < li > bar </ li > < li > baz </ li > </ ul > وأخيراً ، لدينا مكون TodoApp الذي يلف النموذج والقائمة:
class TodoApp ( Element ):
class PropTypes :
todos : Required [ List [ TodoObject ]]
type : DefaultChoices = [ 'todo' , 'thing' ]
def render ( self , context ):
return
< div >
< h1 > The "{self.type}" list < / h1 >
< TodoForm type = { self . type } / >
< TodoList todos = { self . todos } / >
< / div > todos = [ TodoObject ( "foo" ), TodoObject ( "bar" ), TodoObject ( "baz" )]
print ( < TodoList todos = { todos } type = "thing" / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > < li > bar </ li > < li > baz </ li > </ ul > </ div >دعنا نمرر هذا HTML إلى beautifier 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 > وهذا كل شيء ، لدينا تطبيق TODO-List! لاستخدامها في صفحة ، فقط قم بإنشاء مكون من شأنه أن يجعل علامة 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 لأن TodoApp لدينا لديه نوع من todo افتراضيًا.
لذلك دعونا ننشئ ThingApp .
أول طريقة للقيام بذلك هي أن ترث من TodoApp لدينا. ولكن من خلال الوراثة ، لا يمكننا إزالة الدعائم من الوالد (هذا ليس صحيحًا حقًا ، سنرى هذا لاحقًا) ، لذلك لا يزال لدينا type الدعامة افتراضيًا. لكننا لا نريد قبول أي شيء آخر غير "الشيء". حتى نتمكن من إعادة تعريف type الدعامة مثل هذا:
class ThingApp ( TodoApp ):
class PropTypes :
type : DefaultChoices = [ 'thing' ]دعونا نستخدم هذا المكون:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form method =" post " action =" /thing/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form > < ul > < li > foo </ li > </ ul > </ div > إذا حاولنا تمرير "TODO" type الدعائم ، فلن يعمل:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} type = "todo" / > ) mixt . exceptions . InvalidPropChoiceError :
< ThingApp > . type : `todo` is not a valid choice for this prop ( must be in [ 'thing' ])ولكن لا يزال من الغريب أن تكون قادرًا على تمرير النوع.
لنجرب طريقة أخرى: مكون الوالدين. مكون لا يفعل شيئًا آخر يفعل الأشياء مع أطفاله وإعادته. ما نريده هنا ، هو مكون سيعيد TodoApp مع type الدعامة التي أجبرت على "الشيء".
دعونا نفعل هذا
class ThingApp ( Element ):
class PropTypes :
todos : Required [ List [ TodoObject ]]
def render ( self , context ):
return < TodoApp todos = { self . todos } type = "thing" / > print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form method =" post " action =" /thing/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form > < ul > < li > foo </ li > </ ul > </ div > إنه يعمل ، وهذه المرة ، لا يمكننا تمرير type الدعامة:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) mixt . exceptions . InvalidPropNameError : < ThingApp > . type : is not an allowed prop لاحظ كيف كان علينا تحديد نوع الدعائم todos . سواء في TodoApp و TodoThing .
هناك العديد من الطرق للتعامل مع ذلك.
أول واحد هو تجاهل النوع في ThingApp لأنه سيتم فحصه في TodoApp . لذلك سنستخدم النوع Any :
from typing import Any
#...
class ThingApp ( Element ):
class PropTypes :
todos : Any
#...لنجرب قائمة صالحة من Todos:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > </ ul > </ div >ولكن ماذا لو مررنا بشيء آخر؟
print ( < ThingApp todos = "foo, bar" / > ) mixt . exceptions . InvalidPropValueError :
< TodoApp > . todos : `foo, bar` is not a valid value for this prop ( type : < class 'str' > , expected : List [ TodoObject ]) إنه يعمل كما هو متوقع ولكن يتم الإبلاغ عن الخطأ على مستوى TodoApp ، وهو أمر طبيعي تمامًا.
هناك طريقة أخرى تتمثل في تحديد النوع على مستوى أعلى:
TodoObjects = Required [ List [ TodoObject ]]
class TodoApp ( Element ):
class PropTypes :
todos : TodoObjects
# ...
class ThingApp ( Element ):
class PropTypes :
todos : TodoObjects
# ...الآن إذا مررنا بشيء آخر ، فقد تم الإبلاغ عن الخطأ على المستوى الصحيح:
print ( < ThingApp todos = "foo, bar" / > ) mixt . exceptions . InvalidPropValueError :
< TodoThing > . todos : `foo, bar` is not a valid value for this prop ( type : < class 'str' > , expected : List [ TodoObject ]) ولكن إذا لم تتمكن أو لا ترغب في القيام بذلك ، فيمكنك الحفاظ على النوع المحدد في TodoApp et استخدم طريقة فئة prop_type لمكون للحصول على نوع الدعامة:
class ThingApp ( Element ):
class PropTypes :
todos : TodoApp . prop_type ( "todos" )
# ... ولكن هل من المهم حقًا رفع الخطأ لـ ThingApp أو TodoApp ؟ لأنه في النهاية ، TodoApp الذي يجب أن يقوم بالشيك.
لذلك يجب أن تكون هذه طريقة للقيام بذلك بطريقة أكثر عامة ..
رأينا في وقت سابق أن المكون يمكن أن يكون وظيفة واحدة لتقديم مكون. يجب أن تُرجع مكونًا ، علامة HTML. أحد الاختلافات في مكونات الفصل هو أنه لا توجد PropTypes لذا لا يوجد التحقق من الصحة. ولكن ... هذا بالضبط ما نحتاجه.
نريد أن يقبل ThingApp بعض الدعائم ( todos Prop) ، وإرجاع TodoApp مع دعامة type محددة.
لذلك يمكننا أن نفعل:
def ThingApp ( todos ):
return < TodoApp type = "thing" todos = { todos } / > هنا يمكننا أن نرى أنه لا يمكننا نقل type إلى ThingsApp ، فهي ليست وسيطة صالحة.
لنجربها:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > </ ul > </ div > هنا لدينا دعامة واحدة فقط لتمريرها ، لذلك من السهل. لكن تخيل لو كان لدينا الكثير. يمكننا استخدام بناء الجملة {**props} :
def ThingApp ( ** props ):
return < TodoApp type = "thing" { ** props } / >ويمكنك القيام به مع عدد أقل من الشخصيات (إذا كان مهمًا):
ThingApp = lambda ** props : < TodoApp type = "thing" { ** props } / >هذين الفصلين يتصرفان بالضبط نفس الشيء.
ولا يمكنك تمرير دعامة type لأنه سيكون خطأ في الثعبان ، حيث سيتم تمريره مرتين إلى TodoApp :
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} type = "thing" / > ) TypeError : BaseMetaclass object got multiple values for keyword argument 'type' (نعم ، يتحدث عن BaseMetaclass وهو metaclass الذي ينشئ فصول المكونات لدينا)
وسيتم التحقق من صحة أي دعائم خاطئة أخرى من قبل TodoApp :
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} foo = "bar" / > ) mixt . exceptions . InvalidPropNameError : < TodoApp > . foo : is not an allowed prop مع وضع ذلك في الاعتبار ، كان بإمكاننا إنشاء مجموعة عامة تجبر نوع أي مكون يقبل دعامة 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 Prop هو "الشيء" ويتم تمرير الدعائم الأخرى (هنا فقط todos ) بشكل صحيح.
الآن قم بتوسيع هذا المفهوم إلى حالة أكثر عامة: "المكونات ذات الترتيب العالي". في رد فعل "مكون عالي الترتيب" ، هو "وظيفة تأخذ مكونًا وتُرجع مكونًا جديدًا."
الفكرة هي:
EnhancedComponent = higherOrderComponent ( WrappedComponent )طريقة كلاسيكية للقيام بذلك هي إرجاع فئة مكون جديدة:
def higherOrderComponent ( WrappedComponent ):
class HOC ( Element ):
__display_name__ = f"higherOrderComponent( { WrappedComponent . __display_name__ } )"
class PropTypes ( WrappedComponent . PropTypes ):
pass
def render ( self , context ):
return < WrappedComponent { ** self . props } > { self . childre ()} < / WrappedComponent >
return HOC لاحظ كيف قمنا بتعيين فئة PropTypes على الوراثة من أحد المكونات ملفوفة ، وكيف نمرر جميع الدعائم إلى المكون ملفوف ، إلى جانب الأطفال. مع المكون الذي تم إرجاعه ، سوف يقبل نفس الدعائم ، مع نفس أنواعها ، مثلها المغلفة.
وأيضًا لاحظ __display_name__ . سيتم استخدامه في استثناءات للسماح لك الآن بالمكون الذي رفعه. هنا ، دون إجباره ، كان من الممكن تعيينه على HOC ، وهو أمر غير مفيد. بدلاً من ذلك ، نشير إلى أنها نسخة تحولت من المكون الذي تم تمريره.
هنا وظيفة لا تفعل شيئًا مفيدًا.
في مثالنا ، كان بإمكاننا القيام بذلك:
def thingify ( WrappedComponent ):
class HOC ( Element ):
__display_name__ = f"thingify( { WrappedComponent . __display_name__ } )"
class PropTypes ( WrappedComponent . PropTypes ):
__exclude__ = { 'type' }
def render ( self , context ):
return < WrappedComponent type = "thing" { ** self . props } > { self . children ()} < / WrappedComponent >
return HOCشيئان مهمان هنا:
__exclude__ = {'type'} لإزالة type الدعامة من تلك التي نرثها من WrappedComponent.PropTypes . لذلك سيتوقع المكون الذي تم إرجاعه نفس الدعائم التي تم لفها بالضبط ، باستثناء type .{self.children()} في المكون ملفوف المقدمة ، لأنه حتى لو نعلم بالفعل أن المكون الذي سنلفه ، TodoApp ، لا يأخذون الأطفال (لا يمكن أن يفعل شيئًا معهم) ، لا يمكننا أن نقول مسبقًا أنه سيكون دائمًا ما يكون عليه الحال ، وأيضًا أن هذا المكون من الدرجة العليا لن يعتاد لالتفاف عن مكون آخر من TodoApp . لذلك من الأفضل دائمًا القيام بذلك. والآن يمكننا إنشاء ThingApp لدينا:
ThingApp = thingify ( TodoApp )واستخدامه:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > </ ul > </ div >إذا حاولنا تمرير النوع:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} type = "thing" / > ) mixt . exceptions . InvalidPropNameError : < thingify ( TodoApp ) > . type : is not an allowed prop لذلك كما هو مخطط ، لا يمكننا تمرير النوع. ولاحظ كيف يتم استخدام __display_name__ .
دعونا نفكر في مدى قوة هذا.
دعنا نقول أننا نريد الحفاظ على TodoApp لدينا قائمة من TodoObject . لكننا نريد الحصول عليها من "مصدر".
يمكننا حتى كتابته مباشرة هذا المكون الجديد ذات الترتيب العالي بطريقة عامة:
def from_data_source ( WrappedComponent , prop_name , get_source ):
class HOC ( Element ):
__display_name__ = f"from_data_source( { WrappedComponent . __display_name__ } )"
class PropTypes ( WrappedComponent . PropTypes ):
__exclude__ = { prop_name }
def render ( self , context ):
props = self . props . copy ()
props [ prop_name ] = get_source ( props , context )
return < WrappedComponent { ** props } > { self . children ()} < / WrappedComponent >
return HOC هذه المرة ، تأخذ الدالة from_data_source وسيطتين بالإضافة إلى المكون WrappedComponent :
prop_name : إنه اسم دعامة المكون ملفوف لملء بعض البياناتget_source : إنها وظيفة سيتم استدعاؤها للحصول على البيانات انظر كيف ورثنا PropTypes من المكون ملفوف وكيف استبعدنا prop_name . لذلك ليس لدينا (ولا يمكننا) تمرير البيانات إلى مكوننا الجديد.
ثم في render ، قمنا بتعيين دعامة لتمريرها إلى WrappedComponent مع نتيجة لمكالمة إلى get_source .
لذلك دعونا نكتب وظيفة بسيطة للغاية (يمكن أن تكون هذه وظيفة معقدة مع التخزين المؤقت ، والتصفية ...) التي تأخذ الدعائم والسياق ، وإرجاع بعض البيانات:
def get_todos ( props , context ):
# here it could be a call to a database
return [
TodoObject ( "fooooo" ),
TodoObject ( "baaaar" ),
]ويمكننا تكوين مكوننا:
SourcedTodoApp = from_data_source ( TodoApp , 'todos' , get_todos )
ThingApp = thingify ( SourcedTodoApp )وتشغيله:
print ( < ThingApp / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > fooooo </ li > < li > baaaar </ li > </ ul > </ div >إنه يعمل كما هو متوقع ، ويتم جلب البيانات فقط عندما يحتاج المكون إلى تقديم.
لذلك ، لدينا قائمة TODO ، يمكن أن تجلب البيانات من مصدر خارجي. لكن قد نريد أن تكون البيانات مختلفة اعتمادًا على المستخدم.
ما يمكننا القيام به ، إنه في المستوى الرئيسي ، وتجلب المستخدم لدينا وتمريره على كل مكونات للتأكد من أن كل مكون قادر على الحصول على تسجيل الدخول الحالي في المستخدم.
ألن يكون مرهقا؟
حل حالة الاستخدام هذه هو الغرض الدقيق لمفهوم Context الذي يوفره mixt . إنه ، بالطبع ، مستوحى من مفهوم السياق في رد الفعل.
وكما قالوا:
تم تصميم السياق لتبادل البيانات التي يمكن اعتبارها "عالمية" لمكونات الشجرة من رد الفعل ، مثل المستخدم الحالي أو السمة أو اللغة المفضلة.
يعد إنشاء سياق أمرًا بسيطًا مثل إنشاء مكون ، إلا أنه سيرث من BaseContext ولا يحتاج إلى طريقة render (ستجعل أطفالها).
ويستغرق فئة PropTypes ، التي تحدد أنواع البيانات التي سيقبلها السياق ويمرر الشجرة.
لذلك دعونا ننشئ سياقنا الذي سيحمل معرف المستخدم المصادق عليه.
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:
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 >يمكننا أن نرى إدخالات TODO للمستخدم 2.
في هذه الحالة بالطبع ، كان بإمكاننا اجتياز معرف المستخدم كدعم. لكن تخيل أن تطبيق TODO عميق في شجرة المكونات ، فمن الأسهل بكثير تمريره بهذه الطريقة.
ولكن كما قيل في وثائق React:
لا تستخدم السياق فقط لتجنب تمرير الدعائم بضع مستويات. التزم بالحالات التي يجب الوصول فيها إلى نفس البيانات في العديد من المكونات على مستويات متعددة.
عندما لا يكون هناك سياق ، يتم ضبط وسيطة context على طريقة render على EmptyContext وليس على None . حتى تتمكن من استخدام طريقة has_prop مباشرة للتحقق مما إذا كان الدعامة متاحًا عبر السياق.
دعنا نقوم بتحديث وظائف get_todos لإرجاع قائمة فارغة من كائنات TODO إذا لم يكن هناك مستخدم مصادق عليه.
def get_todos ( props , context ):
if not context . has_prop ( 'authenticated_user_id' ) or not context . authenticated_user_id :
return []
return {
1 :[
TodoObject ( "1-1" ),
TodoObject ( "1-2" ),
],
2 : [
TodoObject ( "2-1" ),
TodoObject ( "2-2" ),
]
}[ context . authenticated_user_id ]لنجرب هذا:
print ( < ThingApp / > ) < div > < h1 > The "thing" list < / h1 > < form > ... < / form > < ul > < / ul > < / div >ولا يزال يعمل مع مستخدم في السياق:
print (
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / UserContext >
) < div > < h1 > The "thing" list < / h1 > < form > ... < / form > < ul > < li > 1 - 1 < / li > < li > 1 - 2 < / li > < / ul > < / div >ملاحظة مهمة حول السياقات : يمكنك الحصول على العديد من السياقات! لكن تحديد نفس الدعامة في العديد من السياقات قد يؤدي إلى سلوك غير محدد.
الجميع يحب تصميم جميل ، وربما بعض التفاعل.
يمكن القيام به بسهولة: ننشئ HTML و HTML يمكن أن يحتوي على بعض CSS و JS.
دعنا نضيف بعض التفاعل أولاً: عند إضافة عنصر في TodoForm ، دعنا نضيفه إلى القائمة.
أولاً ، نضيف في مكوننا TodoForm طريقة render_javascript التي ستستضيف (السيئ ، يمكننا أن نفعل بشكل أفضل ولكن ليس النقطة) JavaScript:
class TodoForm ( Element ):
# ...
def render_javascript ( self , context ):
return html . Raw ( """
function on_todo_add_submit(form) {
var text = form.todo.value;
alert(text);
}
""" )للبدء ، نعرض فقط نص TODO الجديد.
الآن قم بتحديث طريقة render الخاصة بنا لإرجاع JavaScript هذا (لاحظ أن استخدام طريقة render_javascript هو فقط لفصل المخاوف ، فقد يكون ذلك في طريقة render مباشرة.
class TodoForm ( Element ):
# ...
def render ( self , context ):
# ...
return
< Fragment >
< script > { self . render_javascript ( context )} < / script >
< form method = "post" action = { add_url } onsubmit = "return on_todo_add_submit(this);" >
< label > New { self . type }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >
< / Fragment > لاحظ علامة Fragment . إنها طريقة لتغليف العديد من العناصر التي يجب إرجاعها ، كما هو الحال في React. كان يمكن أن تكون قائمة بسيطة ولكن مع الكوميديا في النهاية:
return [
< script > ... < / script > ,
< form >
...
< / form >
] الآن نريد إضافة عنصر إلى القائمة. ليس دور TodoForm القيام بذلك ، ولكن إلى القائمة. لذلك سنضيف بعض JS في مكون TodoList : وظيفة تأخذ بعض النص وإنشاء إدخال جديد.
أما بالنسبة إلى TodoForm ، فإننا نضيف طريقة render_javascript مع (لا تزال سيئة) JavaScript:
class TodoList ( Element ):
# ...
def render_javascript ( self , context ):
todo_placeholder = < Todo todo = { TodoObject ( text = 'placeholder' )} / >
return html . Raw ( """
TODO_TEMPLATE = "%s";
function add_todo(text) {
var html = TODO_TEMPLATE.replace("placeholder", text);
var ul = document.querySelector('#todo-items');
ul.innerHTML = html + ul.innerHTML;
}
""" % ( todo_placeholder )) ونقوم بتحديث طريقة render الخاصة بنا لإضافة علامة <script> id إلى علامة ul ، المستخدمة في JavaScript:
class TodoList ( Element ):
# ...
def render ( self , context ):
return
< Fragment >
< script > { self . render_javascript ( context )} < / script >
< ul id = "todo-items" > {[ < Todo todo = { todo } / > for todo in self . todos ]} < / ul >
< / Fragment > والآن يمكننا تحديث طريقة render_javascript لمكون TodoForm لاستخدام وظيفة add_toto javaScript الجديدة الخاصة بنا:
class TodoForm ( Element ):
# ...
def render_javascript ( self , context ):
return html . Raw ( """
function on_todo_add_submit(form) {
var text = form.todo.value;
add_todo(text);
}
""" )وهذا كل شيء. لا شيء مميز ، في الواقع.
ولكن دعونا نلقي نظرة على إخراج OU TodoApp :
print (
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / User >
)الإخراج التجميل هو:
< div >
< h1 > The "thing" list </ h1 >
< script >
function on_todo_add_submit ( form ) {
var text = form . todo . value ;
add_todo ( text ) ;
}
</ script >
< form method =" post " action =" /thing/add " onsubmit =" return on_todo_add_submit(this); " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< script >
TODO_TEMPLATE = "<li>placeholder</li>" ;
function add_todo ( text ) {
var html = TODO_TEMPLATE . replace ( "placeholder" , text ) ;
var ul = document . querySelector ( '#todo-items' ) ;
ul . innerHTML = html + ul . innerHTML ;
}
</ script >
< ul id =" todo-items " >
< li > 1-1 </ li >
< li > 1-2 </ li >
</ ul >
</ div > لذلك لدينا العديد من علامة script . قد يكون من الرائع أن يكون لديك واحد فقط.
يأتي mixt مع طريقة "لجمع" أجزاء مما يتم تقديمه لوضعها في مكان آخر. لدينا تحت تصرفنا جامعين بسيطين ، لاستخدامها كمكونات: JSCollector و CSSCollector .
هذه المكونات تجمع أجزاء من شجرة أطفالهم.
الطريقة الأولى هي استخدام علامة Collect جامع.
أولاً ، دعونا نغير مكالمتنا الرئيسية:
from mixt import JSCollector
print (
< JSCollector render_position = "after" >
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / User >
< / JSCollector >
) سيؤدي ذلك إلى جمع محتوى جميع علامة JSCollector.Collect .
دعنا نقوم بتحديث TodoForm واستبدال علامة script الخاصة بنا بعلامة JSCollector.Collect :
class TodoForm ( Element ):
# ...
def render ( self , context ):
if callable ( self . add_url ):
add_url = self . add_url ( self . type )
else :
add_url = self . add_url
return
< JSCollector . Collect > { self . render_javascript ( context )} < / JSCollector . Collect >
< form method = "post" action = { add_url } onsubmit = "return on_todo_add_submit(this);" >
< label > New { self . type }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >
< / Fragment > يمكننا أن نفعل الشيء نفسه مع TodoList :
class TodoList ( Element ):
# ...
def render ( self , context ):
return
< Fragment >
< JSCollector . Collect > { self . render_javascript ( context )} < / JSCollector . Collect >
< ul id = "todo-items" > {[ < Todo todo = { todo } / > for todo in self . todos ]} < / ul >
< / Fragment >الآن دعنا ندير رمزنا المحدث:
print (
< JSCollector render_position = "after" >
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / User >
< / JSCollector >
)الإخراج التجميل هو:
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " onsubmit =" return on_todo_add_submit(this); " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul id =" todo-items " >
< li > 1-1 </ li >
< li > 1-2 </ li >
</ ul >
</ div >
< script type =" text/javascript " >
function on_todo_add_submit ( form ) {
var text = form . todo . value ;
add_todo ( text ) ;
}
TODO_TEMPLATE = "<li>placeholder</li>" ;
function add_todo ( text ) {
var html = TODO_TEMPLATE . replace ( "placeholder" , text ) ;
var ul = document . querySelector ( '#todo-items' ) ;
ul . innerHTML = html + ul . innerHTML ;
}
</ script > كما ترون ، فإن جميع البرامج النصية في علامة script واحدة ، في النهاية. بتعبير أدق ، في نهاية المكان الذي كانت فيه علامة JSCollector ، لأننا استخدمنا render_position="after" . احتمال آخر هو render_position="before" لوضع هذا حيث بدأت علامة JSCollector .
كل هذا العمل بنفس الطريقة تمامًا لعلامة CSSCollector ، حيث يتم وضع المحتوى في علامة <style type="text/css> .
نظرًا لأن استخدام JS/CSS أمر شائع جدًا في عالم HTML ، فقد أضفنا بعض السكر لجعل كل هذا أسهل في القيام به.
إذا كان لديك طريقة render_js ، فسيقوم JSCollector تلقائيًا بجمع نتيجة هذه الطريقة. نفس الشيء بالنسبة لـ CSSSelector وطريقة render_css .
مع هذا ، لا حاجة لعلامة JSCollector.Collect .
لجعل هذا العمل في مثالنا ، في TodoForm و TodoList :
JSCollector.CollectFragment غير الضرورية الآنrender_javascript إلى render_js .html.Raw في render_js لأنها غير مطلوبة عندما يستدعي المجمع render_js نفسه: إذا كان الإخراج عبارةبهذه الطريقة لدينا نفس النتيجة بالضبط.
إنه يعمل الآن لأنه ليس لدينا سوى مثيل واحد لطفل مع طريقة render_js .
ولكن إذا كان لدينا العديد من الأطفال ، فسيتم استدعاء هذه الطريقة لكل طفل. إذا كانت الحقيقة ، يجب أن تحتوي فقط على رمز خاص جدًا بهذه المثيل.
لخدمة JS/CSS مرة واحدة فقط لفئة مكون ، يتعين علينا استخدام render_js_global أو render_css_global (من المتوقع أن تكون classmethod )
سيتم جمعها في المرة الأولى ، وفي المرة الأولى ، يتم العثور على مثيل ، قبل جمع طريقة render_js .
لذلك ، يمكننا تغيير render_js الخاص بنا إلى render_js_global ، وتزيينها باستخدام @classmethod وسيظل يعمل على نفس المنوال.
نحن الآن قادرون على إعادة تجميع جافا سكريبت أو نمط. ولكن ماذا لو أردنا وضعه في مكان آخر ، كما هو الحال في علامة head أو في نهاية علامة body ؟
من الممكن مع المراجع ، الملقب "الحكام". إنه نفس السياق كما في رد الفعل ، دون جزء DOM بالطبع.
يمكنك إنشاء مرجع ، وتمريره إلى مكون ، ويمكنك استخدامه في أي مكان.
دعنا نقوم بتحديث الكود الرئيسي لدينا للقيام بذلك.
أولا نقوم بإنشاء المرجع.
from mixt import Ref
js_ref = Ref () سيؤدي ذلك إلى إنشاء كائن جديد يعقد مرجعًا إلى مكون. في مكون ، لا تحتاج إلى استيراد Ref ويمكنك استخدام js_ref = self.add_ref() ، لكننا لسنا في مكون هنا.
لحفظ المرجع ، نقوم ببساطة بنقلها إلى الدعامة ref :
< JSCollector ref = { js_ref } > ... < / JSCollector > لاحظ أننا أزلنا دعامة render_position ، لأننا الآن لا نريد وضع JS قبل أو بعد العلامة ، ولكن في مكان آخر.
للوصول إلى المكون المشار إليه بواسطة المرجع ، استخدم السمة current :
js_collector = js_ref . currentبالطبع لا يمكن القيام بذلك إلا بعد التقديم.
كيف يمكننا استخدام هذا لإضافة علامة script في head .
أول تحديث HTML الخاص بنا لتشمل علامات html الكلاسيكية head body :
return str (
< html >
< head >
< / head >
< body >
< JSCollector ref = { js_ref } >
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / UserContext >
< / JSCollector >
< / body >
< / html >
) في هذه المرحلة ، ليس لدينا أي علامة script في الإخراج:
< html >
< head > </ head >
< body >
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " onsubmit =" return on_todo_add_submit(this); " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul id =" todo-items " >
< li > 1-1 </ li >
< li > 1-2 </ li >
</ ul >
</ div >
</ body >
</ html > أول شيء يجب معرفته: هو جامع قادر على تقديم كل الأشياء التي جمعها عن طريق calliing طريقة render_collected .
وتذكر أنه يتضمن بالفعل علامة script ، فقد نرغب في القيام بذلك:
# ...
< head >
{ js_ref . current . render_collected ()}
< / head >
# ...لكن هذا لا يعمل:
AttributeError : 'NoneType' object has no attribute 'render_collected'ذلك لأننا نحاول الوصول إلى القيمة الحالية في وقت العرض. يجب أن يتم بعد.
لهذا ، يمكننا استخدام ميزة mixt : إذا كان هناك شيء إضافي إلى الشجرة قابل للاستدعاء ، فسيتم استدعاؤه بعد العرض ، عند التحويل إلى سلسلة.
حتى نتمكن من استخدام Lambda على سبيل المثال:
# ...
< head >
{ lambda : js_ref . current . render_collected ()}
< / head >
# ...والآن يعمل:
< html >
< head >
< script type =" text/javascript " >
function on_todo_add_submit ( form ) {
var text = form . todo . value ;
add_todo ( text ) ;
}
TODO_TEMPLATE = "<li>placeholder</li>" ;
function add_todo ( text ) {
var html = TODO_TEMPLATE . replace ( "placeholder" , text ) ;
var ul = document . querySelector ( '#todo-items' ) ;
ul . innerHTML = html + ul . innerHTML ;
}
</ script >
</ head >
< body >
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " onsubmit =" return on_todo_add_submit(this); " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul id =" todo-items " >
< li > 1-1 </ li >
< li > 1-2 </ li >
</ ul >
</ div >
</ body >
</ html > يا هلا صنعناها! أوضحت جميع الميزات الرئيسية لـ mixt . يمكنك الآن استخدام mixt في مشاريعك الخاصة.
كخطوة تالية ، قد ترغب في قراءة وثائق API.