VanillaJSX.com
VanillaJSX.com

原始链接: https://vanillajsx.com/

本机实现的 JSX 返回可以直接使用和操作的 DOM 元素,使它们可重用并能够保持自己的状态。 通过使用单击或按键等事件,这些元素可以相互交互以构建复杂的交互式网页。 大型数据集可以在原生 JSX 中高效处理,而不需要单独的 Virtual Dom 库,但性能可能会成为非常大的数据集的问题。 为了处理更复杂的状态,嵌套组件允许父组件通过分派事件响应子组件所做的更改。 例如,在此代码片段中,“TodoList”组件根据用户输入创建动态的交互式列表,而“FindNames”函数使用 JSX 动态显示搜索结果并筛选名称数据集。 Native JSX 允许开发人员使用熟悉的 JavaScript 语法编写应用程序,而不必处理原始 HTML 标记,从而简化了 Web 开发。 然而,管理更大、更复杂的状态可能需要额外的库,如 Redux 或 MobX。

通过返回 DOM 的描述而不是实际 DOM 来避免手动 DOM 操作,从而允许通过使用虚拟文档对象模型 (VDOM) 或类似技术来重复评估和有效更新 UI。 React、Angular 和 Vue 是利用 VDOM 来实现此目的的流行库。 然而,由于优化不佳、复杂性增加以及缺乏适当架构的经验,VDOM 最初感知到的好处(例如改进的性能和易于维护)对于复杂的 Web 应用程序来说可能是虚幻的。 作者主张使用轻量级框架,例如 lit-html 和 vanillaJSX,它们提供与 VDOM 类似的优点,而不会增加过多的复杂性。 此外,作者建议最终目标应该是简单且易于理解的代码,并认为更少的代码最终会导致更少的错误、更容易的调试和更高的生产力。
相关文章

原文
Vanilla JSX
Join me in brainstorming how JSX might work natively.

What if JSX just returned DOM elements?

View source

export default function ClickMe() {
  let i = 0;
  const el = <button>Click me</button> as HTMLButtonElement;
  el.onclick = (e) => {
    el.textContent = `Clicked ${++i} times`;
  };
  return el;
}

Would they be reusable?

Could they keep their own state?

View source

import ClickMe from "./sample1.js";

export default () => <>
  <p><ClickMe /></p>
  <p><ClickMe /></p>
  <p><ClickMe /></p>
</>;

How would they work together?

Could they create an interactive DOM tree?

View source

function TodoInput(attrs: { add: (v: string) => void }) {
  const input = <input type='text' /> 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 = <ul class='todolist' /> as HTMLUListElement;
  add(v: string) {
    const item = <li>{v}</li> as HTMLLIElement;
    item.onclick = () => item.remove();
    this.ul.append(item);
  }
}

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

How would they handle large data?

Could they be convenient without a virtual dom?

View source

import { data } from "../fetch-dataset.js";
// Names of US citizens born in 1882 from ssa.gov

export default function FindNames() {
  const status = <p class='status' /> as HTMLParagraphElement;
  const results = <ul /> as HTMLUListElement;
  const input = <input
    type='text'
    value='.?mary?'
    autocomplete='new-password'
    oninput={updateMatches}
  /> as HTMLInputElement;

  updateMatches();
  function updateMatches() {
    const regex = new RegExp(`(${input.value})`, 'gi');
    const matched = (data.entries()
      .filter(([k]) => k.match(regex))
      .toArray());

    const matches = (Iterator.from(matched)
      .map(match => <Item regex={regex} match={match} />)
      .take(25));

    results.replaceChildren(...matches);
    status.textContent = `${matched.length} / ${data.size}`;
  }

  return <div>{input}{status}{results}</div>;
}

function Item(attrs: { match: [string, number], regex: RegExp }) {
  const [name, count] = attrs.match;
  const total = <small style='color:#fff3'>({count})</small>;
  return <li>
    <span innerHTML={highlight(name, attrs.regex)} /> {total}
  </li>;
}

function highlight(str: string, regex: RegExp) {
  return str.replace(regex, '<span class="match">$1</span>');
}

How would they manage complex state?

How could parent components react to children?

View source

export default () => <>
  <TodoList />
</>;

function TodoList() {
  const list = new List();

  list.add('foo');
  list.add('bar').toggle();
  list.add('qux');

  const input = <input type='text' /> as HTMLInputElement;
  input.onkeydown = (e) => {
    if (e.key === 'Enter' && input.value.trim().length > 0) {
      list.add(input.value);
      input.value = '';
    }
  };

  return <div id='real-todolist'>
    <div>{input}</div>
    <div class='actions'>
      <Counter list={list} />
      <button onclick={() => list.clearDone()}>Clear</button>
      <button onclick={() => list.invertAll()}><i>Invert</i></button>
    </div>
    {list.ul}
  </div>;
}

class List extends EventTarget {

  ul = <ul class='list' /> as HTMLUListElement;
  items: Item[] = [];
  itemUnlisteners = new Map<Item, () => void>();

  add(text: string) {
    const item = new Item(this, text);
    this.items.push(item);
    this.ul.append(item.li);
    this.dispatchEvent(new Event('item-added'));

    this.itemUnlisteners.set(item, listen(item, 'toggled', () => {
      this.dispatchEvent(new Event('item-toggled'));
    }));

    return item;
  }

  rem(item: Item) {
    const unlisten = this.itemUnlisteners.get(item)!;
    this.itemUnlisteners.delete(item);
    unlisten();

    this.items = this.items.filter(it => it !== item);
    this.dispatchEvent(new Event('item-removed'));
  }

  clearDone = () => this.doneItems().forEach(it => it.remove());
  invertAll = () => this.items.forEach(it => it.toggle());

  doneItems = () => this.items.filter(it => it.done);

}

class Item extends EventTarget {

  done = false;
  #checkbox = <input type='checkbox' /> as HTMLInputElement;
  li;

  constructor(private list: List, text: string) {
    super();
    this.li = (
      <li class='item'>
        {this.#checkbox}
        <span onclick={() => this.toggle()}>{text}</span>
        <button class='close' onclick={() => this.remove()}>✕</button>
      </li> as HTMLLIElement
    );
    this.#checkbox.onclick = () => this.toggle();
  }

  remove() {
    this.li.remove();
    this.list.rem(this);
  }

  toggle() {
    this.done = !this.done;
    this.li.classList.toggle('done', this.done);
    this.#checkbox.checked = this.done;
    this.dispatchEvent(new Event('toggled'));
  }

}

function Counter({ list }: { list: List }) {
  const span = <span /> as HTMLSpanElement;

  const updateText = () => {
    const done = list.doneItems().length;
    const total = list.items.length;
    span.textContent = `Done: ${done}/${total}`;
  };

  updateText();
  list.addEventListener('item-added', updateText);
  list.addEventListener('item-removed', updateText);
  list.addEventListener('item-toggled', updateText);

  return span;
}

function listen(target: EventTarget, event: string, fn: () => void) {
  target.addEventListener(event, fn);
  return () => target.removeEventListener(event, fn);
}

The only runtime needed for this is @imlib/jsx-browser.js.

JSX is compiled by @swc/core to automatically import it.

The idea came out of my work on imlib.

Also check out the source to this page.

联系我们 contact @ memedata.com