Contoh praktis menggunakan fungsi render Vue: membuat kisi tipografi untuk sistem desain

Dalam materi, terjemahan yang kami terbitkan hari ini, kami akan berbicara tentang cara membuat kisi tipografi untuk sistem desain menggunakan fungsi render Vue . Ini adalah demo proyek yang akan kami ulas di sini. Anda dapat menemukan kodenya di sini. Penulis bahan ini mengatakan bahwa ia menggunakan fungsi render karena fakta bahwa mereka memungkinkan kontrol yang jauh lebih tepat atas proses pembuatan kode HTML daripada template Vue biasa. Namun, yang mengejutkannya, dia tidak dapat menemukan contoh-contoh praktis dari aplikasi mereka. Dia hanya menemukan manual instruksi. Dia berharap bahwa materi ini akan membuat perbedaan menjadi lebih baik berkat contoh praktis menggunakan fungsi render Vue di sini.


Fungsi Pembuat Vue


Bagi saya, fungsi render sepertinya sesuatu yang agak tidak lazim bagi Vue. Segala sesuatu dalam kerangka ini menekankan keinginan untuk kesederhanaan dan pemisahan tugas dari berbagai entitas. Tetapi fungsi render adalah campuran aneh dari HTML dan JavaScript, yang seringkali sulit dibaca.

Misalnya, ini adalah markup HTML:

<div class="container">   <p class="my-awesome-class">Some cool text</p> </div> 

Untuk membentuknya, Anda memerlukan fungsi berikut:

 render(createElement) {  return createElement("div", { class: "container" }, [    createElement("p", { class: "my-awesome-class" }, "Some cool text")  ]) } 

Saya menduga bahwa konstruksi seperti itu akan membuat banyak orang segera berpaling dari fungsi render. Lagi pula, kemudahan penggunaan adalah apa yang menarik pengembang ke Vue. Sangat disayangkan jika banyak orang tidak melihat pahala sejati mereka di balik penampakan fungsi render yang tidak sedap dipandang. Masalahnya adalah bahwa fungsi render dan komponen fungsional adalah alat yang menarik dan kuat. Saya, untuk menunjukkan kemampuan dan nilai sejati mereka, akan berbicara tentang bagaimana mereka membantu saya memecahkan masalah yang sebenarnya.

Harap dicatat bahwa akan sangat berguna untuk membuka versi demo proyek yang sedang dipertimbangkan di tab peramban yang berdekatan dan mengaksesnya saat membaca artikel.

Menentukan kriteria untuk sistem desain


Kami memiliki sistem desain berdasarkan VuePress . Kami perlu memasukkan halaman baru di dalamnya, menunjukkan berbagai kemungkinan tipografi desain teks. Beginilah tata letak yang diberikan perancang itu kepada saya.


Tata Letak Halaman

Dan berikut ini adalah contoh kode CSS yang sesuai dengan halaman ini:

 h1, h2, h3, h4, h5, h6 {  font-family: "balboa", sans-serif;  font-weight: 300;  margin: 0; } h4 {  font-size: calc(1rem - 2px); } .body-text {  font-family: "proxima-nova", sans-serif; } .body-text--lg {  font-size: calc(1rem + 4px); } .body-text--md {  font-size: 1rem; } .body-text--bold {  font-weight: 700; } .body-text--semibold {  font-weight: 600; } 

Header diformat berdasarkan nama tag. Untuk memformat elemen lain, nama kelas digunakan. Selain itu, ada kelas terpisah untuk kekayaan dan ukuran font.

Sebelum saya mulai menulis kode, saya merumuskan beberapa aturan:

  • Karena tujuan utama halaman ini adalah visualisasi data, data harus disimpan dalam file terpisah.
  • Untuk memformat judul, tag header semantik harus digunakan (yaitu, <h1> , <h2> dan sebagainya), pemformatannya tidak boleh berdasarkan kelas.
  • Tag paragraf ( <p> ) dengan nama kelas (misalnya, <p class="body-text--lg"> ) harus digunakan di badan halaman.
  • Bahan-bahan yang terdiri dari berbagai elemen harus dikelompokkan dengan membungkusnya dalam <p> root <p> , atau dalam elemen root lain yang sesuai yang tidak memiliki kelas stylization yang ditugaskan. Elemen turunan harus dibungkus dengan <span> , yang menetapkan nama kelas. Berikut ini aturannya:

     <p>  <span class="body-text--lg">Thing 1</span>  <span class="body-text--lg">Thing 2</span> </p> 
  • Bahan, output yang tidak ada persyaratan khusus, harus dibungkus dengan <p> , yang diberi nama kelas yang diinginkan. Elemen turunan harus dilampirkan dalam <span> :

     <p class="body-text--semibold">  <span>Thing 1</span>  <span>Thing 2</span> </p> 
  • Untuk setiap sel gaya yang akan ditata, nama kelas harus ditulis hanya sekali.

