Komponen Web Alih-alih Bereaksi - Upaya Lain

Halo, Habr!

Baru-baru ini saya memutuskan untuk mencari tahu seperti apa aplikasi web sisi klien yang ditulis sepenuhnya pada komponen web vanilla tanpa menggunakan kerangka kerja. Ternyata cukup berhasil, pada akhirnya saya membuat sketsa templat PWA seluler , yang sekarang saya gunakan dalam prototipe. Melanjutkan dari bangunan berikut:

  • DOM adalah suatu keadaan. Karena kami tidak memiliki kerangka kerja, kami segera melupakan fungsionalisme dan kembali ke OOP imperatif. Komponen web adalah node DOM berumur panjang yang merangkum keadaan mereka dan memiliki API publik. Mereka tidak diciptakan kembali, tetapi diubah. Ini berarti bahwa kita harus mempertimbangkan DOM tidak hanya sebagai representasi, tetapi sebagai repositori objek bisnis, dan oleh karena itu kita perlu membangun hierarki komponen dengan mempertimbangkan kenyamanan interaksi mereka.
  • Interaksi komponen. Komponen dapat berkomunikasi melalui panggilan langsung, pertukaran panggilan balik, atau melalui peristiwa DOM pengguna naik / turun. Metode yang terakhir paling disukai, karena mengurangi keterlibatan bersama (coupling), dan memesan grafik obligasi (lihat contoh di bawah).
    Peristiwa DOM hanya bekerja di dalam hierarki - mereka dapat muncul dari bawah ke atas rantai leluhur, atau disiarkan ke semua keturunan. Dalam kasus lain, API browser standar digunakan untuk mengatasi komponen: document.querySelector ('halaman-rumah'), dan beberapa komponen dapat mendaftarkan diri di jendela dan digunakan secara global: APP.route ('halaman-login').
  • Gaya batin. Shadow DOM tidak digunakan, oleh karena itu komponen mewarisi gaya global, tetapi juga dapat memiliki sendiri. Karena <style scoped> tidak mungkin diimplementasikan dalam waktu dekat, Anda harus menggunakan awalan nama komponen untuk mendeklarasikan style internal, tetapi ini diam-diam dan berfungsi dengan baik (lihat contoh di bawah).
  • HTML/DOM. DOM β€” , HTML (value, checked, innerHTML contenteditable=Β«trueΒ» ..). JS , β€” / , ( ). , , this.pass β€” , <input> . , DOM, , , , .
  • Navigasi. Komponen halaman hidup di dalam wadah <main>, dan setelah dibuat, mereka tidak dihapus, tetapi hanya disembunyikan. Ini memungkinkan Anda untuk menerapkan navigasi menggunakan location.hash, dan tombol browser standar bolak-balik berfungsi dengan benar. Saat menavigasi ke komponen yang ada, metode onRoute () dipanggil, di mana Anda dapat memperbarui data.

Struktur aplikasi


Aplikasi kami terdiri dari:

  • komponen root <app-app>, dapat diakses melalui window.APP, berisi router halaman dan fungsionalitas global;
  • panel dengan tombol kontekstual (saya tidak memasukkannya ke komponen terpisah, tetapi menjadikannya bagian dari tata letak <app-app> untuk menyederhanakan penanganan acara);
  • menu tarik-turun (komponen terpisah);
  • wadah <main> ke mana komponen halaman akan ditambahkan: <page-home>, <page-login>, <page-work> saat dibuka.

Halaman ditumpuk dengan navigasi bolak-balik. Selain itu, kami mendemonstrasikan aliran data bottom-up dan top-down:

  • Status otorisasi dan nama pengguna saat ini disimpan di komponen <app-app>, tetapi berasal dari komponen <page-login> melalui acara sembulan.
  • Sebuah pengatur waktu mencentang di komponen <app-app>, yang mengirimkan nilai saat ini turun melalui acara siaran yang hanya dicegat di turunan <page-work>.

Melalui acara pop-up, pemasangan tombol peka konteks pada panel aplikasi juga dilaksanakan - tombol itu sendiri, bersama-sama dengan handler, dibuat di komponen halaman, kemudian dikirim ke atas, di mana mereka dicegat di tingkat aplikasi dan dimasukkan ke dalam panel.

Penerapan


Untuk bekerja dengan DOM internal komponen, dan mengirim acara hilir, pustaka WcMixin.js kecil digunakan - kurang dari 200 baris kode, setengahnya (penyatuan peristiwa input pengguna) juga dapat dibuang. Yang lainnya adalah vanila murni. Komponen khas (halaman otorisasi) terlihat seperti ini:

import wcmixin from './WcMixin.js'

