wtf-is-jsx: 什么是 jsx/vdom/h/createElement/render

一些基础知识的学习
更新于: 2023-07-05 10:14:02

名词

先解释一下,文章中后续会出现的一些名词。

名称解释
WTF“WTF,网络流行词,What The Fuck的缩写,该词类似于什么鬼
Pragma

Pragma指令可以在代码中使用,以告诉转译器特定的信息或指示。

在这个上下文中,Pragma被用来声明一个函数的名称,该函数会在运行时被调用,用于处理每个节点。

节点通常是指抽象语法树(AST)中的一个元素,AST是在代码转译过程中用于表示源代码结构的一种数据结构。

通过声明Pragma并指定相应的函数名称,转译器可以在转译代码时将该函数的调用插入到适当的位置,以处理每个节点。

这样,你可以自定义转译过程中节点的处理逻辑,以适应特定的需求或实现特定的功能。

总而言之,Pragma是一个用于在转译过程中指定运行时函数调用的指令,使得转译器能够在适当的时候调用指定的函数来处理每个节点。这为开发者提供了更大的灵活性和控制权,以定制转译过程并实现特定的行为。

Transpiler(转译器)

此文中的transpiler主要指 babel

在软件开发中,转译器(transpiler)用于将一种编程语言的代码转换为另一种编程语言的代码。

其中一个常见的用例是将新版本的JavaScript代码转译为旧版本的JavaScript代码,以便在不支持新语法或特性的旧浏览器中运行。

Partials

"Partials"(局部模板)是由无逻辑/有限逻辑的模板引擎引入的概念,用于在不同的上下文中重用视图的片段。

模板引擎是一种用于生成动态内容的工具,它将模板与数据结合,生成最终的输出。在某些情况下,视图可能需要在不同的上下文中重复使用,但是模板引擎本身可能没有提供直接的机制来实现这种重用。为了解决这个问题,模板引擎引入了"Partials"的概念。

"Partials"是指将视图分割成可重用的片段或组件,可以在多个不同的上下文中使用。这些片段可以包含HTML、CSS、JavaScript和其他模板语法。通过将视图拆分为较小的部分,我们可以更好地组织和管理代码,并且可以在需要的地方进行重用。

使用"Partials",我们可以在不同的模板或视图中引用相同的片段,而不必在每个上下文中重复编写相同的代码。这提高了代码的可维护性和重用性,同时也减少了重复劳动。通过传递不同的数据给这些"Partials",可以根据不同的上下文生成不同的输出。

总而言之,"Partials"是一种模板引擎中的概念,用于在不同的上下文中重用视图的片段。通过将视图拆分为可重用的部分,我们可以更好地组织代码,并实现在不同上下文中的视图重用,提高代码的可维护性和重用性。


The Pragma

You declare this per-file or per-function to tell your transpiler (eg: Babel) the name of a function that should be called at runtime for each node (see Transpilation).

您可以按照每个文件或每个函数声明这一点,以告诉您的transpiler(例如:Babel)应该在运行时为每个节点调用的函数的名称(请参阅Transpilation)。

inject calls to an h() function for each node
为每个节点注入对h()函数的调用

/** @jsx h */

除了使用 /** @jsx h */ 注释,还有其他表示形式可以告诉Babel使用特定的函数来处理JSX语法。以下是一些常见的表示形式:

  1. /** @jsx functionName */: 这种形式中,functionName 是用于处理JSX语法的函数的名称。可以根据需要将其替换为实际的函数名称。
  2. // @jsx functionName: 这种形式是使用双斜杠 (//) 注释的方式,而不是使用多行注释 (/** ... */)。类似地,functionName 是用于处理JSX语法的函数的名称。
  3. /* @jsx functionName */: 这种形式是使用单行注释 (/* ... */) 的方式,而不是使用多行注释。同样,functionName 是用于处理JSX语法的函数的名称。