Opsi untuk memecahkan masalah


Sebelum mulai bekerja, saya mempertimbangkan beberapa opsi untuk menyelesaikan tugas yang ditetapkan sebelum saya. Inilah ikhtisar mereka.

▍ Menulis kode HTML secara manual


Saya suka menulis kode HTML secara manual, tetapi hanya jika memungkinkan kita untuk menyelesaikan masalah yang ada. Namun, dalam kasus saya, penulisan kode manual berarti memasukkan berbagai fragmen kode berulang yang menghadirkan beberapa variasi. Saya tidak menyukainya. Selain itu, ini berarti bahwa data tidak dapat disimpan dalam file terpisah. Akibatnya, saya menolak pendekatan ini.

Jika saya membuat halaman yang dimaksud begitu saja, saya akan mendapatkan sesuatu seperti berikut:

 <div class="row">  <h1>Heading 1</h1>  <p class="body-text body-text--md body-text--semibold">h1</p>  <p class="body-text body-text--md body-text--semibold">Balboa Light, 30px</p>  <p class="group body-text body-text--md body-text--semibold">    <span>Product title (once on a page)</span>    <span>Illustration headline</span>  </p> </div> 

▍Menggunakan pola Vue tradisional


Dalam kondisi normal, pendekatan ini paling sering digunakan. Namun, lihat contoh ini .


Contoh menggunakan templat Vue

Di kolom pertama ada yang berikut ini:

  • <h1> , yang disajikan dalam bentuk di mana browser menampilkannya.
  • <p> mengelompokkan beberapa <span> anak-anak dengan teks. Kelas ditugaskan untuk masing-masing elemen ini (tetapi tidak ada kelas khusus yang ditugaskan untuk tag <p> itu sendiri).
  • <p> yang tidak memiliki elemen <span> bersarang tempat kelas ditugaskan.

Untuk menerapkan semua ini, banyak contoh arahan v-if dan v-if-else diperlukan. Dan ini, saya tahu, akan mengarah pada fakta bahwa kode akan menjadi sangat membingungkan segera. Juga, saya tidak suka menggunakan semua logika kondisional ini di markup.

▍Menghasilkan fungsi


Akibatnya, setelah menganalisis kemungkinan alternatif, saya memilih fungsi render. Di dalamnya, menggunakan JavaScript, menggunakan konstruksi kondisional, simpul anak dari simpul lain dibuat. Saat membuat simpul anak ini, semua kriteria yang diperlukan diperhitungkan. Dalam situasi ini, solusi seperti itu tampak sempurna bagi saya.

Model data


Seperti yang saya katakan, saya ingin menyimpan data tipografi dalam file JSON yang terpisah. Ini akan memungkinkan, jika perlu, untuk melakukan perubahan tanpa menyentuh markup. Ini datanya.

