Menggali ClojureScript untuk Pengembangan Front-end

Diterbitkan: 2022-03-11

Mungkin ada dua pemikiran utama di kepala Anda saat membaca pengantar ini:

  1. Apa itu ClojureScript?
  2. Ini tidak relevan dengan minat saya.

Tapi tunggu! Di situlah Anda salah—dan saya akan membuktikannya kepada Anda. Jika Anda bersedia menginvestasikan 10 menit waktu Anda, saya akan menunjukkan kepada Anda bagaimana ClojureScript dapat membuat penulisan front-end dan aplikasi React-y menyenangkan, cepat, dan—yang terpenting— fungsional .

Beberapa Prasyarat Tutorial ClojureScript

  • Pengetahuan cadel tidak diperlukan. Saya akan melakukan yang terbaik untuk menjelaskan contoh kode apa pun yang tersebar di dalam posting blog ini!
  • Namun, jika Anda ingin melakukan sedikit pra-membaca, saya akan sangat merekomendasikan https://www.braveclojure.com/, toko serba ada untuk memulai dengan Clojure (dan dengan ekstensi, ClojureScript).
  • Clojure dan ClojureScript berbagi bahasa yang sama—saya akan sering merujuknya bersamaan dengan Clojure[Script] .
  • Saya berasumsi bahwa Anda memiliki pengetahuan tentang React dan pengetahuan front-end umum.

Cara Mempelajari ClojureScript: Versi Singkat

Jadi Anda tidak punya banyak waktu untuk mempelajari ClojureScript, dan Anda hanya ingin melihat ke mana arah semua ini. Hal pertama yang pertama, apa itu ClojureScript?

Dari situs web ClojureScript: ClojureScript adalah kompiler untuk Clojure yang menargetkan JavaScript. Ini memancarkan kode JavaScript yang kompatibel dengan mode kompilasi lanjutan dari kompiler pengoptimalan Google Closure.

Antara lain, ClojureScript memiliki banyak hal untuk ditawarkan:

  • Ini adalah bahasa pemrograman multiparadigma dengan pemrograman fungsional yang ramping—pemrograman fungsional dikenal untuk meningkatkan keterbacaan kode serta membantu Anda menulis lebih banyak dengan lebih sedikit kode .
  • Ini mendukung kekekalan secara default — ucapkan selamat tinggal pada seluruh rangkaian masalah runtime!
  • Berorientasi pada data: kode adalah data dalam ClojureScript. Sebagian besar aplikasi Clojure[Script] dapat direduksi menjadi satu set fungsi yang beroperasi pada beberapa struktur data yang mendasarinya, yang membuat proses debug menjadi sederhana dan kode sangat mudah dibaca.
  • Itu mudah! Memulai dengan ClojureScript itu mudah—tidak ada kata kunci yang mewah dan sedikit keajaiban.
  • Ini memiliki perpustakaan standar yang fantastis. Benda ini memiliki segalanya.

Dengan itu, mari kita buka kaleng cacing ini dengan sebuah contoh:

 (defn component [] [:div "Hello, world!"])

Catatan bagi mereka yang tidak terbiasa dengan dialek Lisp atau ClojureScript: Bagian terpenting dari contoh ini adalah :div , [] , dan () . :div adalah kata kunci yang mewakili elemen <div> . [] adalah vektor, seperti ArrayList di Java, dan () adalah urutan, seperti LinkedList . Saya akan menyentuh ini secara lebih rinci nanti di posting ini!

Ini adalah bentuk paling dasar dari komponen React di ClojureScript. Itu saja—hanya kata kunci, string, dan sejumlah daftar.

Astaga! Anda mengatakan, itu tidak jauh berbeda dari "hello world" di BEJ atau di TSX:

 function component() { return ( <div> "Hello, world!" </div> ); }

Namun, ada beberapa perbedaan penting yang dapat kita temukan bahkan dari contoh dasar ini:

  • Tidak ada bahasa yang disematkan; semua yang ada di dalam contoh ClojureScript adalah string, kata kunci, atau daftar.
  • Ini adalah ringkas ; daftar menyediakan semua ekspresi yang kita butuhkan tanpa redundansi tag penutup HTML.

Kedua perbedaan kecil ini memiliki konsekuensi besar, tidak hanya dalam cara Anda menulis kode, tetapi juga dalam cara Anda mengekspresikan diri!

Bagaimana itu, Anda bertanya? Mari terjun ke medan dan lihat apa lagi yang tersedia ClojureScript untuk kita…

