3 điểm bởi GN⁺ 2024-08-17 | 1 bình luận | Chia sẻ qua WhatsApp

Các ví dụ của Vanilla JSX

Nếu JSX trả về phần tử DOM thì sao?

  • Hàm ClickMe tạo một nút bấm và hiển thị số lần được nhấp
  • Mỗi khi nhấp vào nút, nội dung văn bản sẽ được cập nhật
export default function ClickMe() {
  let i = 0;
  const el = Click me as HTMLButtonElement;
  el.onclick = (e) => {
    el.textContent = `Clicked ${++i} times`;
  };
  return el;
}

Khả năng tái sử dụng

  • Có thể dùng component ClickMe nhiều lần để mỗi thành phần giữ trạng thái riêng
import ClickMe from "./sample1.js";
export default () =>

;

Tạo cây DOM tương tác

  • Có thể quản lý danh sách việc cần làm bằng các lớp TodoInputTodoList
  • Có thể thêm mục và nhấp để xóa chúng
function TodoInput(attrs: { add: (v: string) => void }) {
  const input =  as HTMLInputElement;
  input.placeholder = 'Add todo item...';
  input.onkeydown = (e) => {
    if (e.key === 'Enter') {
      attrs.add(input.value);
      input.value = '';
    }
  };
  return input;
}

class TodoList {
  ul =  as HTMLUListElement;
  add(v: string) {
    const item =
- {v}
 as HTMLLIElement;
    item.onclick = () => item.remove();
    this.ul.append(item);
  }
}

export default () => {
  const list = new TodoList();
  list.add('foo');
  list.add('bar');
  return
     list.add(v)} />
    {list.ul}
  ;
};

Xử lý dữ liệu quy mô lớn

  • Hàm FindNames xử lý và lọc lượng dữ liệu lớn để hiển thị kết quả
  • Các mục khớp sẽ được cập nhật theo thời gian thực dựa trên giá trị đầu vào
import { data } from "../fetch-dataset.js";
export default function FindNames() {
  const status =  as HTMLParagraphElement;
  const results =  as HTMLUListElement;
  const input =  as HTMLInputElement;
  updateMatches();

  function updateMatches() {
    const matched = (data.entries().filter(([k]) => k.match(input.value)).toArray());
    const matches = (Iterator.from(matched).map(match => ).take(30));
    results.replaceChildren(...matches);
    status.textContent = `${matched.length} / ${data.size}`;
  }

  return
    {input}
    {status}
    {results}

;
}

function Item(attrs: { match: [string, number], regex: string }) {
  const [name, count] = attrs.match;
  const total = ({count});
  return
-
     {total}

;
}

function highlight(str: string, regex: string) {
  if (!regex) return str;
  const r = new RegExp(`(${regex})`, 'gi');
  return str.replace(r, '$1');
}

Giới thiệu về imlib

  • imlib là thư viện được phát triển cho immaculatalibrary.com
  • Được dùng để xây dựng minigamemaker.com và website bạn đang đọc
  • Nó được tạo ra vì các trạng thái hiện có chưa đủ đáp ứng, và là cách được ưa thích nhất để xây dựng ứng dụng

Tóm tắt của GN⁺

  • Bài viết này giải thích cách dùng JSX để trực tiếp tạo và tương tác với các phần tử DOM
  • Đưa ra cách xử lý dữ liệu quy mô lớn hiệu quả mà không cần dùng virtual DOM
  • Thư viện imlib giúp phát triển ứng dụng theo cách đơn giản và trực quan
  • Các dự án khác có tính năng tương tự gồm React, Vue.js, v.v.

