اكتشاف ClojureScript لتطوير الواجهة الأمامية
نشرت: 2022-03-11ربما تكون هناك فكرتان رئيسيتان في ذهنك أثناء قراءة هذه المقدمة:
- ما هو ClojureScript؟
- هذا ليس له علاقة باهتماماتي.
لكن انتظر! هذا هو المكان الذي تكون فيه مخطئًا - وسأثبت لك ذلك. إذا كنت على استعداد لاستثمار 10 دقائق من وقتك ، فسأوضح لك كيف يمكن لـ ClojureScript أن تجعل كتابة تطبيقات الواجهة الأمامية و React-y ممتعة وسريعة و- والأهم من ذلك- وظيفية .
بعض المتطلبات الأساسية لبرنامج ClojureScript التعليمي
- المعرفة اللاذعة ليست مطلوبة. سأبذل قصارى جهدي لشرح أي عينات من التعليمات البرمجية مبعثرة في منشور المدونة هذا!
- ومع ذلك ، إذا كنت ترغب في إجراء القليل من القراءة المسبقة ، فإنني أوصي بشدة بـ https://www.braveclojure.com/ ، المتجر الشامل لبدء استخدام Clojure (وامتدادًا ، ClojureScript).
- تشترك Clojure و ClojureScript في لغة مشتركة - سأشير إليها غالبًا في نفس الوقت باسم Clojure [سيناريو] .
- أفترض أن لديك معرفة بـ React ومعرفة عامة عن الواجهة الأمامية.
كيف تتعلم ClojureScript: النسخة القصيرة
لذلك ليس لديك الكثير من الوقت لتعلم ClojureScript ، وتريد فقط أن ترى إلى أين يتجه هذا الأمر برمته. أول الأشياء أولاً ، ما هو ClojureScript؟
من موقع ClojureScript على الويب: ClojureScript هو مترجم لـ Clojure يستهدف JavaScript. يصدر كود JavaScript متوافق مع وضع التجميع المتقدم لمجمع تحسين Google Closure.
من بين أشياء أخرى ، لدى ClojureScript الكثير لتقدمه:
- إنها لغة برمجة متعددة النماذج ذات برمجة وظيفية بسيطة - ومن المعروف أن البرمجة الوظيفية تعمل على تحسين وضوح الكود بالإضافة إلى مساعدتك في كتابة المزيد باستخدام تعليمات برمجية أقل .
- إنه يدعم الثبات بشكل افتراضي - قل وداعًا لمجموعة كاملة من مشكلات وقت التشغيل!
- إنها موجهة للبيانات: الكود عبارة عن بيانات في ClojureScript. يمكن اختزال معظم تطبيقات Clojure [Script] إلى مجموعة من الوظائف التي تعمل على بعض هياكل البيانات الأساسية ، مما يجعل تصحيح الأخطاء بسيطًا والكود سهل القراءة.
- انه سهل! يعد بدء استخدام ClojureScript أمرًا سهلاً - فلا توجد كلمات رئيسية رائعة وقليل من السحر.
- لديها مكتبة قياسية رائعة. هذا الشيء لديه كل شيء.
بهذه الطريقة ، دعونا نفتح علبة الديدان بمثال:
(defn component [] [:div "Hello, world!"])
ملاحظة لأولئك الذين ليسوا على دراية بلهجات Lisp أو ClojureScript: أهم أجزاء هذا المثال هي :div
و []
و ()
. :div
هي كلمة أساسية تمثل عنصر <div>
. []
متجه ، يشبه إلى حد كبير ArrayList
في Java ، و ()
هو تسلسل يشبه إلى حد كبير LinkedList
. سوف أتطرق إلى هذا بمزيد من التفصيل لاحقًا في هذا المنشور!
هذا هو الشكل الأساسي لمكوِّن React في ClojureScript. هذا كل شيء - مجرد كلمة رئيسية وسلسلة ومجموعة كاملة من القوائم.
أف! تقول ، هذا لا يختلف كثيرًا عن "hello world" في JSX أو في TSX:
function component() { return ( <div> "Hello, world!" </div> ); }
ومع ذلك ، هناك بعض الاختلافات الجوهرية التي يمكننا ملاحظتها حتى من هذا المثال الأساسي:
- لا توجد لغات مضمنة. كل شيء في مثال ClojureScript هو إما سلسلة أو كلمة رئيسية أو قائمة.
- إنه موجز توفر القوائم كل التعبيرات التي نحتاجها بدون تكرار علامات إغلاق HTML.
هذان الاختلافان الصغيران لهما عواقب وخيمة ، ليس فقط في كيفية كتابة التعليمات البرمجية ولكن في كيفية التعبير عن نفسك أيضًا!
كيف هو أن تسأل؟ دعنا ننتقل إلى المعركة ونرى ما يخبئه لنا ClojureScript أيضًا ...
- الشروع في العمل مع لغة البرمجة Elm
- الشروع في استخدام لغة برمجة الإكسير
اللبنات
خلال هذا البرنامج التعليمي لـ ClojureScript ، سأسعى إلى عدم التعمق كثيرًا في ما يجعل Clojure [البرنامج النصي] رائعًا (وهو كثير من الأشياء ، لكني أستطرد). ومع ذلك ، سيكون من المفيد تغطية بعض المفاهيم الأساسية بحيث يكون من الممكن فهم مدى اتساع ما يمكننا القيام به هنا.
لأولئك من ذوي الخبرة Clojuristas و Lispians ، لا تتردد في القفز إلى القسم التالي!
هناك ثلاثة مفاهيم رئيسية سأحتاج إلى تغطيتها أولاً:
الكلمات الدالة
Clojure [سيناريو] لديه مفهوم يسمى Keyword. يقع في مكان ما بين سلسلة ثابتة (على سبيل المثال ، في Java) ومفتاح. إنها معرّفات رمزية تقيم نفسها بنفسها .
على سبيل المثال ، الكلمة الرئيسية :cat
ستشير دائمًا إلى :cat
وليس إلى أي شيء آخر أبدًا. كما هو الحال في Java قد تقول:
private static const String MY_KEY = "my_key"; // ... myMap.put(MY_KEY, thing); // ... myMap.get(MY_KEY);
... في Clojure سيكون لديك ببساطة:
(assoc my-map :my-key thing) (my-map :my-key) ; equivalent to (:my-key my-map) ...nice and flexible!
لاحظ أيضًا: في Clojure ، الخريطة عبارة عن مجموعة (من مفاتيح القيم ، تمامًا مثل Java HashMap
) ووظيفة للوصول إلى محتوياتها. مرتب!
القوائم
كلوجور [سيناريو] كونها لهجة لاثغة يعني أنها تضع الكثير من التركيز على القوائم. كما ذكرت سابقًا ، هناك شيئان رئيسيان يجب أن تكون على دراية بهما:
-
[]
متجه ، يشبه إلى حد كبيرArrayList
. -
()
هو تسلسل يشبه إلى حد كبيرLinkedList
.
لإنشاء قائمة بالأشياء في Clojure [البرنامج النصي] ، يمكنك القيام بما يلي:
[1 2 3 4] ["hello" "world"] ["my" "list" "contains" 10 "things"] ; you can mix and match types ; in Clojure lists!
بالنسبة للتسلسلات ، الأمر مختلف قليلاً:
'(1 2 3 4) '("hello" "world")
الإعداد المسبق موضح '
القسم التالي.
المهام
أخيرا ، لدينا وظائف. الوظيفة في Clojure [البرنامج النصي] هي تسلسل يتم كتابته بدون إضافة '
. العنصر الأول في تلك القائمة هو الوظيفة نفسها ، وستكون جميع العناصر التالية هي الوسيطات . علي سبيل المثال:
(+ 1 2 3 4) ; -> 10 (str "hello" " " "world") ; -> "hello world" (println "hi!") ; prints "hi!" to the console (run-my-function) ; runs the function named `run-my-function`
إحدى النتائج الطبيعية لهذا السلوك هي أنه يمكنك بناء تعريف للدالة دون تنفيذها فعليًا! سيتم تنفيذ تسلسل "عارية" فقط عند تقييم البرنامج.
(+ 1 1) ; -> 2 '(+ 1 1); -> a list of a function and two numbers
سيصبح هذا وثيق الصلة في وقت لاحق!
يمكن تحديد الوظائف بعدة طرق:
; A normal function definition, assigning the function ; to the symbol `my-function` (defn my-function [arg1 arg2] (+ arg1 arg2)) ; An anonymous function that does the same thing as the above (fn [arg1 arg2] (+ arg1 arg2)) ; Another, more concise variation of the above #(+ %1 %2)
فحص أقرب
والآن بعد أن غطينا الأساسيات ، دعنا نتعمق في المزيد من التفاصيل لنرى ما يحدث هنا.
يتم إجراء React في ClojureScript بشكل عام باستخدام مكتبة تسمى Reagent. يستخدم الكاشف Hiccup وصياغته لتمثيل HTML. من ويكي Hiccup repo:
"Hiccup يحول هياكل بيانات Clojure مثل هذا:"
[:a {:href "http://github.com"} "GitHub"]
"في سلاسل HTML مثل هذا:"
<a href="http://github.com">GitHub</a>
ببساطة ، يصبح العنصر الأول في القائمة هو نوع عنصر HTML ، ويصبح العنصر المتبقي محتويات هذا العنصر. اختياريًا ، يمكنك تقديم خريطة السمات التي سيتم إرفاقها بعد ذلك بهذا العنصر.
يمكن أن تتداخل العناصر مع بعضها البعض ببساطة عن طريق دمجها في قائمة الأبوين! هذا أسهل مع مثال:
[:div [:h1 "This is a header"] [:p "And in the next element we have 1 + 1"] [:p (+ 1 1)]]
لاحظ كيف يمكننا وضع أي دالة قديمة أو بناء جملة Clojure عام داخل هيكلنا دون الحاجة إلى الإعلان صراحة عن طريقة التضمين. إنها مجرد قائمة ، بعد كل شيء!
والأفضل من ذلك ، ما الذي يمكن تقييمه في وقت التشغيل؟