这些表示形式都是为了告诉Babel在转译过程中使用指定的函数来处理JSX语法。选择哪种形式取决于个人或团队的偏好和项目规范。

在实际使用时,请根据Babel的文档或项目的要求选择适合的表示形式,并确保使用的函数存在且能够正确处理JSX语法 


Transpilation

转译

If you're not using a transpiler yet, you should be. Writing, debugging, testing and running JavaScript is all more effective when using ES6/ES2015. Babel is the most popular and highly recommended transpiler out there, so I'll assume that's what you are using.

Along with converting your ES6/ES7+ syntax to JavaScript-of-today, Babel includes support for transpiling JSX ==right out of the box==. You don't need to add or change anything to use this feature.

It's easiest to see how this works by looking at a very simple example:

如果你还没有使用transiler,你应该使用。使用ES6/ES2015时,编写、调试、测试和运行JavaScript会更加有效。巴别塔是最受欢迎和强烈推荐的转发器,所以我认为这就是你正在使用的。
除了将您的ES6/ES7+语法转换为当前的JavaScript之外,Babel还支持直接转换JSX==。您不需要添加或更改任何内容即可使用此功能。

通过看一个非常简单的例子,最容易看出这是如何工作的:

Before

The code  you write.

/** @jsx h */
let foo = <div id="foo">Hello!</div>;

After

The code you run,这个 foo 最终会是一个 js object,类似于 json 的格式,表示 HTML 结构。

var foo = h('div', {id:"foo"}, 'Hello!');

Let's Build a JSX Renderer

让我们构建一个JSX渲染器

步骤

  1. 首先,我们需要定义h()我们的转译代码正在调用的函数。(你可以随意称呼它,我使用它h()是因为这种类型的“构建器”函数的最初想法被称为hyperscript(“hyper文本“+”爪哇脚本”))
  2. 好吧,这很容易。
function h(nodeName, attributes, ...args) {
      let children = args.length ? [].concat(...args) : null;
      return { nodeName, attributes, children };
}

现在我们的h()函数输出了这些嵌套的 JSON 对象,所以我们最终得到一个像这样的“树”:

{
  nodeName: "div",
  attributes: {
    "id": "foo"
  },
  children: ["Hello!"]
}

所以我们只需要一个接受该格式并输出实际 DOM 节点的函数:

function render(vnode) {
    // Strings just convert to #text Nodes:
    if (vnode.split) return document.createTextNode(vnode);

    // create a DOM element with the nodeName of our VDOM element:
    let n = document.createElement(vnode.nodeName);

    // copy attributes onto the new node:
    let a = vnode.attributes || {};
    Object.keys(a).forEach( k => n.setAttribute(k, a[k]) );

    // render (build) and then append child nodes:
    (vnode.children || []).forEach( c => n.appendChild(render(c)) );

    return n;
}

vdom 优点

  • 虚拟 DOM 的好处是它非常轻量级。小对象引用其他小对象,由易于优化的应用逻辑组成的结构。
  • 这也意味着它不依赖于任何渲染逻辑或缓慢的 DOM 方法。

使用 JSX

  • 我们知道JSX转化为h()函数调用。
  • 这些函数调用创建一个简单的“虚拟”DOM 树。
  • 我们可以使用该render()函数来制作匹配的“真实”DOM 树。
  • 看起来是这样的:
// JSX -> VDOM:
let vdom = <div id="foo">Hello!</div>;

// VDOM -> DOM:
let dom = render(vdom);

// add the tree to <body>:
document.body.appendChild(dom);

Partials, Iteration & Logic: No new Syntax

