浅谈React与SolidJS对于JSX的应用
React将JSX这一概念深入人心。但,并非只有React利用了JSX,VUE、SolidJS等JS库或者框架都使用了JSX这一概念。网上已经有大量关于JSX的概念与形式的讲述文章,不在本文的讨论范围。
前言
实际上,JSX并不是合法有效的JS代码或HTML代码。目前为止也没有任何一家浏览器的引擎实现了对JSX的读取和解析。此外,JSX本身没有完全统一的规范,除了一些基本的规则以外,各种利用了JSX的JS库可以根据自身需求来设计JSX额外的特性。譬如,React中的元素会有className属性,而SolidJS中的元素会有classList属性。
在FaceBook官方博文中也明确提到了:
JSX是一种类似XML的语法扩展。它不打算由引擎或浏览器实现。它也不会作为某种提案被合并到ECMAScript规范中。它旨在被各种预处理器(转译器)用于将这些标记转换为标准的ECMAScript。
JSX的标签一定只有类似于HTML元素的标签吗?并不是这样的。比如,SolidJS中除了包含形如HTML的全部基础标签以外,还有一些控制标签,例如:<For>
、<Show>
等,它们是完全根据自身库的需要设计处理的标签。
回到更加现实的部分,浏览器总是基于HTML+JavaScript+CSS来完成前端的渲染的。前端领域中日新月异的库、框架绝大部分都逃离不了这三要素,JSX也包括在内。无论我们设计出来的JSX语法糖多么的“甜”,就现状来看,最终都或多或少的成为了HTML、JS或CSS中的某部分。
接下来,我们将进一步讨论各种前端框架是如何使用JSX的。
React中的JSX
工程预编译JSX
React中使用JSX已经老生常谈了。简单来讲,通过编译器(一般都是babel)可以将结构化的JSX组件,转换为同样结构化的JS代码调用形式。在React中,转换JSX为原生JS代码分为两种形式:
- React17以前的
React.createElment
形式; - React17以后的
'react/jsx-runtime'
形式。
先讲第一种:直接转换为React.createElement
。假设源代码如下:
import React from 'react';
function App() {
return <h1>Hello World</h1>;
}
转换过程,会将上述JSX转换为如下的createElement代码:
import React from 'react';
function App() {
return React.createElement('h1', null, 'Hello world');
}
但官方提到了关于这种转换方式的两个问题:
- 如果使用 JSX,则需在
React
的环境下,因为 JSX 将被编译成React.createElement
。 - 有一些
React.createElement
无法做到的性能优化和简化。
基于上述的问题,在React17以后,提供了另一种转换方式:引入jsx-runtime层。假设源码如下:
function App() {
return <h1>Hello World</h1>;
}
下方是新 JSX 被转换编译后的结果:
// 由编译器引入(禁止自己引入!)
import {jsx as _jsx} from 'react/jsx-runtime';
function App() {
return _jsx('h1', { children: 'Hello world' });
}
第二种模式的核心在于,编译出来的代码与React库本身进行了解耦,只将JSX转换为了与React无关的JS形式的调用描述,没有直接使用React.createElement
。
上图描述了一个前端React工程里JSX代码转换为浏览器能够运行的JS代码的基本过程。当然,Babel在这个转换过程中承担了重要角色。
在Babel中,与上述两种转换相关的是部分是:@babel/preset-react
(核心其实是该preset预置集内部的插件@babel/plugin-transform-react-jsx
)。无论是@babel/preset-react
还是@babel/plugin-transform-react-jsx
,都允许我们配置上述的转换行为。
Babel的v7.9.0版本之前,只能转换为React.createElement
形式。在v7.9.0版本以后,支持我们配置转换行为。默认选项为 {"runtime": "classic"}
,也就是说默认还是React.createElement
。
如需启用新的转换,你可以使用 {"runtime": "automatic"}
作为 @babel/plugin-transform-react-jsx
或 @babel/preset-react
的选项:
// 如果你使用的是 @babel/preset-react
{
"presets": [
["@babel/preset-react", {
"runtime": "automatic"
}]
]
}
// 如果你使用的是 @babel/plugin-transform-react-jsx
{
"plugins": [
["@babel/plugin-transform-react-jsx", {
"runtime": "automatic"
}]
]
}
浏览器使用JSX
首先,我们可以按照如下的方式,直接基于CDN模式的React进行开发:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<script src="https://unpkg.com/react@18.2.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js"></script>
<script>
const appComp = React.createElement('div', {style: {color: 'blue'}}, 'hello, world');
ReactDOM.createRoot(document.querySelector('#app')).render(appComp)
</script>
</body>
</html>
- 调用
React.createElement
创建React节点实例; - 调用ReactDOM的API完成某个节点的渲染。
可以直接从页面上看到渲染效果:
这种方式则是最直接的,使用了最原生的写法。具备JS基础的同学应该都能理解。如果我们在script中编写了jsx代码:
- const appComp = React.createElement('div', {style: {color: 'blue'}}, 'hello, world');
+ const appComp = (
+ <div style={{color: 'blue'}}>
+ hello, world
+ </div>
+ );
ReactDOM.createRoot(document.querySelector('#app')).render(appComp)
毫无疑问会报错:
严格意义上讲,浏览器没法解析JSX代码(前面已经提到了),但是我们可以通过Babel提供的standalone
模块库(@babel/standalone · Babel (babeljs.io))来完成这一任务。该库不仅仅支持JSX,同时还支持ES6语法直接在浏览器上运行,而无需对代码进行预编译,其初衷是支持一些浏览器(说的就是你IE)能够编写ES6的代码。
关于@babel/standalone的具体使用方式为:
- 引入
@babel/standalone
的CDN; - 将原有的含有JSX脚本的
<script>
标签添加属性type="text/babel"
:<script type="text/babel">
。
<script src="https://unpkg.com/react@18.2.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js"></script>
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
- <script>
+ <script type="text/babel">
// const appComp = React.createElement('div', {style: {color: 'blue'}}, 'hello, world');
const appComp = (
<div style={{color: 'blue'}}>
hello, world
</div>
);
ReactDOM.createRoot(document.querySelector('#app')).render(appComp)
</script>
完成上述的配置以后,我们就能在浏览器中看到源自JSX渲染而来的React组件了:
这个过程主要为@babel/standalone
的js在加载的过程中,会读取HTML上的type="text/babel"
的节点,然后对其内容进行编译转换。不难想到, 这个过程会十分消耗性能。所以Babel官方也强调了,只能在某些场景下使用。
SolidJS中的JSX
SolidJS是新发展起来的又一响应式框架,同样的,SolidJS也使用JSX来完成视图层的编写。
不同于React的是,Solid 模型更简单,没有 Hook 规则。每个组件执行一次,随着依赖项的更新,钩子和绑定会多次执行。Solid 遵循与 React 相同的理念,具有单向数据流、读/写隔离和不可变接口。但是放弃了使用虚拟 DOM,使用了完全不同的实现。
工程于编译JSX
同样的,基于浏览器无法直接解析JSX事实,所以我们会比较好奇SolidJS编译出的内容,是什么样的。在SolidJS提供的Playground中(Solid Playground (solidjs.com)),我们可以更加直观的看到SolidJS将JSX编译为了什么结果:
import { createSignal } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(1);
return (
<button
type="button"
onClick={() => setCount(count() + 1)}
>
{count()}
</button>
);
}
在本文中,我们主要分析JSX的处理过程,暂不涉及响应式的实现方式。
首先可以看到我们编写的JSX,被解析为了一段非常纯粹的HTML代码字符串片段:
`<button type="button"></button>`
然后,该字符串交给了来自"solid-js/web"
中的template
这个方法进行解析处理。
那么这个template
方法是什么呢?通过查找类型定义,可以找到其来源于solid-js/web
包中,client.ts
导出的template
的定义:
通过查看client.ts
的源码,会发现solid-js/web
关于client.ts
的整个部分都来自dom-expression/src/client
导出的内容:
solid/client.ts at main · solidjs/solid (github.com)
// "solid-js/web"的client部分
export * from "dom-expressions/src/client";
所以,进一步翻阅dom-expression这个库,会找到client.js代码的实现,并且能够定位到template这个方法的实现(dom-expressions/client.js at main · ryansolid/dom-expressions (github.com)):
export function template(html, check, isSVG) {
const t = document.createElement("template");
t.innerHTML = html;
if ("_DX_DEV_" && check && t.innerHTML.split("<").length - 1 !== check)
throw `The browser resolved template HTML does not match JSX input:\n${t.innerHTML}\n\n${html}. Is your HTML properly formed?`;
let node = t.content.firstChild;
if (isSVG) node = node.firstChild;
return node;
}
实际上,在不同的版本下,这些工具方法的实现有所不同,但是核心不变:
- 创建template元素
- 将html字符串插入到该元素
- 进行一定的处理
- 返回html对应的元素
比如我们编写一个demo:
经过编译后,查看编译代码,能够看到相关的实现:
与React一样,SolidJS同样用到了Babel对SolidJS的代码进行编译。核心的则是babel-preset-solid
,与之前一些标准的preset(比如@babel/preset-typescript
或是@babel/preset-react
)命名不同,因为SolidJS还没有成为Babel官方预置集(还是比较小众的)。
关于SolidJS的代码处理过程,在Babel中,先经过babel-preset-solid
进行编译,将JSX编译为模板字符串以及处理各种调用;然后,如果是TypeScript代码,则需要@babel/preset-typescript
来进行TS代码处理。
浏览器使用JSX
遗憾的是,目前SolidJS还没有提供关于如何以UMD CDN方式直接在HTML中使用,就更不用说在浏览器中使用JSX进行代码编写了。不过,SolidJS还有一个名为solid-element
的库,该库底层基于WebComponents,可以让我们预定义HTML元素,然后直接在HTML中使用这些元素。
浅谈React与SolidJS对于JSX的应用的更多相关文章
- 浅谈React
浅谈react react是什么?其官网给出了明确定义:A JavaScript library for building user interfaces,一个用于构建用户界面的JavaScript库 ...
- 浅谈React工作原理
浅谈React工作原理:https://www.cnblogs.com/yikuu/p/9660932.html 转自:https://cloud.tencent.com/info/63f656e0b ...
- 【转】浅谈React、Flux 与 Redux
本文转自<浅谈React.Flux 与 Redux>,转载请注明出处. React React 是一个 View 层的框架,用来渲染视图,它主要做几件事情: 组件化 利用 props 形成 ...
- 浅谈React数据流管理
引言:为什么数据流管理如此重要?react的核心思想就是:UI=render(data),data就是我们说的数据流,render是react提供的纯函数,所以用户界面的展示完全取决于数据层.这篇文章 ...
- 浅谈 React
机缘巧合认识React,翻了2天的资料,又整理了1天,也算是简单入门了;之前也学过angular,相比来说,的确React代码逻辑更加简单明了,理解起来也相对容易. React 具备以下特性:1.声明 ...
- 浅谈React和VDom关系
组件化 组件的封装 组件的复用 组件的封装 视图 数据 视图和数据之间的变化逻辑 import React, {Component} from 'react'; export default clas ...
- 浅谈React编程思想
React是Facebook推出的面向视图层开发的一个框架,用于解决大型应用,包括如何很好地管理DOM结构,是构建大型,快速Web app的首选方式. React使用JavaScript来构建用户界面 ...
- 浅谈React受控与非受控组件
背景 React内部分别使用了props, state来区分组件的属性和状态.props用来定义组件外部传进来的属性, 属于那种经过外部定义之后, 组件内部就无法改变.而state维持组件内部的状态更 ...
- 浅谈react的初步试用
现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Native 发布,结果一天之内,就获得了 5000 颗星,受瞩目程度可见一斑. React 起源于 Face ...
- 浅谈react受控组件与非受控组件
引言 最近在使用蚂蚁金服出品的一条基于react的ant-design UI组件时遇到一个问题,编辑页面时input输入框会展示保存前的数据,但是是用defaultValue就是不起作用,输入框始终为 ...
随机推荐
- Markdown:简洁高效的文本标记语言
引言 在当今信息爆炸的时代,我们需要一种简洁.高效的文本标记语言来排版和发布内容.Markdown应运而生,它是一种轻量级的文本标记语言,以其简单易学.易读易写的特点,成为了广大写作者的首选工具.本文 ...
- Pandas数据合并
目录 1) 在单个键上进行合并操作 2) 在多个键上进行合并操作 使用how参数合并 1) left join 2) right join 3) outer join(并集) 4) inner joi ...
- 将docker镜像推送到阿里云镜像仓库
1.注册阿里云账号(支付宝扫码登录也可以) 进入控制台,找到[容器镜像服务] 2.创建命名空间 3.创建镜像仓库 4.设置授权凭证 5.登录 docker login --username=index ...
- Java集合框架学习(十四) Iterator接口详解
Iterator接口介绍 public interface Iterator<E> iterator 用于迭代集合类型对象,例如: HashMap, ArrayList, LinkedLi ...
- 我在winform项目里使用“Windows I/O完成端口”的经验分享
少年!看你骨骼惊奇,是万中无一的练武奇才,我这儿有本武林秘籍,见与你有缘就送你了! 如来神掌 Windows I/O完成端口是一个我至今都说不好的话题,请宽容的接受我这不是科班出身的自学成才的野生程序 ...
- 手写web框架
重新认识HTTP http请求报文包含三个部分(请求行 + 请求头 + 请求体) 请求行 请求行包含三个内容: method + request-URI + http-version -- 例如 GE ...
- 【Azure 事件中心】使用Apache Flink 连接 Event Hubs 出错 Kafka error: No resolvable bootstrap urls
问题描述 参考Github上 Event Hub的示例代码(Using Apache Flink with Event Hubs for Apache Kafka Ecosystems : https ...
- 二: sql模式(sql_mode)
# sql_mode 1 介绍 sql_mode 会影响 MySQL支持的SQL语法以及它执行的数据验证检查.通过设置sql_mode,可以完成不同严格程度 的数据校验,有效地保障数据准确性. MyS ...
- Java 设计模式----单例模式--懒汉式
1 package com.bytezreo.singleton; 2 3 /** 4 * 5 * @Description 单例模式 ---懒汉式 6 * @author Bytezero·zhen ...
- Python(上机题) 通俗易懂的基础题目解析
python 题目 文章目录 python 题目 题目一:幸运数对 题目二:lambda 函数找最大值 题目三:n个数前后互换 (切片) 题目四:字符串相减(删除指定字符) 方法一:可以用空字符来替换 ...