Skip to content
Mithril.js 2
Main Navigation 指南API

简体中文

English
繁體中文
Español
Français
Русский
Português – Brasil
Deutsch
日本語
한국어
Italiano
Polski
Türkçe
čeština
magyar

简体中文

English
繁體中文
Español
Français
Русский
Português – Brasil
Deutsch
日本語
한국어
Italiano
Polski
Türkçe
čeština
magyar

主题

Sidebar Navigation

入门

安装

简单应用

资源

JSX

在旧版浏览器上使用 ES6+

动画

测试

示例

第三方库集成

路径处理

关键概念

虚拟 DOM

组件

生命周期方法

键(Key)

自动重绘系统

杂项

框架对比

从 v1.x 迁移

从 v0.2.x 迁移

API

页面导航

JSX ​

描述 ​

JSX 是一种 JavaScript 语法扩展,允许您在 JavaScript 代码中编写类似 HTML 的标签。它不是 JavaScript 标准,也不是构建应用程序的必要条件,但如果您或您的团队喜欢,可以使用它。

jsx
function MyComponent() {
  return {
    view: () => m('main', [m('h1', 'Hello world')]),
  };
}

// 可以写成:

function MyComponent() {
  return {
    view: () => (
      <main>
        <h1>Hello world</h1>
      </main>
    ),
  };
}

使用 JSX 时,可以通过花括号在 JSX 标签中插入 JavaScript 表达式:

jsx
var greeting = 'Hello';
var url = 'https://google.com';
var link = <a href={url}>{greeting}!</a>;
// 生成 <a href="https://google.com">Hello!</a>

组件可以通过以下两种方式使用:组件名称首字母大写的约定,或者将其作为属性访问:

jsx
m.render(document.body, <MyComponent />)
// 等价于 m.render(document.body, m(MyComponent))
<m.route.Link href="/home">Go home</m.route.Link>
// 等价于 m(m.route.Link, {href: "/home"}, "Go home")

设置 ​

使用 JSX 最简单的方式是通过 Babel 插件。

Babel 依赖于 npm,它会在您安装 Node.js 后自动安装。安装 npm 后,创建一个项目文件夹并运行以下命令:

bash
npm init -y

如果您想同时使用 Webpack 和 Babel,请参阅下面的部分。

要单独安装 Babel,请使用以下命令:

bash
npm install @babel/core @babel/cli @babel/preset-env @babel/plugin-transform-react-jsx --save-dev

创建一个 .babelrc 文件:

json
{
  "presets": ["@babel/preset-env"],
  "plugins": [
    [
      "@babel/plugin-transform-react-jsx",
      {
        "pragma": "m",
        "pragmaFrag": "'['"
      }
    ]
  ]
}

要运行 Babel,请设置一个 npm 脚本。打开 package.json 并在 "scripts" 下添加此条目:

json
{
  "name": "my-project",
  "scripts": {
    "babel": "babel src --out-dir bin --source-maps"
  }
}

您现在可以使用以下命令运行 Babel:

bash
npm run babel

将 Babel 与 Webpack 结合使用 ​

如果您尚未安装 Webpack 作为模块打包器,请使用以下命令:

bash
npm install webpack webpack-cli --save-dev

您可以按照以下步骤将 Babel 集成到 Webpack 中。

bash
npm install @babel/core babel-loader @babel/preset-env @babel/plugin-transform-react-jsx --save-dev

创建一个 .babelrc 文件:

json
{
  "presets": ["@babel/preset-env"],
  "plugins": [
    [
      "@babel/plugin-transform-react-jsx",
      {
        "pragma": "m",
        "pragmaFrag": "'['"
      }
    ]
  ]
}

接下来,创建一个名为 webpack.config.js 的文件

jsx
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, './bin'),
    filename: 'app.js',
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /\/node_modules\//,
        use: {
          loader: 'babel-loader',
        },
      },
    ],
  },
  resolve: {
    extensions: ['.js', '.jsx'],
  },
};

对于已经熟悉 Webpack 的用户,请注意,将 Babel 选项添加到 webpack.config.js 的 babel-loader 部分会导致错误,因此您需要将它们包含在单独的 .babelrc 文件中。

此配置假定应用程序入口点的源代码文件位于 src/index.js 中,打包后的文件将输出到 bin/app.js。

要运行打包器,请设置一个 npm 脚本。打开 package.json 并在 "scripts" 下添加此条目:

json
{
  "name": "my-project",
  "scripts": {
    "start": "webpack --mode development --watch"
  }
}

现在,您可以通过从命令行运行以下命令来运行打包器:

bash
npm start

生产构建 ​