Partial,迭代,逻辑,都没有新的语法出现。

  • 我们拥有所有 JavaScript,而不是模板语言引入的有限概念。
  • Partials: 是无逻辑/有限逻辑模板引擎引入的概念,用于跨不同上下文重用视图块。
  • 迭代:迭代似乎是每种新模板语言重新发明的东西(我和任何人一样有罪)。使用 JSX,无需学习一次性语法:迭代 JavaScript 程序中其他任何地方的语法。您可以选择最适合给定任务的迭代样式:[].forEach()[].map()和循环等。forwhile
  • 逻辑,就像迭代一样,是模板语言喜欢重新发明的东西。一方面,无逻辑模板提供了一种非常差的将逻辑嵌入到视图中的方法:有限的构造,例如{{#if value}}将逻辑推入控制器层,从而导致膨胀。这避免了构建一种用于描述更复杂逻辑的语言,避免了可预测性和安全陷阱。

代码生成的引擎

另一方面,使用代码生成的引擎(一种从粗鄙到不可原谅的技术)通常拥有执行任意 JavaScript 表达式以进行逻辑甚至迭代的能力。这是不惜一切代价避免这种情况的充分理由:您的代码正在从其原始位置(可能是模块、闭包或标记内)中剥离出来并在“其他地方”进行评估。这对我来说不够可预测或不够安全。

JSX allows all of of JavaScript's language features, without relying on generating grotesque code in a build step and without eval() & friends.
// Array of strings we want to show in a list:
let items = ['foo', 'bar', 'baz'];

// creates one list item given some text:
function item(text) {
    return <li>{text}</li>;
}

// a "view" with "iteration" and "a partial":
let list = render(
  <ul>
    { items.map(item) }
  </ul>
);

Putting it Together

大全。

const ITEMS = 'hello there people'.split(' ');

// turn an Array into list items: 
let list = items => items.map( p => <li> {p} </li> );
 
// view with a call out ("partial") to generate a list from an Array:
let vdom = (
    <div id="foo">
        <p>Look, a simple JSX DOM renderer!</p>
        <ul>{ list(ITEMS) }</ul>
    </div>
);
 
// render() converts our "virtual DOM" (see below) to a real DOM tree:
let dom = render(vdom);
 
// append the new nodes somewhere:
document.body.appendChild(dom);
 
// Remember that "virtual DOM"? It's just JSON - each "VNode" is an object with 3 properties.
let json = JSON.stringify(vdom, null, '  ');

// The whole process (JSX -> VDOM -> DOM) in one step:
document.body.appendChild(
    render( <pre id="vdom">{ json }</pre> )
);

VDOM

Virtual dom,可以被看作是一个 JavaScript 对象结构,它用来表示真实 DOM 树的抽象。

虚拟 DOM(Virtual DOM)可以被看作是一个 JavaScript 对象结构,它用来表示真实 DOM 树的抽象。这个抽象的 JavaScript 对象结构通常可以用 JSON 来表示,但并不限定只能用 JSON 格式。
虚拟 DOM 通常由一系列嵌套的 JavaScript 对象组成,每个对象表示一个 DOM 元素或组件。这些对象包含了元素的类型(如标签名或组件名)、属性、子元素等信息。
你可以将虚拟 DOM 看作是对真实 DOM 树的一种轻量级的描述,它在内存中以对象的形式存在。通过比较虚拟 DOM 树的变化,我们可以高效地计算出需要对真实 DOM 进行的最小操作,从而实现高效的 DOM 更新。
尽管虚拟 DOM 的结构可以用 JSON 表示,但虚拟 DOM 的概念更多地关注于抽象的描述层面,而不仅仅是数据的序列化形式。在实际使用中,虚拟 DOM 通常以 JavaScript 对象的形式存在,并通过一系列操作来更新和操作这些对象,而不是直接通过 JSON 进行处理。
因此,虚拟 DOM 可以看作是一种抽象的、用于描述 DOM 结构的数据结构,而不仅仅是简单的 JSON 数据。
vdom 本质是 json

JSX 与 babel

  • Vdom: 我们写的代码
  • h: 在 React 生态系统中,使用 h 函数的习惯是受到了另一个 JavaScript 库叫做 "HyperScript" 的影响。HyperScript 是一个用于创建虚拟 DOM 的库,它的创建函数就是 h。
  • render:将 vdom 渲染成为真实 dom 的情况

参考