JSX
Descripción
JSX es una extensión de sintaxis que permite escribir etiquetas HTML intercaladas con JavaScript. No forma parte de ningún estándar de JavaScript y no es imprescindible para construir aplicaciones, pero su uso puede resultar más agradable según tus preferencias o las de tu equipo.
function MyComponent() {
return {
view: () => m('main', [m('h1', 'Hello world')]),
};
}
// puede escribirse como:
function MyComponent() {
return {
view: () => (
<main>
<h1>Hello world</h1>
</main>
),
};
}
Cuando se usa JSX, es posible interpolar expresiones de JavaScript dentro de las etiquetas JSX usando llaves:
var greeting = 'Hello';
var url = 'https://google.com';
var link = <a href={url}>{greeting}!</a>;
// genera <a href="https://google.com">Hello!</a>
Los componentes se pueden utilizar siguiendo una convención que consiste en poner en mayúscula la primera letra del nombre del componente o accediendo a él como una propiedad:
m.render(document.body, <MyComponent />)
// equivalente a m.render(document.body, m(MyComponent))
<m.route.Link href="/home">Go home</m.route.Link>
// equivalente a m(m.route.Link, {href: "/home"}, "Go home")
Configuración
La manera más sencilla de usar JSX es mediante un plugin de Babel.
Babel requiere npm, que se instala automáticamente cuando instalas Node.js. Una vez que npm esté instalado, crea una carpeta de proyecto y ejecuta este comando:
npm init -y
Si deseas usar Webpack y Babel juntos, salta a la sección siguiente.
Para instalar Babel como una herramienta independiente, usa este comando:
npm install @babel/core @babel/cli @babel/preset-env @babel/plugin-transform-react-jsx --save-dev
Crea un archivo llamado .babelrc
:
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "m",
"pragmaFrag": "'['"
}
]
]
}
Para ejecutar Babel, configura un script de npm. Abre package.json
y añade esta entrada bajo "scripts"
:
{
"name": "my-project",
"scripts": {
"babel": "babel src --out-dir bin --source-maps"
}
}
Ahora puedes ejecutar Babel usando este comando:
npm run babel
Usando Babel con Webpack
Si aún no has instalado Webpack como empaquetador (bundler), usa este comando:
npm install webpack webpack-cli --save-dev
Puedes integrar Babel con Webpack siguiendo estos pasos:
npm install @babel/core babel-loader @babel/preset-env @babel/plugin-transform-react-jsx --save-dev
Crea un archivo llamado .babelrc
:
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "m",
"pragmaFrag": "'['"
}
]
]
}
Luego, crea un archivo llamado webpack.config.js
:
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'],
},
};
Para aquellos que ya estén familiarizados con Webpack, tengan en cuenta que agregar las opciones de Babel a la sección babel-loader
de su webpack.config.js
generará un error, por lo que es necesario incluirlas en el archivo .babelrc
por separado.
Esta configuración asume que el archivo de código fuente para el punto de entrada de la aplicación está en src/index.js
, y esto generará el bundle en bin/app.js
.
Para ejecutar el empaquetador, configura un script de npm. Abre package.json
y añade esta entrada bajo "scripts"
:
{
"name": "my-project",
"scripts": {
"start": "webpack --mode development --watch"
}
}
Ahora puedes ejecutar el empaquetador ejecutando esto desde la línea de comandos:
npm start
Compilación para producción
Para generar un archivo minificado, abre package.json
y añade un nuevo script de npm llamado build
:
{
"name": "my-project",
"scripts": {
"start": "webpack -d --watch",
"build": "webpack -p"
}
}
Puedes usar hooks en tu entorno de producción para ejecutar automáticamente el script de build de producción. Aquí hay un ejemplo para Heroku:
{
"name": "my-project",
"scripts": {
"start": "webpack -d --watch",
"build": "webpack -p",
"heroku-postbuild": "webpack -p"
}
}
Haciendo que m
sea accesible globalmente
Para poder acceder a m
globalmente desde todo tu proyecto, primero importa webpack
en tu archivo webpack.config.js
de esta manera:
const webpack = require('webpack');
Luego crea un nuevo plugin en la propiedad plugins
del objeto de configuración de Webpack:
{
plugins: [
new webpack.ProvidePlugin({
m: 'mithril',
}),
];
}
Consulta la documentación de Webpack para más información sobre ProvidePlugin
.
Diferencias con React
JSX en Mithril presenta algunas diferencias sutiles pero importantes en comparación con el JSX de React.
Convenciones de mayúsculas y minúsculas para propiedades de atributos y estilos
React requiere que utilices los nombres de propiedad DOM en camelCase en lugar de los nombres de atributos HTML para todos los atributos que no sean data-*
y aria-*
. Por ejemplo, usar className
en lugar de class
y htmlFor
en lugar de for
. En Mithril, es más común usar los nombres de atributos HTML en minúsculas. Mithril siempre recurre a establecer atributos si una propiedad no existe, lo que se alinea más intuitivamente con HTML. Ten en cuenta que, en la mayoría de los casos, los nombres de propiedad DOM y los nombres de atributos HTML son iguales o muy similares. Por ejemplo, value
/checked
para las entradas y el atributo global tabindex
frente a la propiedad elem.tabIndex
en los elementos HTML. Muy raramente difieren más allá de las mayúsculas y minúsculas: la propiedad elem.className
para el atributo class
o la propiedad elem.htmlFor
para el atributo for
se encuentran entre las pocas excepciones.
De manera similar, React siempre utiliza los nombres de propiedad de estilo en camelCase expuestos en el DOM a través de las propiedades de elem.style
(como cssHeight
y backgroundColor
). Mithril admite tanto eso como los nombres de propiedad CSS en kebab-case (como height
y background-color
), y prefiere idiomáticamente este último. Solo cssHeight
, cssFloat
y las propiedades con prefijo de proveedor difieren en algo más que en mayúsculas y minúsculas.
Eventos DOM
React convierte a mayúscula el primer carácter de todos los controladores de eventos: onClick
escucha los eventos click
y onSubmit
escucha los eventos submit
. Algunos se alteran aún más a medida que son varias palabras concatenadas. Por ejemplo, onMouseMove
escucha los eventos mousemove
. Mithril no realiza esta conversión de mayúsculas y minúsculas, sino que simplemente añade el prefijo on
al evento nativo, por lo que añadirías listeners para onclick
y onmousemove
para escuchar esos dos eventos respectivamente. Esto corresponde mucho más estrechamente al esquema de nombres de HTML y es mucho más intuitivo si provienes de un entorno de HTML o DOM vainilla.
React admite la programación de listeners de eventos durante la fase de captura (en la primera pasada, de afuera hacia adentro, en contraposición a la fase de burbuja predeterminada que va de adentro hacia afuera en la segunda pasada) añadiendo Capture
a ese evento. Mithril actualmente carece de tal funcionalidad, pero podría ganarla en el futuro. Si esto es necesario, puedes agregar y eliminar manualmente tus propios listeners en lifecycle hooks.
JSX vs hyperscript
JSX e hyperscript son dos sintaxis diferentes que puedes usar para especificar vnodes, y tienen diferentes ventajas y desventajas:
JSX es mucho más accesible si provienes de un entorno de HTML/XML y te sientes más cómodo especificando elementos DOM con ese tipo de sintaxis. También suele ser más limpio, ya que utiliza menos signos de puntuación y los atributos contienen menos ruido visual, por lo que muchas personas lo encuentran más fácil de leer. Y, por supuesto, muchos editores comunes brindan soporte de autocompletado para elementos DOM de la misma manera que lo hacen para HTML. Sin embargo, requiere un paso de build adicional para usarlo, el soporte del editor no es tan amplio como con JS normal, y es considerablemente más detallado. También es un poco más detallado cuando se trata de mucho contenido dinámico porque tienes que usar interpolaciones para todo.
Hyperscript es más accesible si provienes de un entorno de JS backend que no involucra mucho HTML o XML. Es más conciso con menos redundancia, y proporciona un azúcar sintáctico similar a CSS para clases estáticas, ID y otros atributos. También se puede usar sin ningún paso de compilación, aunque puedes agregar uno si lo deseas. Y es un poco más fácil de trabajar al manejar una gran cantidad de contenido dinámico, porque no necesitas "interpolar" nada. Sin embargo, la brevedad puede dificultar la lectura para algunas personas, especialmente para aquellas con menos experiencia que provienen de un entorno de HTML/CSS/XML frontend, y no conozco ningún plugin que complete automáticamente partes de selectores de hyperscript como ID, clases y atributos.
Puedes observar las ventajas y desventajas en árboles más complejos. Por ejemplo, considera este árbol de hyperscript, adaptado de un proyecto del mundo real por @dead-claudia con algunas alteraciones para mayor claridad y legibilidad:
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 }),
]
)
)
),
]),
};
}
Aquí tienes el equivalente exacto del código anterior, utilizando JSX en su lugar. Puedes ver cómo las dos sintaxis difieren solo en este fragmento, y qué ventajas y desventajas se aplican.
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>
),
};
}
Consejos y trucos
Convirtiendo HTML a JSX
En Mithril.js, el HTML bien formado generalmente es JSX válido. Se requiere poco más que simplemente pegar HTML sin formato para que las cosas funcionen. Casi lo único que normalmente tendrías que hacer es cambiar los valores de propiedad sin comillas, como attr=value
, a attr="value"
, y cambiar los elementos vacíos, como <input>
, a <input />
, ya que JSX se basa en XML y no en HTML.
Cuando se usa hyperscript, a menudo necesitas traducir HTML a la sintaxis de hyperscript para usarlo. Para ayudar a acelerar este proceso, puedes usar un convertidor de HTML a plantilla de Mithril creado por la comunidad que hará gran parte del trabajo por ti.