Terkait:
  • Memulai dengan Bahasa Pemrograman Elm
  • Memulai dengan Bahasa Pemrograman Elixir

Blok bangunan

Sepanjang tutorial ClojureScript ini, saya akan berusaha untuk tidak menggali terlalu jauh apa yang membuat Clojure[Script] hebat (yang banyak hal, tapi saya ngelantur). Meskipun demikian, akan berguna untuk memiliki beberapa konsep dasar yang tercakup sehingga memungkinkan untuk memahami luasnya apa yang dapat kita lakukan di sini.

Bagi para Clojurista dan Lispian yang berpengalaman, silakan lanjutkan ke bagian berikutnya!

Ada tiga konsep utama yang perlu saya bahas terlebih dahulu:

Kata kunci

Clojure[Script] memiliki konsep yang disebut Kata Kunci. Itu terletak di suatu tempat antara string konstan (katakanlah, di Jawa) dan kunci. Mereka adalah pengidentifikasi simbolis yang mengevaluasi diri mereka sendiri .

Sebagai contoh, kata kunci :cat akan selalu mengacu pada :cat dan tidak pernah ke yang lain. Sama seperti di Jawa Anda mungkin mengatakan:

 private static const String MY_KEY = "my_key"; // ... myMap.put(MY_KEY, thing); // ... myMap.get(MY_KEY);

…di Clojure Anda hanya akan memiliki:

 (assoc my-map :my-key thing) (my-map :my-key) ; equivalent to (:my-key my-map) ...nice and flexible!

Perhatikan juga: Di Clojure, peta adalah kumpulan (kunci nilai, seperti Java HashMap ) dan fungsi untuk mengakses kontennya. Rapi!

Daftar

Clojure[Script] menjadi dialek Lisp berarti menempatkan banyak penekanan pada daftar. Seperti yang saya sebutkan sebelumnya, ada dua hal utama yang harus diperhatikan:

  1. [] adalah vektor, seperti ArrayList .
  2. () adalah urutan, seperti LinkedList .

Untuk membuat daftar hal-hal di Clojure[Script], Anda melakukan hal berikut:

 [1 2 3 4] ["hello" "world"] ["my" "list" "contains" 10 "things"] ; you can mix and match types ; in Clojure lists!

Untuk urutan, ini sedikit berbeda:

 '(1 2 3 4) '("hello" "world")

Prepending ' dijelaskan di bagian selanjutnya.

Fungsi