要生成一个经过最小化的文件,打开 package.json 并添加一个新的名为 build 的 npm 脚本:

json
{
  "name": "my-project",
  "scripts": {
    "start": "webpack -d --watch",
    "build": "webpack -p"
  }
}

您可以使用生产环境中的钩子来自动运行生产构建脚本。这是一个 Heroku 的示例:

json
{
  "name": "my-project",
  "scripts": {
    "start": "webpack -d --watch",
    "build": "webpack -p",
    "heroku-postbuild": "webpack -p"
  }
}

使 m 全局可访问 ​

为了从您的所有项目中全局访问 m,首先需要在您的 webpack.config.js 中导入 webpack,如下所示:

js
const webpack = require('webpack');

然后在 Webpack 配置对象的 plugins 属性中创建一个新插件:

js
{
  plugins: [
    new webpack.ProvidePlugin({
      m: 'mithril',
    }),
  ];
}

有关 ProvidePlugin 的更多信息,请参阅 Webpack 文档。

与 React 的差异 ​

Mithril 中的 JSX 与 React 的 JSX 相比,有一些细微但重要的差异。

属性和样式属性大小写约定 ​

React 要求您对除 data-* 和 aria-* 属性之外的所有属性使用驼峰式 DOM 属性名称,而不是 HTML 属性名称。例如,使用 className 而不是 class,使用 htmlFor 而不是 for。在 Mithril 中,通常使用小写的 HTML 属性名称。如果属性不存在,Mithril 总是会降级到设置属性,这与 HTML 更加直观一致。请注意,在大多数情况下,DOM 属性和 HTML 属性名称要么相同,要么非常相似。例如,输入框的 value/checked 和 tabindex 全局属性与 HTML 元素上的 elem.tabIndex 属性。它们很少在大小写之外有所不同:class 属性的 elem.className 属性或 for 属性的 elem.htmlFor 属性是少数例外。

类似地,React 总是使用通过 elem.style 属性(如 cssHeight 和 backgroundColor)在 DOM 中公开的驼峰式样式属性名称。Mithril 同时支持这种方式和 kebab-cased CSS 属性名称(如 height 和 background-color),并且更推荐使用后者。只有 cssHeight、cssFloat 和带有供应商前缀的属性在大小写之外有所不同。

DOM 事件 ​

React 将所有事件处理程序的第一个字符大写:onClick 监听 click 事件,onSubmit 监听 submit 事件。有些事件在将多个单词连接在一起时会被进一步更改。例如,onMouseMove 监听 mousemove 事件。Mithril 不执行此大小写映射,而是将 on 前置到原生事件,因此您将添加 onclick 和 onmousemove 的监听器来分别监听这两个事件。这与 HTML 的命名方案更加紧密地对应,如果您来自 HTML 或原生 DOM 背景,则更加直观。

React 支持在捕获阶段(在第一阶段,从外向内,而不是默认的冒泡阶段,在第二阶段从内向外)调度事件监听器,方法是将 Capture 附加到该事件。Mithril 目前缺乏这种功能,但将来可能会获得此功能。如果这是必要的,您可以在生命周期钩子中手动添加和删除您自己的监听器。

JSX vs hyperscript ​

JSX 和 hyperscript 是两种不同的语法,您可以用来指定 vnode(虚拟节点),它们各有优劣:

  • 如果您来自 HTML/XML 背景,并且更喜欢使用这种语法来指定 DOM 元素,那么 JSX 更容易上手。在许多情况下,它也稍微简洁一些,因为它使用的标点符号更少,并且属性包含的视觉噪音更少,因此许多人发现它更容易阅读。当然,许多常见的编辑器都像支持 HTML 一样,为 DOM 元素提供自动完成支持。但是,它需要额外的构建步骤才能使用,编辑器支持不如普通 JS 广泛,而且它比较冗长。在处理大量动态内容时,代码也会变得更长,因为您必须对所有内容使用插值。

  • 如果您来自不涉及太多 HTML 或 XML 的后端 JS 背景,那么 Hyperscript 会更容易上手。它更简洁,冗余更少,并且为静态类、ID 和其他属性提供了类似 CSS 的语法糖。它也可以在完全没有构建步骤的情况下使用,尽管您可以根据需要自行添加一个。并且在面对大量动态内容时,它更容易使用,因为您不需要“插值”任何内容。但是,对于某些人来说,这种简洁的写法确实会降低可读性,尤其是那些经验不足且来自前端 HTML/CSS/XML 背景的人,而且我不知道有任何插件可以自动完成 hyperscript 选择器的各个部分,如 ID、类和属性。

