使用不带JSX ==没有构建的React
#javascript #网络开发人员 #react #jsx

删除构建步骤释放对通常过于强制的情况做出反应。

您是否曾经想不使用React没有构建步骤?是否曾经发现自己愿意使用React,但是您没有使用路线等构建单页应用程序?您是否只需要一个具有高度交互性的页面上的组件/小部件?

那么,为什么React首先需要一个构建步骤/转板器?您不能只是加载反应CDN并启动并运行吗?好吧,是的,但是您通常会使用模板,这就是JSX的来源 do 需要编译器。

没有JSX的反应

As it states on the React docs site

JSX并不需要使用React。当您不想在构建环境中设置汇编时,使用无JSX的React尤为方便。

上面的链接页面继续显示以下JSX的示例,

class Hello extends React.Component {
  render() {
    return <div>Hello {this.props.toWhat}</div>;
  }
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Hello toWhat="World" />);

以及编译的输出的外观

class Hello extends React.Component {
  render() {
    return React.createElement("div", null, `Hello ${this.props.toWhat}`);
  }
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(React.createElement(Hello, { toWhat: "World" }, null));

即使是provides a playground编写JSX,并查看最终的JavaScript输出将是什么。漂亮整洁。

太好了,我们不需要建立步骤。但是对我来说,与之合作并不是超级实用的。现代JavaScript框架的伟大之处是,它们让您与HTML动态合作。在React.createElement上裸露金属对我来说并不像是一个很棒的开发人员体验。

有一些库的选择可以轻松使用React.createElement,例如react-hyperscript。它提供了一些用于创建元素的速记,但是对我来说,从您将要输出的HTML中感觉有点太抽象了。

var h = require("react-hyperscript");
var React = require("react");

var AnotherComponent = require("./another-component");

module.exports = React.createClass({
  render: function render() {
    return h("div.example", [
      h("h1#heading", "This is hyperscript"),
      h("h2", "creating React.js markup"),
      h(AnotherComponent, { foo: "bar" }, [
        h("li", [h("a", { href: "http://whatever.com" }, "One list item")]),
        h("li", "Another list item"),
      ]),
    ]);
  },
});

但对我来说,(我认为许多React开发人员),写作HTML感觉最自然。您的组件最终使HTML呈现,因此您的模板以HTML般的结构编写是有道理的,因此JSX的受欢迎程度。

HTM模板

然而,有一个库更接近JSX (类似于HTML的感觉),但不需要构建步骤。 htm。 HTM使用标记模板来利用模板字面作为本机JavaScript模板字符串。如果您没有使用tagged templates玩,我鼓励您检查一下,这是一个非常强大的功能,最近已成为JavaScript的一部分。

如果您打算在任何时间内使用HTM,我强烈鼓励VSCode extension强调您的HTML字符串为HTML。它不仅为您的JavaScript内部的HTML提供了语法突出显示,而且还可以按照您的期望将其格式化。

标记的模板允许您将模板字符串发送到函数,以在字符串上运行其他处理/操作。您可以阅读有关tagged templates here的更多信息。但是重要的是要知道它们在all major browsers中得到了支持。

让我们看看一些例子

好吧,让我们看一下超级简单的应用程序将使用HTM而不是JSX的样子。在此示例组件中,我们将只有一个带有名称和爱好的标题,然后才能更新这些值。

要开始,让我们首先加载我们需要将cdn的javascript加载到我们的html文件中,然后启动我们的应用程序。

<head>
  <body>
    <div id="app">
      <!-- the application will be rendered here -->
    </div>

    <script
      crossorigin
      src="https://unpkg.com/react@18/umd/react.production.min.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
    ></script>

    <script type="module">
      import htm from "https://unpkg.com/htm?module";
      const html = htm.bind(React.createElement);