[:div [:h1 "This is a header"] [:p "And in the next element we have 1 + 1"] [:p 2]]
قائمة الكلمات الرئيسية ومحتويات بالطبع! لا توجد أنواع مضحكة ولا أساليب سحرية خفية. إنها مجرد قائمة قديمة بسيطة للأشياء. يمكنك لصق هذه القائمة واللعب بها بقدر ما تريد - ما تراه هو ما تحصل عليه.
مع قيام Hiccup بالتخطيط ويقوم الكاشف بتنفيذ المنطق ومعالجة الأحداث ، ننتهي ببيئة React تعمل بكامل طاقتها.
مثال أكثر تعقيدًا
حسنًا ، دعنا نربط هذا ببعض المكونات. أحد الأشياء السحرية في React (و Reagent) هو أنك تقوم بتجزئة العرض الخاص بك ومنطق التخطيط إلى وحدات نمطية ، والتي يمكنك إعادة استخدامها في جميع أنحاء التطبيق الخاص بك.
لنفترض أننا أنشأنا مكونًا بسيطًا يعرض زرًا وبعض المنطق البسيط:
; widget.cljs (defn component [polite?] [:div [:p (str "Do not press the button" (when polite? ", please."))] [:input {:type "button" :value "PUSH ME" :on-click #(js/alert "What did I tell you?")}]])
ملاحظة سريعة حول التسمية: عادةً ما تكون الوحدات النمطية في Clojure ذات مسافات اسم ، لذلك قد يتم استيراد widget.cljs
ضمن widget
مساحة الاسم. هذا يعني أنه سيتم الوصول إلى وظيفة component
المستوى الأعلى كعنصر widget/component
. أحب أن يكون لدي مكون واحد فقط من المستوى الأعلى لكل وحدة ، ولكن هذا هو تفضيل النمط - قد تفضل تسمية وظيفة المكون الخاصة بك بشيء مثل polite-component
widget-component
.
يقدم لنا هذا المكون البسيط عنصر واجهة مستخدم مهذب اختياريًا. (when polite? ", please.")
بالتقييم إلى ", please."
عندما تكون polite? == true
polite? == true
ولا nil
عندما يكون false
.
الآن دعنا ندمج هذا في app.cljs
لدينا:
(defn app [] [:div [:h1 "Welcome to my app"] [widget/component true]])
هنا نقوم بتضمين عنصر واجهة المستخدم الخاص بنا في مكون التطبيق الخاص بنا من خلال تسميته بالعنصر الأول في القائمة — تمامًا مثل الكلمات الرئيسية لـ HTML! يمكننا بعد ذلك تمرير أي توابع أو معلمات إلى المكون من خلال توفيرها كعناصر أخرى من نفس القائمة. نحن هنا ببساطة نجتاز true
، فهل هو polite? == true
polite? == true
، وبالتالي نحصل على النسخة المهذبة.
إذا أردنا تقييم وظيفة التطبيق لدينا الآن ، فسنحصل على ما يلي:
[:div [:h1 "Welcome to my app"] [widget/component true]] ; <- widget/component would look more like a ; function reference, but I have kept it ; clean for legibility.
لاحظ كيف لم يتم تقييم widget/component
! (راجع قسم الوظائف إذا كنت محتارًا.)
يتم تقييم المكونات الموجودة في شجرة DOM الخاصة بك فقط (وبالتالي تحويلها إلى كائنات React حقيقية خلف الكواليس) إذا تم تحديثها ، مما يحافظ على الأشياء لطيفة وسريعة ويقلل من مقدار التعقيد الذي يتعين عليك التعامل معه في أي وقت.
يمكن الحصول على مزيد من التفاصيل حول هذا الموضوع للمهتمين في وثائق الكاشف.
يسرد على طول الطريق
علاوة على ذلك ، لاحظ كيف أن DOM هو مجرد قائمة من القوائم ، وأن المكونات ما هي إلا وظائف تقوم بإرجاع قوائم من القوائم. لماذا يعد هذا مهمًا جدًا عندما تتعلم ClojureScript؟
لأن أي شيء يمكنك القيام به للوظائف أو القوائم ، يمكنك القيام به للمكونات.
هذا هو المكان الذي تبدأ فيه في الحصول على عوائد مركبة باستخدام لهجة Lisp مثل ClojureScript: تصبح مكوناتك وعناصر HTML كائنات من الدرجة الأولى يمكنك معالجتها مثل أي بيانات عادية أخرى! دعني أقول ذلك مرة أخرى:
المكونات وعناصر HTML هي كائنات مدعومة من الدرجة الأولى داخل لغة Clojure!
هذا صحيح ، لقد سمعتني. يشبه الأمر تقريبًا أن Lisps صُممت لمعالجة القوائم (تلميح: لقد كانت كذلك).
يتضمن ذلك أشياء مثل:
- تعيين عناصر قائمة ذات تعداد رقمي:
(def words ["green" "eggs" "and" "ham"]) (defn li-shout [x] [:li (string/uppercase x)) (concat [:ol] (map li-shout words) ; becomes [:ol [:li "GREEN"] [:li "EGGS"] [:li "AND"] [:li "HAM"]]
- مكونات التغليف:
; in widget.cljs (defn greeting-component [name] [:div [:p (str "Hiya " name "!")]]) ; ... (def shouty-greeting-component #(widget/greeting-component (string/uppercase %))) (defn app [] [:div [:h1 "My App"] [shouty-greeting-component "Luke"]]) ; <- will show Hiya LUKE!
- سمات الحقن:
(def default-btn-attrs {:type "button" :value "I am a button" :class "my-button-class"}) (defn two-button-component [] [:div [:input (assoc default-btn-attrs :on-click #(println "I do one thing"))] [:input (assoc default-btn-attrs :on-click #(println "I do a different thing"))]])
يعتبر التعامل مع أنواع البيانات القديمة البسيطة مثل القوائم والخرائط أبسط بشكل كبير من أي شيء يشبه الفصل ، وينتهي به الأمر إلى أن يكون أكثر قوة على المدى الطويل!
ظهور نمط
حسنًا ، دعنا نلخص. ما الذي أظهره برنامجنا التعليمي ClojureScript حتى الآن؟
- يتم تقليل كل شيء إلى أبسط وظائف - العناصر هي مجرد قوائم ، والمكونات هي مجرد وظائف تقوم بإرجاع العناصر.
- نظرًا لأن المكونات والعناصر هي كائنات من الدرجة الأولى ، فنحن قادرون على كتابة المزيد باستخدام القليل .
تتلاءم هاتان النقطتان بشكل مريح مع روح البرمجة الوظيفية Clojure - فالشفرة هي البيانات التي يجب معالجتها ، ويتم بناء التعقيد من خلال توصيل أجزاء أقل تعقيدًا. نقدم برنامجنا (صفحة الويب الخاصة بنا في هذا المثال) كبيانات (قوائم ، وظائف ، خرائط) ونحتفظ به على هذا النحو حتى اللحظة الأخيرة التي يتولى فيها Reagent ويحولها إلى رمز React. هذا يجعل الكود الخاص بنا قابلاً لإعادة الاستخدام ، والأهم من ذلك أنه من السهل جدًا قراءته وفهمه بقليل من السحر.
الحصول على الأناقة
نحن نعرف الآن كيفية إنشاء تطبيق ببعض الوظائف الأساسية ، لذلك دعنا ننتقل إلى كيفية جعله يبدو جيدًا. هناك طريقتان للتعامل مع هذا الأمر ، أبسطها هي استخدام أوراق الأنماط والإشارة إلى فئاتها في مكوناتك:
.my-class { color: red; }
[:div {:class "my-class"} "Hello, world!"]
سيفعل هذا تمامًا كما تتوقع ، حيث يقدم لنا كلمة حمراء جميلة "مرحبًا ، أيها العالم!" نص.
ومع ذلك ، لماذا ننتقل إلى كل هذه المشاكل في تحديد موقع العرض والرمز المنطقي في المكونات الخاصة بك ، ولكن بعد ذلك فصل التصميم الخاص بك في ورقة أنماط - ليس عليك الآن فقط البحث في مكانين مختلفين ، ولكنك تتعامل مع اثنين مختلفين لغات أيضًا!
لماذا لا تكتب CSS لدينا كرمز في مكوناتنا (انظر الموضوع هنا؟). سيعطينا هذا مجموعة من المزايا:
- كل ما يحدد المكون موجود في نفس المكان.
- يمكن ضمان أسماء الصفوف بشكل فريد من خلال الجيل الذكي.
- يمكن أن تكون CSS ديناميكية ، وتتغير مع تغير بياناتنا.
نكهتي الشخصية المفضلة لـ CSS-in-code هي مع Clojure Style Sheets (cljss). تبدو CSS المضمنة كما يلي:
;; -- STYLES ------------------------------------------------------------ (defstyles component-style [] {:color "red" :width "100%"}) ;; -- VIEW -------------------------------------------------------------- (defn component [] [:div {:class (component-style)} "Hello, world!"])
تقوم defstyles
بإنشاء وظيفة من شأنها إنشاء اسم فئة فريد لنا (وهو أمر رائع لأي شخص يستورد مكوناتنا.)
هناك العديد من الأشياء الأخرى التي يمكن لـ cljss القيام بها من أجلك (تكوين الأنماط والرسوم المتحركة وتجاوزات العناصر وما إلى ذلك) لن أخوض في التفاصيل هنا. أوصي بأن تتحقق من ذلك بنفسك!
تجميع أجزاء تطبيق ClojureScript
أخيرًا ، هناك الصمغ المطلوب لإلصاق كل هذا معًا. لحسن الحظ ، إلى جانب ملف المشروع و index.html
، فإن boilerplate موجود على الأقل هنا.
انت تحتاج:
- ملف تعريف مشروعك ،
project.clj
. يعد هذا عنصرًا أساسيًا في أي مشروع Clojure ، وهو يحدد تبعياتك - حتى مباشرة من GitHub - وخصائص الإنشاء الأخرى (على غرارbuild.gradle
أوpackage.json
.) - ملف
index.html
يعمل كنقطة ربط لتطبيق Reagent. - بعض كود الإعداد لبيئة التطوير وأخيراً لبدء تشغيل تطبيق الكاشف الخاص بك.
ستجد مثال الكود الكامل لهذا البرنامج التعليمي ClojureScript متاحًا على GitHub.
هذا كل شيء (في الوقت الحالي). آمل أن أكون قد أثارت فضولك بمقدار ضئيل ، سواء كان ذلك للتحقق من لهجة Lisp (Clojure [Script] أو غير ذلك) أو حتى لتجربة يدك في إنشاء تطبيق الكاشف الخاص بك! أعدك أنك لن تندم على ذلك.
انضم إلي في متابعة هذا المقال ، Getting Into a State ، حيث أتحدث عن إدارة الدولة باستخدام إعادة الإطار - قل مرحبًا Redux في ClojureScript!