Setiap objek JSON dalam file adalah deskripsi dari baris terpisah:

 {  "text": "Heading 1",  "element": "h1", //  .  "properties": "Balboa Light, 30px", //   .  "usage": ["Product title (once on a page)", "Illustration headline"] //   .   -   . } 

Berikut ini HTML yang muncul setelah memproses objek ini:

 <div class="row">  <h1>Heading 1</h1>  <p class="body-text body-text--md body-text--semibold">h1</p>  <p class="body-text body-text--md body-text--semibold">Balboa Light, 30px</p>  <p class="group body-text body-text--md body-text--semibold">    <span>Product title (once on a page)</span>    <span>Illustration headline</span>  </p> </div> 

Sekarang perhatikan contoh yang lebih kompleks. Array mewakili kelompok anak-anak. Properti objek classes , yang merupakan objek, dapat menyimpan deskripsi kelas. Properti base dari objek classes berisi deskripsi kelas yang umum untuk semua node di sel. Setiap kelas yang ada di properti variants diterapkan ke elemen tunggal dalam grup.

 {  "text": "Body Text - Large",  "element": "p",  "classes": {    "base": "body-text body-text--lg", //     .    "variants": ["body-text--bold", "body-text--regular"] //    ,       .        .  },  "properties": "Proxima Nova Bold and Regular, 20px",  "usage": ["Large button title", "Form label", "Large modal text"] } 

Objek ini berubah menjadi HTML berikut:

 <div class="row">  <!--  1 -->  <p class="group">    <span class="body-text body-text--lg body-text--bold">Body Text - Large</span>    <span class="body-text body-text--lg body-text--regular">Body Text - Large</span>  </p>  <!--  2 -->  <p class="group body-text body-text--md body-text--semibold">    <span>body-text body-text--lg body-text--bold</span>    <span>body-text body-text--lg body-text--regular</span>  </p>  <!--  3 -->  <p class="body-text body-text--md body-text--semibold">Proxima Nova Bold and Regular, 20px</p>  <!--  4 -->  <p class="group body-text body-text--md body-text--semibold">    <span>Large button title</span>    <span>Form label</span>    <span>Large modal text</span>  </p> </div> 

Struktur dasar proyek


Kami memiliki komponen induk, TypographyTable.vue , yang berisi markup untuk membentuk tabel. Kami juga memiliki komponen TypographyRow.vue , TypographyRow.vue , yang bertanggung jawab untuk membuat baris tabel dan berisi fungsi render kami.

Saat membentuk baris tabel, array dengan data dilintasi. Objek yang menggambarkan baris tabel diteruskan ke komponen TypographyRow sebagai properti.

 <template>  <section>    <!--         -->    <div class="row">      <p class="body-text body-text--lg-bold heading">Hierarchy</p>      <p class="body-text body-text--lg-bold heading">Element/Class</p>      <p class="body-text body-text--lg-bold heading">Properties</p>      <p class="body-text body-text--lg-bold heading">Usage</p>    </div>     <!--             -->    <typography-row      v-for="(rowData, index) in $options.typographyData"      :key="index"      :row-data="rowData"    />  </section> </template> <script> import TypographyData from "@/data/typography.json"; import TypographyRow from "./TypographyRow"; export default {  //     ,        typographyData: TypographyData,  name: "TypographyTable",  components: {    TypographyRow }; </script> 

Di sini saya ingin mencatat satu hal yang menyenangkan: data tipografi dalam instance Vue dapat direpresentasikan sebagai properti. Anda dapat mengaksesnya menggunakan konstruk $options.typographyData , karena tidak berubah dan tidak boleh reaktif (terima kasih kepada Anton Kosykh ).

Membuat komponen fungsional


Komponen TypographyRow yang memproses data adalah komponen fungsional. Komponen fungsional adalah entitas yang tidak memiliki status dan instance. Ini berarti bahwa mereka tidak memiliki this , dan bahwa mereka tidak memiliki akses ke metode siklus hidup komponen Vue.

Ini adalah "kerangka" dari komponen yang sama dengan mana kita akan mulai mengerjakan komponen kita:

 //  <template> <script> export default {  name: "TypographyRow",  functional: true, //       props: {    rowData: { //          type: Object     },  render(createElement, { props }) {    //    } </script> 

Metode komponen render mengambil argumen context yang memiliki properti props . Properti ini dapat dirusak dan digunakan sebagai argumen kedua.

Argumen pertama adalah createElement . Ini adalah fungsi yang memberitahu Vue simpul mana yang harus dibuat. Demi singkatnya dan standarisasi kode, saya menggunakan singkatan h untuk createElement . Anda dapat membaca tentang mengapa saya melakukan ini di sini .

Jadi, h mengambil tiga argumen:

  1. Tag HTML (mis. div ).
  2. Objek data yang berisi atribut templat (misalnya, { class: 'something'} ).
  3. String teks (jika kita hanya menambahkan teks) atau simpul anak yang dibuat menggunakan h .

Begini tampilannya:

 render(h, { props }) {  return h("div", { class: "example-class" }, "Here's my example text") } 

Mari kita simpulkan apa yang sudah kita buat. Yaitu, kami sekarang memiliki yang berikut:

  1. File dengan data yang rencananya akan digunakan dalam pembentukan halaman.
  2. Komponen Vue reguler yang mengimpor file data.
  3. Kerangka kerja komponen fungsional yang bertanggung jawab untuk menampilkan baris tabel.

Untuk membuat baris tabel, data dari format JSON harus dikirimkan sebagai argumen ke h . Anda dapat mentransfer semua data tersebut dalam sekali jalan, tetapi dengan pendekatan ini Anda akan memerlukan sejumlah besar logika kondisional, yang akan menurunkan kelengkapan kode. Sebaliknya, saya memutuskan untuk melakukan ini:

  1. Ubah data menjadi format standar.
  2. Tampilan data yang diubah.

Transformasi data


Saya ingin data saya disajikan dalam format yang sesuai dengan argumen yang diterima oleh h . Tetapi sebelum mengonversinya, saya merencanakan struktur apa yang harus mereka miliki di file JSON:

 //   {  tag: "", // HTML-    cellClass: "", //   .       -   null  text: "", // ,     children: [] //   ,     .     ,    . } 

Setiap objek mewakili satu sel tabel. Empat sel membentuk setiap baris tabel (mereka dikumpulkan dalam array):

 //   [ { cell1 }, { cell2 }, { cell3 }, { cell4 } ] 

Titik input dapat berupa fungsi seperti berikut:

 function createRow(data) { //      ,         let { text, element, classes = null, properties, usage } = data;  let row = [];  row[0] = createCellData(data) //         row[1] = createCellData(data)  row[2] = createCellData(data)  row[3] = createCellData(data)  return row; } 

Mari kita lihat tata letak lagi.


Tata Letak Halaman

Anda dapat melihat bahwa pada kolom pertama elemen-elemennya ditata secara berbeda. Dan di kolom yang tersisa format yang sama digunakan. Jadi mari kita mulai dengan ini.
Biarkan saya mengingatkan Anda bahwa saya ingin menggunakan struktur JSON berikut sebagai model untuk menggambarkan setiap sel:

 {  tag: "",  cellClass: "",  text: "",  children: [] } 

Dengan pendekatan ini, struktur seperti pohon akan digunakan untuk menggambarkan setiap sel. Ini justru karena beberapa sel mengandung kelompok anak-anak. Kami menggunakan dua fungsi berikut untuk membuat sel:

  • Fungsi createNode mengambil setiap properti yang kami tertarik sebagai argumen.
  • Fungsi createCell memainkan peran pembungkus di sekitar createNode , dengan bantuannya kami memeriksa apakah text argumen text array. Jika demikian, kami membuat array anak-anak.

 //    function createCellData(tag, text) {  let children;  //  ,         const nodeClass = "body-text body-text--md body-text--semibold";  //   text   -   ,    span.  if (Array.isArray(text)) {    children = text.map(child => createNode("span", null, child, children));   return createNode(tag, nodeClass, text, children); } //    function createNode(tag, nodeClass, text, children = []) {  return {    tag: tag,    cellClass: nodeClass,    text: children.length ? null : text,    children: children  }; } 

Sekarang kita bisa melakukan sesuatu seperti ini:

 function createRow(data) {  let { text, element, classes = null, properties, usage } = data;  let row = [];  row[0] = ""  row[1] = createCellData("p", ?????) //         row[2] = createCellData("p", properties) //    row[3] = createCellData("p", usage) //    return row; } 

Saat membentuk kolom ketiga dan keempat, kami meneruskan properties dan usage sebagai argumen teks. Namun, kolom kedua berbeda dari kolom ketiga dan keempat. Di sini kami menampilkan nama-nama kelas, yang, dalam sumber data, disimpan dalam formulir ini:

 "classes": {  "base": "body-text body-text--lg",  "variants": ["body-text--bold", "body-text--regular"] }, 

Selain itu, jangan lupa bahwa saat bekerja dengan header, kelas tidak digunakan. Oleh karena itu, kita perlu membuat nama tag header untuk baris yang sesuai (yaitu, h1 , h2 , dan sebagainya).

Kami membuat fungsi bantu yang memungkinkan kami mengubah data ini menjadi format yang memfasilitasi penggunaannya sebagai argumen text .

 //          function displayClasses(element, classes) {  //   ,     (   )  return getClasses(classes) ? getClasses(classes) : element; } //       (    )     (   ),   null (  ,   ) // : "body-text body-text--sm" or ["body-text body-text--sm body-text--bold", "body-text body-text--sm body-text--italic"] function getClasses(classes) {  if (classes) {    const { base, variants = null } = classes;    if (variants) {      //             return variants.map(variant => base.concat(`${variant}`));       return base;   return classes; } 

Sekarang kita bisa melakukan hal berikut:

 function createRow(data) {  let { text, element, classes = null, properties, usage } = data;  let row = [];  row[0] = ""  row[1] = createCellData("p", displayClasses(element, classes)) //    row[2] = createCellData("p", properties) //    row[3] = createCellData("p", usage) //    return row; } 

Transformasi data yang digunakan untuk menunjukkan gaya


Kita perlu memutuskan apa yang harus dilakukan dengan kolom pertama dari tabel, menunjukkan contoh penerapan gaya. Kolom ini berbeda dari yang lain. Di sini kami menerapkan tag dan kelas baru untuk setiap sel alih-alih menggunakan kombinasi kelas yang digunakan oleh kolom yang tersisa:

 <p class="body-text body-text--md body-text--semibold"> 

Alih-alih mencoba mengimplementasikan fungsi ini di createCellData atau createNodeData , saya mengusulkan untuk membuat fungsi baru yang akan mengambil keuntungan dari kemampuan fungsi-fungsi dasar ini yang melakukan transformasi data. Ini akan menerapkan mekanisme pemrosesan data baru:

 function createDemoCellData(data) {  let children;  const classes = getClasses(data.classes);  //   ,       ,               if (Array.isArray(classes)) {    children = classes.map(child =>      //    "data.text"      ,   ,            createNode("span", child, data.text, children)    );   //   ,       if (typeof classes === "string") {    return createNode("p", classes, data.text, children);   //  ,    ( )  return createNode(data.element, null, data.text, children); } 

Sekarang data string direduksi menjadi format yang dinormalisasi dan dapat diteruskan ke fungsi render:

 function createRow(data) {  let { text, element, classes = null, properties, usage } = data  let row = []  row[0] = createDemoCellData(data)  row[1] = createCellData("p", displayClasses(element, classes))  row[2] = createCellData("p", properties)  row[3] = createCellData("p", usage)  return row } 

Render data


Berikut cara merender data yang ditampilkan di halaman:

 //   ,    "props" const rowData = props.rowData; //   ,     const row = createRow(rowData); //    "div"     return h("div", { class: "row" }, row.map(cell => renderCells(cell))); //    function renderCells(data) {  //  ,      if (data.children.length) {    return renderCell(      data.tag, //          { //            class: {          group: true, //    ""               [data.cellClass]: data.cellClass //      ,                  },      //        data.children.map(child => {        return renderCell(          child.tag,          { class: child.cellClass },          child.text        );      })    );   //     -     return renderCell(data.tag, { class: data.cellClass }, data.text); } // -  "h"     function renderCell(tag, classArgs, text) {  return h(tag, classArgs, text); } 

Sekarang semuanya sudah siap ! Di sini , sekali lagi, kode sumber.

Ringkasan


Perlu dikatakan bahwa pendekatan yang dipertimbangkan di sini adalah cara eksperimental untuk menyelesaikan masalah yang agak sepele. Saya yakin bahwa banyak orang akan mengatakan bahwa solusi ini sangat rumit dan berlebihan dengan kelebihan teknik. Mungkin saya akan setuju dengan itu.

Terlepas dari kenyataan bahwa pengembangan proyek ini memakan banyak waktu, data sekarang benar-benar terpisah dari presentasi. Sekarang, jika desainer kami masuk untuk menambahkan beberapa baris ke tabel, atau menghapus baris yang ada dari sana, saya tidak perlu menyapu kode HTML yang membingungkan . Untuk melakukan ini, cukup bagi saya untuk mengubah beberapa properti di file JSON.

Apakah hasilnya sepadan dengan usaha? Saya pikir perlu melihat keadaan. Ini, bagaimanapun, sangat karakteristik pemrograman. Saya ingin mengatakan bahwa di kepala saya, dalam proses mengerjakan proyek ini, gambar berikut ini terus muncul.


Mungkin ini adalah jawaban untuk pertanyaan saya tentang apakah proyek ini sepadan dengan usaha yang dihabiskan untuk pengembangannya.

Pembaca yang budiman! ? ?

Source: https://habr.com/ru/post/id458494/


All Articles