      import App from "./App.js";
      ReactDOM.render(html`<${App} />`, document.getElementById("app"));
    </script>
  </body>
</head>

因此,我们已将React和Reactdom加载到我们的HTML文件中,然后将HTM和我们的<App/>组件加载为模块。
(不幸的是,不幸的是没有ESM版本可以从CDN加载),因此我们将它们仅在页面上的单独脚本标签中加载,然后将它们从窗口对象中引用。

将html绑定到React.createElement

htm是一个通用库,因此,为了使其与React一起使用,我们必须将其绑定到React.Createlement。这本质上将CreateElement设置为我们的HTML字符串的标记模板函数。

import htm from "https://unpkg.com/htm?module";
const html = htm.bind(React.createElement);

然后我们渲染我们的应用程序组件

ReactDOM.render(html`<${App} />`, document.getElementById("app"));

让我们看一下我们的应用程序组件。您会注意到这与您使用JSX所写的内容有多相似。

import { useState, createElement } from "react";
import htm from "htm";
import Hobbies from "./Hobbies.js";
import "../index.css";

const html = htm.bind(createElement);

function App() {
  const [name, setName] = useState("Dave");
  const [hobbies, setHobbies] = useState("bonsai, sewing, running");

  return html`<div class="container">
    <article>
      <header>
        <hgroup>
          <h1>Hello there!</h1>
          <h2>
            My name is <mark>${name}</mark>, and my hobbies include
            <hr />
            ${hobbies
              .split(",")
              .map((hobby) => html`<kbd>${hobby.trim()}</kbd>`)}
          </h2>
        </hgroup>
      </header>
      <label>
        Name
        <input value=${name} onChange=${(ev) => setName(ev.target.value)} />
      </label>
      <label>
        Hobbies
        <${Hobbies} hobbies=${hobbies} setHobbies=${setHobbies} />
      </label>
    </article>
  </div>`;
}

export default App;

App组件依次呈现看起来像这样的爱好组件。我本可以将它们组合在一起,只有一个组件,但是我想举一个加载子组件的示例,因为我们看到了<${Hobbies}>

import { createElement } from "react";
import htm from "htm";

const html = htm.bind(createElement);

function Hobbies({ hobbies, setHobbies }) {
  return html`<textarea
    value=${hobbies}
    onChange=${(ev) => setHobbies(ev.target.value)}
  ></textarea>`;
}

export default Hobbies;

HTM和JSX之间的差异

尽管正如我们所讨论的那样,它看起来与JSX非常相似,进而又是HTML。我想指出的是一些关键区别。

插值组件名称

正如您可能已经看到的那样,您必须在渲染时“ interpaly” 组件名称,而不是像JSX中的自定义DOM元素一样使用它们。

在JSX中

<label>
  Hobbies
  <Hobbies hobbies={hobbies} setHobbies={setHobbies}></Hobbies>
</label>

htm等效

 <label>
  Hobbies
  <${Hobbies} hobbies=${hobbies} setHobbies=${setHobbies} />
</label>

您可以在课堂上使用课程而不是className!

这对我来说很大。一直困扰着我,您必须将className用于JSX中的类属性。我不确定为什么会像这样打扰我,但是每次我在JSX中的元素中添加一堂课时,我都会考虑一下。

<div className="my-class"></div>

vs

<div class="my-class"></div>

返回HTML时,您必须调用HTML方法

例如,当我们在循环中列出我们的爱好时,我们正在返回HTML,因此我们必须调用HTML方法。

${hobbies.split(",")
  .map((hobby) => html`<kbd>${hobby.trim()}</kbd>`)}

引号是可选的

围绕元素属性的引号动态或静态是完全可选的

<div class="foo"></div>

自关闭标签

htm会让您在任何事物上使用自关闭标签。

<div />

关闭组件标签

由于HTM要求您在渲染时插入组件名称,因此它具有方便的功能,可让您与<//>关闭组件,而不必再次重做名称。

<${Footer}>footer content<//>

传播道具

如果您想将组件的道具分配给元素属性,通常是root ,在JSX中,您将这样做。

<div {...props}>

在htm中,您会做同样的事情,只有字符串插值外部的传播操作员。

<div ...${props}>

布尔属性

我真的很喜欢这个功能,如果您曾经在诸如checkedselected之类的布尔属性上挣扎,也可能会。

const checked = true;
return html`<input type="checkbox" checked=${checked} /> `;

多个根元素

JSX不允许您具有多个根元素。 htm做!如果您发现自己将自己的组件包裹在React.Fragment中,以便您可以按照自己的方式构造自己的dom,那么您会喜欢的。

在JSX中

return <React.Fragment>
  <div>root item one</div>
  <div>root item two</div>
</React.Fragment>

htm

return html`<div>root item one</div>
  <div>root item two</div>`;

HTML评论

您可以利用模板中的普通HTML注释。在JSX

中不可能

预编译设置

我知道您选择HTM而不是JSX的巨大原因是将构建步骤抛在后面。话虽如此,我最近在一个出于其他原因需要建立步骤的项目上使用了HTM。在几个小的上对HTM感到满意(无构建)项目,我想继续使用它。

我错过的无编译设置的主要内容之一是热模块重新加载。一旦您使用HMR,它就会上瘾,很难返回每小时400次重新加载:)

我将举一个快速示例,说明如何将HTM集成到Berierplate create-react-app应用程序中。

npm i create-react-app react-htm
npm i htm --save-dev
npm serve

在您的输入文件中,index.js将文件更改为以下内容。

import { createRoot } from "react-dom/client";
import { createElement } from "react";
import htm from "htm";
import App from "./App.js";

const html = htm.bind(createElement);
const root = createRoot(document.getElementById("root"));
root.render(html`<${App} />`);

然后,在App.js文件中,将组件更改为以下

import logo from "./logo.svg";
import "./App.css";
import { createElement } from "react";
import htm from "htm";
const html = htm.bind(createElement);

function App() {
  return html`<div class="App">
    <header class="App-header">
      <img src="${logo}" class="App-logo" alt="logo" />
      <p>Edit <code>src/App.js</code> and save to reload.</p>
      <a
        class="App-link"
        href="https://reactjs.org"
        target="_blank"
        rel="noopener noreferrer"
      >
        Learn React
      </a>
    </header>
  </div>`;
}

export default App;

您现在正在使用htm为模板运行应用程序。您的构建和HMR将完全按照您的习惯工作。我希望您喜欢深入研究JSX的替代方案,并在未来的情况下发现它很有用。