Akhirnya, kami memiliki fungsi. Fungsi dalam Clojure[Script] adalah urutan yang diketik tanpa awalan ' . Elemen pertama dari daftar itu adalah fungsi itu sendiri, dan semua elemen berikut akan menjadi argumen . Sebagai contoh:

 (+ 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`

Salah satu akibat wajar dari perilaku ini adalah Anda dapat membangun definisi fungsi tanpa benar-benar menjalankannya! Hanya urutan 'telanjang' yang akan dieksekusi ketika program dievaluasi.

 (+ 1 1) ; -> 2 '(+ 1 1); -> a list of a function and two numbers

Ini akan menjadi relevan nanti!

Fungsi dapat didefinisikan dalam beberapa cara:

 ; 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)

Pemeriksaan Lebih Dekat

Jadi sekarang kita telah membahas dasar-dasarnya, mari kita telusuri sedikit lebih detail untuk melihat apa yang terjadi di sini.

React di ClojureScript umumnya dilakukan dengan menggunakan library yang disebut Reagent. Reagen menggunakan Hiccup dan sintaksnya untuk mewakili HTML. Dari wiki repo Hiccup:

“Hiccup mengubah struktur data Clojure seperti ini:”

 [:a {:href "http://github.com"} "GitHub"]

“Menjadi string HTML seperti ini:”

 <a href="http://github.com">GitHub</a>

Sederhananya, elemen pertama dari daftar menjadi tipe elemen HTML, dan sisanya menjadi konten elemen itu. Opsional, Anda dapat memberikan peta atribut yang kemudian akan dilampirkan ke elemen itu.

Elemen dapat bersarang di dalam satu sama lain hanya dengan menumpuknya di dalam daftar induknya! Ini paling mudah dilihat dengan contoh:

 [:div [:h1 "This is a header"] [:p "And in the next element we have 1 + 1"] [:p (+ 1 1)]]

Perhatikan bagaimana kita dapat menempatkan fungsi lama atau sintaksis Clojure umum apa pun dalam struktur kita tanpa harus mendeklarasikan metode embedding secara eksplisit. Lagipula itu hanya daftar!

Dan lebih baik lagi, apa evaluasi ini saat runtime?

 [:div [:h1 "This is a header"] [:p "And in the next element we have 1 + 1"] [:p 2]]

Daftar kata kunci dan isinya tentu saja! Tidak ada tipe yang lucu, tidak ada metode tersembunyi yang ajaib. Itu hanya daftar hal-hal lama. Anda dapat menyambung dan bermain dengan daftar ini sebanyak yang Anda inginkan—apa yang Anda lihat adalah apa yang Anda dapatkan.

Dengan Hiccup melakukan tata letak dan Reagen melakukan logika dan pemrosesan acara, kami berakhir dengan lingkungan React yang berfungsi penuh.

Contoh yang Lebih Rumit

Baiklah, mari kita ikat ini sedikit lebih banyak dengan beberapa komponen. Salah satu hal ajaib tentang React (dan Reagent) adalah Anda membagi logika tampilan dan tata letak ke dalam modul, yang kemudian dapat Anda gunakan kembali di seluruh aplikasi Anda.

Katakanlah kita membuat komponen sederhana yang menunjukkan tombol dan beberapa logika sederhana:

 ; 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?")}]])

Catatan singkat tentang penamaan: Modul di Clojure biasanya diberi namespace, jadi widget.cljs mungkin diimpor di bawah widget namespace . Ini berarti fungsi component tingkat atas akan diakses sebagai widget/component . Saya suka hanya memiliki satu komponen tingkat atas per modul, tetapi ini adalah preferensi gaya—Anda mungkin lebih suka memberi nama fungsi komponen Anda seperti polite-component atau widget-component .

Komponen sederhana ini memberi kita widget opsional yang sopan. (when polite? ", please.") dievaluasi menjadi ", please." kapan polite? == true polite? == true dan menjadi nil jika false .

Sekarang mari kita sematkan ini di dalam app.cljs kita :

 (defn app [] [:div [:h1 "Welcome to my app"] [widget/component true]])

Di sini kami menyematkan widget kami di dalam komponen aplikasi kami dengan menyebutnya sebagai item pertama dari daftar—seperti kata kunci HTML! Kami kemudian dapat meneruskan anak atau parameter apa pun ke komponen dengan menyediakannya sebagai elemen lain dari daftar yang sama. Di sini kita hanya lulus true , jadi di widget kita polite? == true polite? == true , dan dengan demikian kita mendapatkan versi yang sopan.

Jika kita mengevaluasi fungsi aplikasi kita sekarang, kita akan mendapatkan yang berikut:

 [: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.

Perhatikan bagaimana widget/component belum dievaluasi! (Lihat bagian Fungsi jika Anda bingung.)

Komponen di dalam pohon DOM Anda hanya dievaluasi (dan dengan demikian diubah menjadi objek Bereaksi nyata di belakang layar) jika telah diperbarui, yang membuat semuanya tetap bagus dan tajam serta mengurangi jumlah kerumitan yang harus Anda tangani kapan saja.

Detail lebih lanjut tentang subjek ini bagi mereka yang tertarik dapat diperoleh di dokumen Reagen.

Daftar Sepanjang Jalan

Selanjutnya, perhatikan bagaimana DOM hanyalah daftar daftar, dan komponen hanyalah fungsi yang mengembalikan daftar daftar. Mengapa ini sangat penting saat Anda mempelajari ClojureScript?

Karena apa pun yang dapat Anda lakukan pada fungsi atau daftar, Anda dapat melakukannya pada komponen.

Di sinilah Anda mulai mendapatkan hasil gabungan dengan menggunakan dialek Lisp seperti ClojureScript: Komponen dan elemen HTML Anda menjadi objek kelas satu yang dapat Anda manipulasi seperti data normal lainnya! Izinkan saya mengatakan itu lagi:

Komponen dan elemen HTML adalah objek kelas satu yang didukung dalam bahasa Clojure!

Itu benar, Anda mendengar saya. Ini hampir seperti Lisps dirancang untuk memproses daftar (petunjuk: memang begitu.)

Ini termasuk hal-hal seperti:

  • Memetakan elemen dari daftar bernomor:
 (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"]]
  • Komponen pembungkus:
 ; 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!
  • Menyuntikkan atribut:
 (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"))]])

Berurusan dengan tipe data lama seperti daftar dan peta secara dramatis lebih sederhana daripada apa pun yang menyerupai kelas, dan akhirnya menjadi jauh lebih kuat dalam jangka panjang!

Sebuah Pola Muncul

Oke, mari kita rekap. Apa yang telah ditunjukkan oleh tutorial ClojureScript kami sejauh ini?

  • Semuanya direduksi menjadi fungsionalitas paling sederhana—elemen hanyalah daftar, dan komponen hanyalah fungsi yang mengembalikan elemen.
  • Karena komponen dan elemen adalah objek kelas satu, kami dapat menulis lebih banyak dengan lebih sedikit .

Kedua poin ini sangat cocok dengan etos Clojure dan pemrograman fungsional—kode adalah data yang akan dimanipulasi, dan kompleksitas dibangun dengan menghubungkan bagian-bagian yang tidak terlalu rumit. Kami menyajikan program kami (halaman web kami dalam contoh ini) sebagai data (daftar, fungsi, peta) dan tetap seperti itu sampai saat terakhir ketika Reagen mengambil alih dan mengubahnya menjadi kode React. Ini membuat kode kita dapat digunakan kembali, dan yang terpenting sangat mudah dibaca dan dipahami dengan sedikit keajaiban.

Menjadi Bergaya

Sekarang kita tahu cara membuat aplikasi dengan beberapa fungsi dasar, jadi mari beralih ke cara membuatnya terlihat bagus. Ada beberapa cara untuk mendekati ini, yang paling sederhana adalah dengan style sheet dan mereferensikan kelas mereka di komponen Anda:

 .my-class { color: red; }
 [:div {:class "my-class"} "Hello, world!"]

Ini akan melakukan persis seperti yang Anda harapkan, memberi kami "Halo, dunia!" merah yang indah! teks.

Namun, mengapa bersusah payah menemukan tampilan dan kode logika ke dalam komponen Anda, tetapi kemudian memisahkan gaya Anda ke dalam lembar gaya—sekarang Anda tidak hanya harus melihat di dua tempat yang berbeda, tetapi Anda juga berurusan dengan dua tempat yang berbeda. bahasa juga!

Mengapa tidak menulis CSS kita sebagai kode di komponen kita (lihat tema di sini?). Ini akan memberi kita banyak keuntungan:

  • Segala sesuatu yang mendefinisikan komponen berada di tempat yang sama.
  • Nama kelas dapat dijamin unik melalui generasi yang cerdas.
  • CSS bisa dinamis, berubah seiring perubahan data kita.

Rasa favorit pribadi saya dari CSS-in-code adalah dengan Clojure Style Sheets (cljss). CSS yang disematkan terlihat seperti di bawah ini:

 ;; -- STYLES ------------------------------------------------------------ (defstyles component-style [] {:color "red" :width "100%"}) ;; -- VIEW -------------------------------------------------------------- (defn component [] [:div {:class (component-style)} "Hello, world!"])

defstyles membuat fungsi yang akan menghasilkan nama kelas unik untuk kita (yang sangat bagus untuk siapa saja yang mengimpor komponen kita.)

Ada banyak hal lain yang dapat dilakukan cljss untuk Anda (mengkomposisikan gaya, animasi, penggantian elemen, dll.) yang tidak akan saya jelaskan secara detail di sini. Saya sarankan Anda memeriksanya sendiri!

Merakit Potongan Aplikasi ClojureScript

Terakhir, ada lem yang dibutuhkan untuk merekatkan semua ini. Untungnya, selain file proyek dan index.html , boilerplate minimal di sini.

Anda membutuhkan:

  • File definisi proyek Anda, project.clj . Pokok dari setiap proyek Clojure, ini mendefinisikan dependensi Anda—bahkan langsung dari GitHub—dan properti build lainnya (mirip dengan build.gradle atau package.json .)
  • Sebuah index.html yang bertindak sebagai titik pengikatan untuk aplikasi Reagen.
  • Beberapa kode pengaturan untuk lingkungan pengembang dan akhirnya untuk memulai aplikasi Reagen Anda.

Anda akan menemukan contoh kode lengkap untuk tutorial ClojureScript ini tersedia di GitHub.

Jadi itu saja (untuk saat ini). Mudah-mudahan, saya setidaknya sedikit menggelitik rasa ingin tahu Anda, apakah itu untuk memeriksa dialek Lisp (Clojure[Script] atau lainnya) atau bahkan mencoba membuat aplikasi Reagen Anda sendiri! Saya berjanji bahwa Anda tidak akan menyesalinya.

Bergabunglah dengan saya untuk menindaklanjuti artikel ini, Getting Into a State, di mana saya berbicara tentang manajemen negara bagian menggunakan re-frame—sapa Redux di ClojureScript!