1 bình luận

 
GN⁺ 2024-08-17
Ý kiến trên Hacker News
  • Nếu trả về nút DOM thực tế thì gần như làm suy yếu lợi thế lớn của các thư viện không dùng JSX như JSX và Lit: API kiểu immediate mode và mô hình UI=f(state)
    Cần trả về mô tả của DOM chứ không phải DOM thật thì mới có thể đánh giá lại template với trạng thái mới và cập nhật hiệu quả DOM được render từ template đó
    Các ví dụ đang cập nhật bằng DOM API kiểu mệnh lệnh như input.onkeydown = ... hay this.ul.append(item), trong khi việc tránh loại mã này và lặp qua các mục trong template có lẽ là lợi ích chính của virtual DOM
    Tuy vậy, vấn đề của virtual DOM là việc tính diff chậm, và có thể giải quyết bằng template tách riêng phần tĩnh với phần động như Lit

    • Theo kinh nghiệm từng xây UI web phức tạp, trong các ứng dụng đủ phức tạp thì lợi thế hiệu năng của virtual DOM gần như là ảo tưởng
      Ngược lại, nó còn khiến người ta nghĩ “cứ render lại cả UI lớn thoải mái, virtual DOM sẽ lo”, tạo tác dụng ngược; hiệu năng thực tế thì kém nhưng cấu trúc ứng dụng lại bị cố định đến mức khó sửa nếu không viết lại
      Nhóm của bạn đã thấy đúng luồng đó, và với tư cách kiến trúc sư, bạn từng đề xuất kiến trúc vanilla chú trọng hiệu năng dựa trên những vấn đề sẽ gặp ở React, nhưng không được tiếp nhận vì kiểu lập luận “có nhiều cách để tránh mà”
      Cuối cùng có lẽ trong nhiều trường hợp người ta phải tự vất vả mới học được
    • Điều này tùy người dùng. Ở góc nhìn khác, JSX chỉ là một DSL khác cho mẫu gọi hàm createElement, còn các lợi ích được mô tả đến từ framework và runtime
      Nói ngắn gọn hơn, JSX là cú pháp thay thế cho lời gọi hàm, hữu ích trong một số ngữ cảnh
      Ở công ty cũ, JSX được dùng cho factory dữ liệu kiểm thử; trông như XML nhưng ở bước tạo phần tử có thể đưa ra các quyết định theo ngữ cảnh để phù hợp với dữ liệu cuối cùng bằng builder pattern
      Nó không liên quan gì đến React hay DOM, cũng không có nghĩa là nếu không có JSX thì không thể biểu đạt theo kiểu khai báo
    • “Điều mong muốn” là gì còn tùy vào từng người. Cá nhân tôi thích cập nhật các nút DOM ngay tại chỗ mà không cần bất kỳ cơ chế reconciliation nào
    • Tôi hiểu giấc mơ “không còn phải cập nhật theo kiểu mệnh lệnh nữa”. Tôi đã dùng các framework kiểu này khoảng 10 năm và quá quen với chúng
      Nhưng cá nhân tôi thích cập nhật DOM theo kiểu mệnh lệnh hơn. Bạn có thể kiểm soát cực kỳ chi tiết chuyện gì đang diễn ra và thiết kế theo cấu trúc rất hiệu quả
      Đồng thời có thể khiến ứng dụng dễ thêm, thay đổi, xóa và sửa tính năng mà không cần phải suy nghĩ theo một hệ phương pháp cứng nhắc của ai đó
    • Tôi đồng ý rằng việc trả về nút DOM thực tế làm suy yếu lợi thế của API kiểu immediate mode và mô hình UI=f(state) ở JSX và các thư viện không dùng JSX như Lit, nhưng điều này lại giống một abstraction bị rò rỉ vào hàng bậc nhất trong toàn bộ ngành tính toán
      Có lý do khiến quanh React hình thành cả một hệ sinh thái về cách tránh render nhiều lần và ngăn các lần refresh không cần thiết
  • Tôi làm dự án này vì đã thấy bức bối với tình trạng của trình tạo site tĩnh suốt 10 năm qua
    Vì chủ yếu làm website tĩnh nên tôi muốn công cụ đơn giản, trực quan; JSX có vẻ phù hợp nhưng tôi đã mệt với sự phức tạp quá mức của các framework JSX như React
    Vì thế tôi tạo một trình tạo site tĩnh render JSX thành chuỗi, rồi mở rộng sang phía trình duyệt để render JSX thành phần tử DOM, điều này diễn ra khá tự nhiên
    Trong một số trường hợp, đặc biệt là layout, nó cũng rất hợp với các component dùng chung. Nhìn chung tôi hài lòng với kết quả, dù một số phần khá hacky và hỗ trợ IDE vẫn có thể tốt hơn
    Ngoài ra, cách này cũng hợp với SEO. Đây là vấn đề mà tôi chưa thấy các framework JSX khác thực sự giải quyết ổn thỏa

    • Không biết bạn đã xem Astro chưa. Nó có cảm giác như một trình tạo site tĩnh gần như hoàn hảo, nên có thể tò mò liệu VanillaJSX còn đang giải quyết điểm thiếu hụt nào khác
    • Ý tưởng hay đấy. Ở trang demo, ứng dụng việc cần làm bị 404
      Trong tài liệu có lẽ nên viết đầy đủ “static site generator” thay vì “SSG”. Dù đã từng dùng trình tạo site tĩnh, tôi vẫn không biết SSG là gì và phải hỏi AI
    • Sẽ rất hay nếu có JSX phía server cho Django
    • Không biết bạn đã xem qua lit-html chưa
      Tôi chuyển từ Vue sang nên khá ngạc nhiên: chỉ với template vanilla JS đơn giản, lit-html có thể làm được nhiều việc mà template Vue làm, bao gồm cả gắn sự kiện
      Nếu dùng extension lit cho VSCode, bạn còn có thể nhận được syntax highlighting và kiểm tra kiểu đầy đủ ngay trong template
      Tôi biết đến nó sau khi Marc Grabanski tweet rằng anh ấy dùng lit-html với vanillajs chứ không phải Lit, và khi tự thử thì thấy nó hoạt động rất tốt. Nó trông khá giống với điều bạn đang cố giải quyết
      Nếu dùng gói template lit-html thì có thể làm hầu hết những gì mô tả ở phần template: https://lit.dev/docs/templates/overview/
      Thậm chí không cần abstraction component của lit-element: https://lit.dev/docs/libraries/standalone-templates/#renderi...
  • Lý do những giả định kiểu “nếu như” này thú vị là vì có thể lần ngược nguồn gốc của JSX về tận XHP của Facebook, và XHP đã được truyền cảm hứng một cách rõ ràng từ chuẩn JS đời đầu E4X, vốn trông và hoạt động tương tự thư viện được mô tả ở đây
    [1] https://engineering.fb.com/2010/02/09/developer-tools/xhp-a-...
    [2] https://en.m.wikipedia.org/wiki/ECMAScript_for_XML

    • E4X có một nhược điểm khá tai hại là nó trả về các instance DOM thực, nên phải cập nhật theo kiểu mệnh lệnh
      Đó cũng là lý do JSX đã vượt qua E4X, và sau đó không còn đề xuất nghiêm túc nào về template HTML bên trong JS nữa
      Dù vậy, có lẽ vẫn có thể hồi sinh ý tưởng chung này theo cách hiện đại hơn: https://github.com/WICG/webcomponents/issues/1069
    • Một chi tiết thú vị là lý do JavaScript có for(of) thay vì for each là vì E4X
      Còn chuyện không có được for (:) thì còn ngớ ngẩn hơn, vì nó có thể xung đột với chú thích :type mà một số thành viên TC39 từng tin rằng một ngày nào đó sẽ tự nhiên đi vào ngôn ngữ
  • Tôi từng xây dựng một thư viện UI dựa trên ý tưởng biểu thức template JSX tạo ra các node DOM thực
    Nó cũng cho phép bind object model vào thuộc tính để loại bỏ bớt phần boilerplate của event handler kiểu mệnh lệnh. Có thể đoán được là tôi sẽ nghĩ vậy, nhưng tôi thấy đây là ý tưởng hay
    https://github.com/tomtheisen/mutraction
    Bạn có thể viết code kiểu tạo model bằng track({ clicks: 0 }), tăng model.clicks khi bấm, rồi phản ánh ngay giá trị đó vào DOM

  • Ở đây cũng đáng nhắc đến VanJS: https://vanjs.org/
    Các thư viện JSX khác như Solid.JS cũng trả về các node DOM, và tôi thích việc ý tưởng này đang dần được chú ý
    Theo tôi, càng tiến gần hơn tới nền tảng mà chúng ta đang dùng thì càng tốt. Tất nhiên, lớp trừu tượng đôi khi vẫn hữu ích, nhưng trên thực tế tôi vẫn chưa thấy cần phải che giấu nền tảng
    Có thể những dự án khổng lồ như ở Facebook sẽ hưởng lợi, nhưng tôi chưa từng làm ở quy mô như vậy

    • So sánh kích thước bundle với SolidJS có phải không mấy ý nghĩa không?
  • Các ví dụ thì rất hay, nhưng điều quan trọng có vẻ là chúng không cho thấy props của component có thể thay đổi theo thời gian
    Có vẻ như khả năng đó thậm chí còn chưa được mô hình hóa. Nếu không cần thì đây là cách làm thông minh, nhưng tôi khó hình dung nó mở rộng lên các ứng dụng phức tạp hơn ra sao

    • Kỹ thuật dùng ở đây và trong toàn bộ phần code phía trình duyệt cũng chính xác là cách VS Code dùng nội bộ, và nó mở rộng khá tốt
      Khác biệt chỉ là nó viết gọn hơn phần code khoảng 10 dòng để tạo và cấu hình phần tử DOM theo cách thông thường
      Thực ra, phần thật sự thú vị trong framework này là gần như mọi thứ khác ngoài đó. Có phần JSX trả về chuỗi ở phía static site generator, phần lấy raw source directory rồi thao tác với string|Buffer tại thời điểm SSG, và một hệ thống module rất hiệu quả, rất nhanh tự xây trên chokidarswc
      Phần dùng JSX làm DOM chỉ là thứ nổi bật nhất về mặt hình ảnh, nhưng thực ra gần như chỉ là một màn biểu diễn
      Ví dụ, source của vanillajsx.com rất gọn, rõ ràng và ngắn. Tôi viết toàn bộ nó chỉ trong hôm nay, không có dependency nào ngoài imlib, và demo JSX-as-DOM lại là phần kém đột phá nhất: https://github.com/sdegutis/vanillajsx.com/tree/main/site
    • Nếu muốn mở rộng cách tiếp cận này ra quy mô thực tế và chắc chắn mọi thứ cùng hoạt động như mong đợi, bạn sẽ cần một lớp reactivity đúng nghĩa
      Nếu bạn thích cách JSX trả về các phần tử DOM cụ thể này thì Solid hoạt động chính xác như vậy, và có thêm một lớp reactivity bài bản trên đó
    • Có thể tôi đang bỏ sót gì đó, nhưng tôi không hiểu cách này ngăn việc dùng setTimeout hay setInterval như thế nào
      Tuy vậy, tôi đồng ý rằng các dự án kiểu này thường hoạt động tốt ở các trường hợp nhỏ, nhưng nhanh chóng sụp đổ trong những kịch bản “thế giới thực”
    • Tôi không hẳn bênh thư viện này và cũng không định dùng nó, nhưng để các thay đổi props gây ra re-render như React/Vue thì ở đây có lẽ chỉ cần dùng JS Proxy
      Có vẻ việc triển khai cũng không quá khó
  • Cái này rất giống Vanilla TSX: https://github.com/wisercoder/uibuilder
    Có ví dụ ứng dụng viết bằng Vanilla TSX ở đây: https://github.com/wisercoder/eureka/tree/master/webapp/Clie...

  • Nó làm tôi nhớ tới ActionScript 3, nơi XML nằm trong lõi ngôn ngữ
    Đây là một ngôn ngữ rất vui để viết, nhưng nổi tiếng là đã thất bại trong việc trở thành ES4. Rốt cuộc phải mất hơn 10 năm mới quay lại được điểm tương tự với TypeScript và JSX

    • https://en.wikipedia.org/wiki/ECMAScript_for_XML — Nó cũng từng có trong Firefox, nhưng công chúng không muốn và cuối cùng đã bị gỡ bỏ
      Nó bị vô hiệu hóa cho trang web từ Firefox 17, tức 6 tháng trước bản phát hành React đầu tiên
    • Tôi không nhớ là có thể tạo XML inline theo cách này. Có lẽ nếu đó là tính năng phía server của Flex thì có thể làm được
  • Không biết có ai khác cũng gặp việc ví dụ cuối không chạy trên Firefox không. Trên Edge thì được nhưng Firefox thì lỗi
    Uncaught (in promise) TypeError: Map.groupBy(...).entries().map is not a function

  • Tôi không thật sự hiểu vì sao JSX lại hấp dẫn hơn cách viết như dưới đây
    h("div", {}, [ h("p", {}, "this is easy"), ...list.map((l) => h("li", {}, l)) ])
    Làm như vậy thì tự động có được loop và nội suy biến mà không cần compiler hay tạo ra cú pháp mới. Mong ai đó giải thích giúp

    • Có lẽ nhiều người cảm thấy bản JSX dễ đọc và quen thuộc hơn nhiều. Tất nhiên cũng là vấn đề sở thích
      Nói công bằng thì trong JSX cũng vẫn dùng loop và nội suy thông thường. {} nhận các biểu thức JavaScript thuần
      Chỉ là cần một bước compile để hạ các thẻ phần tử thành lời gọi createElement
    • Cá nhân tôi thật sự không hiểu những người nhìn đoạn code bạn đưa ra rồi cho rằng nó tốt ngang JSX
      Mỗi người xử lý thông tin theo cách khác nhau, nhưng ví dụ đó có quá nhiều dấu câu nên rất khó nắm bắt nhanh
      Nếu không tự phân rã cú pháp trong đầu thì khó mà thấy được cấu trúc DOM được tạo ra; điều quan trọng hơn nhiều so với việc có cần compiler hay không là hiểu được cấu trúc DOM trong nháy mắt
      Nhân tiện, tôi là người thuộc phổ thần kinh đa dạng, mong là cách giải thích này hữu ích
    • Nếu tiếp tục đi xuống hang thỏ của lập luận đó thì cuối cùng sẽ tới Common Lisp
    • Tôi cũng từng nghĩ vậy. Một lợi thế có vẻ là nó trông giống HTML
      Nó khá giống những gì bạn thấy trong công cụ devtools của trình duyệt nên dễ so sánh và debug hơn
    • Với một file đơn lẻ hoặc app đơn giản thì kiểu đó cũng ổn
      Nhưng trong các app phức tạp, khi số lượng component lên tới hàng trăm và bắt đầu import qua lại, cách viết bằng thẻ JSX sẽ cho cảm giác là cú pháp đơn giản và dễ hiểu
      Sự phản ứng ngược với React cũng phần lớn đến từ việc nó quá nặng cho những việc đơn giản, và đúng là như vậy
      Nhưng app mỗi người xây dựng, độ phức tạp và yêu cầu đều khác nhau, nên không thể đơn giản hóa rằng một bên hấp dẫn hơn. Đó là những công cụ khác nhau cho những bài toán khác nhau.