const me = 'page-login'
customElements.define(me, class extends HTMLElement {
   _but = null

   connectedCallback() {
      this.innerHTML = `
         <style scoped>
            ${me} {
               height: 90%; width: 100%;
               display: flex; flex-direction: column;
               justify-content: center; align-items: center;
            }
            ${me} input { width: 60%; }
         </style>
         <input w-id='userInp/user' placeholder='user'/> 
         <input w-id='passInp/pass' type='password' placeholder='password'/>
      `
      wcmixin(this)

      this.userInp.oninput = (ev) => {
         this.bubbleEvent('login-change', {logged: false, user: this.user})
      }

      this.passInp.onkeypress = (ev) => {
         if (ev.key === 'Enter') this.login()
      }
   }

   onRoute() {
      this.userInp.focus()
      this._but = document.createElement('button')
      this._but.innerHTML = 'Log in<br>β‡’'
      this._but.onclick = () => this.login()
      this.bubbleEvent('set-buts', { custom: [this._but] })
   }

   async login() {
      APP.msg = 'Authorization...'
      this._but.disabled = true
      setTimeout(() => {
         this._but.disabled = false
         if (this.user) {
            this.bubbleEvent('login-change', {logged: true, user: this.user})
            APP.route('page-work')
         } else {
            APP.msg = 'Empty user !'
            this.userInp.focus()
         }
      }, 1500)
   }
})

Pertama, di sini kita melihat gaya komponen lokal. Kedua, satu-satunya atribut non-standar w-id = `` userInp / user '' telah ditambahkan ke markup HTML. Fungsi wcmixin () memproses semua elemen yang ditandai dengan atribut ini dan menambahkan variabel ke komponen saat ini: this.userInp merujuk ke elemen <input> itu sendiri (yang memungkinkan Anda untuk menggantung handler), dan this.user adalah nilai dari elemen (nama pengguna). Jika akses ke elemen tidak diperlukan, Anda dapat menentukan w-id = `` / pengguna '', dan hanya nilainya yang akan dibuat.

Saat memasukkan nama pengguna, kami mengirimkan nilai saat ini melalui acara sembulan, membuat tombol peka konteks dalam metode onRoute (), dan juga mengirimkannya.

Adalah penting bahwa komponen otorisasi tidak tahu apa-apa tentang komponen yang lebih tinggi dari aplikasi / panel, yaitu, tidak terpancing. Dia hanya mengirim acara di lantai atas, dan siapa pun yang mencegatnya terserah pengembang. Penerimaan acara dari aplikasi <app-app> dalam komponen <page-work> diimplementasikan dengan cara yang sama:

import wcmixin from './WcMixin.js'

const me = 'page-work'
customElements.define(me, class extends HTMLElement {

   connectedCallback() {
      this.innerHTML = `
         <p w-id='/msg'>Enter text:</p>
         <p w-id='textDiv/text' contenteditable='true'>1)<br>2)<br>3)</p>
      `
      wcmixin(this)

      this.addEventListener('notify-timer', (ev) => {
         this.msg = `Enter text (elapsed ${ev.val}s):`
      })
   }

   async onRoute() {
      this.textDiv.focus()
      document.execCommand('selectAll',false,null)
      const but = document.createElement('button')
      but.innerHTML = 'Done<br>β‡’'
      but.onclick = () => alert(this.text)
      this.bubbleEvent('set-buts', { custom: [but] })
   }
})

Dan di komponen <app-app> kita menulis:

setInterval(() => {
   this._elapsed += 1
   this.drownEvent('notify-timer', this._elapsed)
}, 1000)

Komponen <app-app> juga tidak tahu apa-apa tentang komponen halaman yang ingin menggunakan penghitungnya, yaitu, ia tidak terikat pada turunannya. Cukup bagi pengembang untuk menyetujui tanda tangan acara. Acara DOM ringan, acara turun dikirim hanya ke komponen web (dan bukan elemen sederhana), dan acara naik melewati seluruh rantai leluhur sebagai standar.

Sebenarnya, hanya itu yang ingin saya katakan.

β†’  Kode proyek lengkap

Keberatan dan saran


Seringkali saya keberatan bahwa pendekatan seperti itu menggabungkan logika bisnis dan tampilan dalam satu komponen β€œtebal”, yang melanggar pola yang diterima secara umum. Namun, kita hanya berbicara tentang logika untuk menampilkan dan memvalidasi input pengguna, sisa logika bisnis dapat dengan mudah dipindahkan ke kelas JS atau bahkan layanan terpisah - dengan hierarki dan metode interaksi sendiri.

Mengapa ini perlu? Masih ada masalah kinerja rendering (pengumpulan sampah tidak gratis), dan pendekatan imperatif menggunakan alat asli akan selalu lebih cepat dan lebih sedikit sumber daya intensif daripada deklaratif / fungsional menggunakan perpustakaan JS dan VDOM. Jika Anda mau, saya siap bersaing dengan perwakilan dari kerangka apa pun, pada TOR yang disepakati, jika Anda menjalankan fungsi pembandingan (saya bisa melakukannya dengan buruk).

Terimakasih atas perhatiannya.

All Articles