• Час читання ~5 хв
  • 06.07.2023

Я особисто великий шанувальник JSX, і мені подобається, як він дозволяє мені розділяти та зв'язувати мій код. Незважаючи на те, що JSX існував до React, він не був би таким популярним, якби React не підняв його. Однак ми можемо використовувати JSX без React, і це також не складно.  

React працює, налаштовуючи свою обгортку для перетворення JSX у виклики функцій createElement. Так, наприклад:Однак більшість транспіллерів дозволяють вибрати власну прагму JSX (функцію, яка замінить ). Наприклад, якщо ви використовуєте Babel, ви можете вказати, яку функцію використовувати, простим коментарем:

const foo = (
    <div className="cool">
        <p>Hello there!</p>
    </div>
)
// Would become this:
React.createElement(
    'div',
    { className: 'cool' },
    React.createElement('p', null, 'Hello there!')
)

  

/** @jsx myJsxFunction */
const foo = (
    <div className="cool">
        <p>Hello there!</p>
    </div>
)

І тепер Babel пройдеReact.createElement деякі параметри myJsxFunction Тепер все, що нам потрібно зробити, це створити функцію, яка приймає ці параметри, і створити справжні вузли DOM, які ми можемо додати до нашого DOM. Отже, приступимо.  

Вузли DOM створюються за допомогою document.createNode(), і для цього потрібен лише тег, тому непогано почати з цього:Тепер

export const createElement = (tag, props, ...children) => {
    const element = document.createElement(tag)
    return element
}

, коли у нас є вузол DOM, Ми повинні фактично додати надані нам атрибути. Це може бути що завгодно, як або style. Тому ми просто пройдемося по всіх наданих атрибутах (використовуючиObject.entries  і просто встановлюючи їх на нашому вузлі DOM):  

export const createElement = (tag, props, ...children) => {
    const element = document.createElement(tag)
    Object.entries(props || {}).forEach(([name, value]) => {
        element.setAttribute(name, value.toString())
    })
    return element
}

Однак з таким підходом є одна проблема.  Наприклад, як class ми будемо обробляти події, якщо у мене є цей JSX:

const SayHello = (
    <div>
        <button onClick={() => console.log("hello there!")}>Say Hello</button>
    </div>
)

Наша функція встановить onClick як звичайний атрибут із зворотним викликом як фактичним текстом. Замість цього ми можемо перевірити, чи наш атрибут починається з "on" і чи знаходиться він у вікні "on". Це покаже нам, подія це чи ні. Наприкладonclick , в обсязі вікна, однак onfoo, немає.    Якщо так, Потім ми можемо зареєструвати обробник подій на цьому вузлі, використовуючи "увімкнено" частину імені.

Ось як це виглядає:Чудово! Тепер залишається лише додати всі дочірні елементи до батьківського. Однак ви не можете додати рядок до вузла DOM, тому, якщо дочірній елемент також не є вузлом, ми можемо створити текстовий вузол і додати його замість:

export const createElement = (tag, props, ...children) => {
  const element = document.createElement(tag)
  Object.entries(props || {}).forEach(([name, value]) => {
      if (name.startsWith('on') && name.toLowerCase() in window)
          element.addEventListener(name.toLowerCase().substr(2), value)
      else element.setAttribute(name, value.toString())
  })
  return element
}

export const createElement = (tag, props, ...children) => {
    const element = document.createElement(tag)
    Object.entries(props || {}).forEach(([name, value]) => {
        if (name.startsWith('on') && name.toLowerCase() in window)
            element.addEventListener(name.toLowerCase().substr(2), value)
        else element.setAttribute(name, value.toString())
    })
    children.forEach(child => {
        element.appendChild(
            child.nodeType === undefined
                ? document.createTextNode(child.toString())
                : child
        )
    })
    return element
} 

