• Czas czytania ~6 min
  • 06.07.2023

Osobiście jestem wielkim fanem JSX i uwielbiam sposób, w jaki pozwala mi dzielić i łączyć mój kod. Mimo że JSX istniał przed React, nie byłby tak popularny, gdyby React go nie podniósł. Jednak możemy używać JSX bez React, a to też nie jest trudne.  

React działa poprzez skonfigurowanie swojego wrappera do konwersji JSX na wywołania funkcji createElement. Na przykład:Jednak większość transpillerów pozwala wybrać własną pragmę JSX (funkcję, która zastąpi ). Na przykład, jeśli używasz Babel, możesz określić, której funkcji chcesz użyć, za pomocą prostego komentarza:

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>
)

A teraz Babel przekaże React.createElementniektóre parametry myJsxFunction Teraz wszystko, co musimy zrobić, to stworzyć funkcję, która pobiera te parametry i tworzy prawdziwe węzły DOM, które możemy dodać do naszego DOM. Zacznijmy. 

 Węzły DOM są tworzone przy użyciu document.createNode(), i wymaga tylko znacznika, więc dobrze jest zacząć od tego:

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

Teraz, gdy mamy węzeł DOM, Musimy faktycznie dodać dostarczone nam atrybuty. Może to być cokolwiek takiego class jak lub . Więc po prostu przejdziemy przez wszystkie dostarczone atrybuty (używającObject.entries  i po prostu ustawiając je w naszym węźle 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
}

  Jest jednak jeden problem z tym podejściem.  Na przykład, jak stylebędziemy obsługiwać zdarzenia, jeśli mam ten JSX:

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

Nasza funkcja ustawi onClick jako normalny atrybut z wywołaniem zwrotnym jako rzeczywistym tekstem. Zamiast tego możemy sprawdzić, czy nasz atrybut zaczyna się od "on" i czy znajduje się w zasięgu okna. To pokaże nam, czy jest to wydarzenie, czy nie. Na przykładonclick , w zakresie okna, jednak onfoo, nie.    Jeśli tak, Następnie możemy zarejestrować program obsługi zdarzeń w tym węźle za pomocą części "on" nazwy.

Oto jak to wygląda:Wspaniale! Teraz pozostaje tylko dodać wszystkie elementy podrzędne do elementu nadrzędnego. Nie można jednak dodać wiersza do węzła DOM, więc w przypadku, gdy element podrzędny również nie jest węzłem, możemy utworzyć węzeł tekstowy i dodać go zamiast tego:

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
} 

Jednak szybko prowadzi to do problemów z głęboko zagnieżdżonymi elementami, a także z elementami tworzonymi przy użyciu map tablicowych. Zamiast tego zastąpmy tę część metodą rekurencyjną: A teraz możemy użyć tego zamiast naszej starej metody: To działa! Spróbuj. Teraz możemy stworzyć podstawowy JSX dla 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)

I powinieneś zobaczyć swój JSX renderowany idealnie.  Jest jeszcze kilka rzeczy,appendChild które możemy dodać, choć np. w React, elementy to zazwyczaj funkcje, których implementacja pozwoli nam zagnieżdżać komponenty i w pełni wykorzystać rekwizyty, które są najważniejszymi cechami JSX.

К счастью, это довольно просто реализовать. Все, что нам нужно сделать, это проверить, является ли тег функцией, а не строкой. Если это так, мы не делаем ничего другого, а просто вызываем функцию. Oto jak to wygląda:Wspaniale! Teraz pozostaje tylko dodać wszystkie elementy podrzędne do elementu nadrzędnego. Nie można jednak dodać wiersza do węzła DOM, więc w przypadku, gdy element podrzędny również nie jest węzłem, możemy utworzyć węzeł tekstowy i dodać go zamiast tego:

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, он просто возвращает его дочерний элемент. Oto jak to wygląda:Wspaniale! Teraz pozostaje tylko dodać wszystkie elementy podrzędne do elementu nadrzędnego. Nie można jednak dodać wiersza do węzła DOM, więc w przypadku, gdy element podrzędny również nie jest węzłem, możemy utworzyć węzeł tekstowy i dodać go zamiast tego:

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

To działa dzięki naszej metodzieappendChild rekurencyjnej.

I to wszystko! Udało się. Super prosta funkcja JSX do DOM, która pozwala nam wykorzystać moc OSX bez konieczności specjalnego korzystania z react. Kod źródłowy można znaleźć tutaj.

  Mam nadzieję, że ten post okazał się pomocny, a także mam nadzieję, że znajdziesz fajne sposoby na wykorzystanie pełnej mocy JSX. Nauczyłem się tego wszystkiego podczas pracy nad Dhow, który jest statycznym generatorem stron JSX dla Node.js. Zasadniczo pozwala pisać kod w stylu Next.js, ale konwertuje go na statyczny HTML bez problemów z hydracją.   Sprawdź to i daj mi znać, Co myślisz. 

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

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...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297