您可以在更复杂的树中看到这些优劣如何发挥作用。例如,考虑这个 hyperscript 树,它由 @dead-claudia 从一个真实世界的项目中改编而来,并做了一些修改使其更清晰易读:

javascript
function SummaryView() {
  let tag, posts;

  function init({ attrs }) {
    Model.sendView(attrs.tag != null);
    if (attrs.tag != null) {
      tag = attrs.tag.toLowerCase();
      posts = Model.getTag(tag);
    } else {
      tag = undefined;
      posts = Model.posts;
    }
  }

  function feed(type, href) {
    return m('.feed', [
      type,
      m('a', { href }, m('img.feed-icon[src=./feed-icon-16.gif]')),
    ]);
  }

  return {
    oninit: init,
    // To ensure the tag gets properly diffed on route change.
    onbeforeupdate: init,
    view: () =>
      m('.blog-summary', [
        m('p', 'My ramblings about everything'),

        m('.feeds', [
          feed('Atom', 'blog.atom.xml'),
          feed('RSS', 'blog.rss.xml'),
        ]),

        tag != null
          ? m(TagHeader, { len: posts.length, tag })
          : m('.summary-header', [
              m('.summary-title', 'Posts, sorted by most recent.'),
              m(TagSearch),
            ]),

        m(
          '.blog-list',
          posts.map(post =>
            m(
              m.route.Link,
              {
                class: 'blog-entry',
                href: `/posts/${post.url}`,
              },
              [
                m(
                  '.post-date',
                  post.date.toLocaleDateString('en-US', {
                    year: 'numeric',
                    month: 'long',
                    day: 'numeric',
                  })
                ),

                m('.post-stub', [
                  m('.post-title', post.title),
                  m('.post-preview', post.preview, '...'),
                ]),

                m(TagList, { post, tag }),
              ]
            )
          )
        ),
      ]),
  };
}

这是上面代码的完全等效的代码,使用 JSX 代替。您可以看到这两种语法仅在这一点上有所不同,以及适用的权衡。

jsx
function SummaryView() {
  let tag, posts;

  function init({ attrs }) {
    Model.sendView(attrs.tag != null);
    if (attrs.tag != null) {
      tag = attrs.tag.toLowerCase();
      posts = Model.getTag(tag);
    } else {
      tag = undefined;
      posts = Model.posts;
    }
  }

  function feed(type, href) {
    return (
      <div class="feed">
        {type}
        <a href={href}>
          <img class="feed-icon" src="./feed-icon-16.gif" />
        </a>
      </div>
    );
  }

  return {
    oninit: init,
    // To ensure the tag gets properly diffed on route change.
    onbeforeupdate: init,
    view: () => (
      <div class="blog-summary">
        <p>My ramblings about everything</p>

        <div class="feeds">
          {feed('Atom', 'blog.atom.xml')}
          {feed('RSS', 'blog.rss.xml')}
        </div>

        {tag != null ? (
          <TagHeader len={posts.length} tag={tag} />
        ) : (
          <div class="summary-header">
            <div class="summary-title">Posts, sorted by most recent</div>
            <TagSearch />
          </div>
        )}

        <div class="blog-list">
          {posts.map(post => (
            <m.route.Link class="blog-entry" href={`/posts/${post.url}`}>
              <div class="post-date">
                {post.date.toLocaleDateString('en-US', {
                  year: 'numeric',
                  month: 'long',
                  day: 'numeric',
                })}
              </div>

              <div class="post-stub">
                <div class="post-title">{post.title}</div>
                <div class="post-preview">{post.preview}...</div>
              </div>

              <TagList post={post} tag={tag} />
            </m.route.Link>
          ))}
        </div>
      </div>
    ),
  };
}

技巧和窍门 ​

将 HTML 转换为 JSX ​

在 Mithril.js 中,格式良好的 HTML 通常也是有效的 JSX。只需粘贴原始 HTML 代码即可正常工作。您通常需要做的唯一事情是将未加引号的属性值(如 attr=value)更改为 attr="value",并将空元素(如 <input>)更改为 <input />,这是因为 JSX 基于 XML 而不是 HTML。

使用 hyperscript 时,您通常需要将 HTML 转换为 hyperscript 语法才能使用它。为了帮助加快此过程,您可以使用社区创建的 HTML 转 Mithril 模板转换器来完成大部分工作。

Pager
上一页简单应用
下一页在旧版浏览器上使用 ES6+

基于 MIT 许可证 发布。

版权所有 (c) 2024 Mithril Contributors

https://mithril.js.org/jsx.html

基于 MIT 许可证 发布。

版权所有 (c) 2024 Mithril Contributors