Однак це швидко призводить до проблем з глибоко вкладеними елементами, а також з елементами, які створюються за допомогою карт масивів. Тому замість цього давайте замінимо цю частину рекурсивним методом: І тепер ми можемо використовувати це замість нашого старого методу: Це працює! Спробуйте. Тепер ми можемо зробити базовий JSX для DOM:

const appendChild = (parent, child) => {
  if (Array.isArray(child))
    child.forEach(nestedChild => appendChild(parent, nestedChild));
  else
    parent.appendChild(child.nodeType ? child : document.createTextNode(child));
};

  

export const createElement = (tag, props, ...children) => {
    const element = document.createElement(tag)
    Object.entries(props || {}).forEach(([name, value]) => {
        if (name.startsWith('on') && name.toLowerCase() in window)
            element.addEventListener(name.toLowerCase().substr(2), value)
        else element.setAttribute(name, value.toString())
    })
    children.forEach(child => {
          appendChild(element, child);
      });
    return element
}

import { createElement } from "./Vanilla"
/** @jsx createElement */
const App = (
    <div>
        <p>My awesome app :)</p>
    </div>
)
document.getElementById("root").appendChild(App)

І ви повинні побачити ваш JSX ідеально.  Є ще кілька речей,appendChild що ми можемо додати, хоча, наприклад, в React, елементи, як правило, є функціями, реалізація яких дозволить нам вкладати компоненти і повною мірою скористатися реквізитом, який є найважливішими особливостями JSX.

К счастью, это довольно просто реализовать. Все, что нам нужно сделать, это проверить, является ли тег функцией, а не строкой. Если это так, мы не делаем ничего другого, а просто вызываем функцию. Ось як це виглядає:Чудово! Тепер залишається лише додати всі дочірні елементи до батьківського. Однак ви не можете додати рядок до вузла DOM, тому, якщо дочірній елемент також не є вузлом, ми можемо створити текстовий вузол і додати його замість:

export const createElement = (tag, props, ...children) => {
    if (typeof tag === "function") return tag(props, children)
    {...}
}

import { createElement } from "./Vanilla"
/** @jsx createElement */
const SayHello = props => (
    <div>
        <h3>Hello {props ? props.name : "world"}</h3>
        <p>I hope you're having a good day</p>
    </div>
)
/* <Component /> === Component() */
document.getElementById("root").appendChild(<SayHello name="foo" />)

/** @jsx createElement */
/** @jsxFrag createFragment */
const UsingFragment = () => (
    <div>
        <p>This is regular paragraph</p>
        <>
            <p>This is a paragraph in a fragment</p>
        </>
    </div>
)

Но чтобы это работало, нам нужна функция, которая берет этот фрагмент и вместо того, чтобы создавать элемент DOM, он просто возвращает его дочерний элемент. Ось як це виглядає:Чудово! Тепер залишається лише додати всі дочірні елементи до батьківського. Однак ви не можете додати рядок до вузла DOM, тому, якщо дочірній елемент також не є вузлом, ми можемо створити текстовий вузол і додати його замість:

const createFragment = (props, ...children) => {
  return children;
}

Це працює завдяки нашому рекурсивному методуappendChild.

І все! Ми це зробили. Надзвичайно проста функція JSX до DOM, яка дозволяє нам використовувати потужність OSX без необхідності спеціально використовувати react. Ви можете знайти вихідний код тут.

  Сподіваюся, ви знайшли цю публікацію корисною, і я також сподіваюся, що ви знайдете кілька цікавих способів використовувати всю потужність JSX. Насправді я дізнався все це, працюючи над Dhow, який є статичним генератором сайтів JSX для Node.js. В основному це дозволяє писати код у стилі Next.js, але перетворює його на статичний HTML без проблем з гідратацією.   Перевірте це і дайте мені знати, Як ти гадаєш. 

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Про автора CrazyBoy49z
WORK EXPERIENCE
Контакти
Ukraine, Lutsk
+380979856297