Rework jsx docs
This commit is contained in:
parent
c8df665bcb
commit
1c07143be4
1 changed files with 231 additions and 179 deletions
216
docs/jsx.md
216
docs/jsx.md
|
|
@ -8,9 +8,9 @@ Explanation, examples, and build notes on how to use JSX in your Mithril.js-base
|
||||||
- [Setup](#setup)
|
- [Setup](#setup)
|
||||||
- [Production build](#production-build)
|
- [Production build](#production-build)
|
||||||
- [Using Babel with Webpack](#using-babel-with-webpack)
|
- [Using Babel with Webpack](#using-babel-with-webpack)
|
||||||
|
- [Differences with React](#differences-with-react)
|
||||||
- [JSX vs hyperscript](#jsx-vs-hyperscript)
|
- [JSX vs hyperscript](#jsx-vs-hyperscript)
|
||||||
- [A note on event listeners](#a-note-on-event-listeners)
|
- [Tips and Tricks](#tips-and-tricks)
|
||||||
- [Converting HTML](#converting-html)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -21,11 +21,8 @@ JSX is a syntax extension that enables you to write HTML tags interspersed with
|
||||||
```jsx
|
```jsx
|
||||||
function MyComponent() {
|
function MyComponent() {
|
||||||
return {
|
return {
|
||||||
view: () =>
|
view: () => m("main", [m("h1", "Hello world")]),
|
||||||
m("main", [
|
};
|
||||||
m("h1", "Hello world"),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// can be written as:
|
// can be written as:
|
||||||
|
|
@ -36,25 +33,27 @@ function MyComponent() {
|
||||||
<main>
|
<main>
|
||||||
<h1>Hello world</h1>
|
<h1>Hello world</h1>
|
||||||
</main>
|
</main>
|
||||||
)
|
),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
When using JSX, it's possible to interpolate JavaScript expressions within JSX tags by using curly braces:
|
When using JSX, it's possible to interpolate JavaScript expressions within JSX tags by using curly braces:
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
var greeting = "Hello"
|
var greeting = "Hello";
|
||||||
var url = "https://google.com"
|
var url = "https://google.com";
|
||||||
var link = <a href={url}>{greeting}!</a>
|
var link = <a href={url}>{greeting}!</a>;
|
||||||
// yields <a href="https://google.com">Hello!</a>
|
// yields <a href="https://google.com">Hello!</a>
|
||||||
```
|
```
|
||||||
|
|
||||||
Components can be used by using a convention of uppercasing the first letter of the component name:
|
Components can be used by using a convention of uppercasing the first letter of the component name or by accessing it as a property:
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
m.render(document.body, <MyComponent />)
|
m.render(document.body, <MyComponent />)
|
||||||
// equivalent to m.render(document.body, m(MyComponent))
|
// equivalent to m.render(document.body, m(MyComponent))
|
||||||
|
<m.route.Link href="/home">Go home</m.route.Link>
|
||||||
|
// equivalent to m(m.route.Link, {href: "/home"}, "Go home")
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -83,10 +82,13 @@ Create a `.babelrc` file:
|
||||||
{
|
{
|
||||||
"presets": ["@babel/preset-env"],
|
"presets": ["@babel/preset-env"],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
["@babel/plugin-transform-react-jsx", {
|
[
|
||||||
|
"@babel/plugin-transform-react-jsx",
|
||||||
|
{
|
||||||
"pragma": "m",
|
"pragma": "m",
|
||||||
"pragmaFrag": "'['"
|
"pragmaFrag": "'['"
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -128,10 +130,13 @@ Create a `.babelrc` file:
|
||||||
{
|
{
|
||||||
"presets": ["@babel/preset-env"],
|
"presets": ["@babel/preset-env"],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
["@babel/plugin-transform-react-jsx", {
|
[
|
||||||
|
"@babel/plugin-transform-react-jsx",
|
||||||
|
{
|
||||||
"pragma": "m",
|
"pragma": "m",
|
||||||
"pragmaFrag": "'['"
|
"pragmaFrag": "'['"
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -139,27 +144,29 @@ Create a `.babelrc` file:
|
||||||
Next, create a file called `webpack.config.js`
|
Next, create a file called `webpack.config.js`
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
const path = require('path')
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: './src/index.js',
|
entry: "./src/index.js",
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, './bin'),
|
path: path.resolve(__dirname, "./bin"),
|
||||||
filename: 'app.js',
|
filename: "app.js",
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [{
|
rules: [
|
||||||
|
{
|
||||||
test: /\.(js|jsx)$/,
|
test: /\.(js|jsx)$/,
|
||||||
exclude: /\/node_modules\//,
|
exclude: /\/node_modules\//,
|
||||||
use: {
|
use: {
|
||||||
loader: 'babel-loader'
|
loader: "babel-loader",
|
||||||
}
|
},
|
||||||
}]
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.js', '.jsx']
|
extensions: [".js", ".jsx"],
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
For those familiar with Webpack already, please note that adding the Babel options to the `babel-loader` section of your `webpack.config.js` will throw an error, so you need to include them in the separate `.babelrc` file.
|
For those familiar with Webpack already, please note that adding the Babel options to the `babel-loader` section of your `webpack.config.js` will throw an error, so you need to include them in the separate `.babelrc` file.
|
||||||
|
|
@ -192,7 +199,7 @@ To generate a minified file, open `package.json` and add a new npm script called
|
||||||
"name": "my-project",
|
"name": "my-project",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack -d --watch",
|
"start": "webpack -d --watch",
|
||||||
"build": "webpack -p",
|
"build": "webpack -p"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -210,6 +217,46 @@ You can use hooks in your production environment to run the production build scr
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Making `m` accessible globally
|
||||||
|
|
||||||
|
In order to access `m` globally from all your project first import `webpack` in your `webpack.config.js` like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
const webpack = require('webpack')
|
||||||
|
```
|
||||||
|
|
||||||
|
Then create a new plugin in the `plugins` property of the Webpack configuration object:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
plugins: [
|
||||||
|
new webpack.ProvidePlugin({
|
||||||
|
m: "mithril",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See [the Webpack docs](https://webpack.js.org/plugins/provide-plugin/) for more information on `ProvidePlugin`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Differences with React
|
||||||
|
|
||||||
|
JSX in Mithril has some subtle but important differences compared to React's JSX.
|
||||||
|
|
||||||
|
#### Attribute and style property case conventions
|
||||||
|
|
||||||
|
React requires you use the camel-cased DOM property names instead of HTML attribute names for all attributes other than `data-*` and `aria-*` attributes. For example using `className` instead of `class` and `htmlFor` instead of `for`. In Mithril, it's more idiomatic to use the lowercase HTML attribute names instead. Mithril always falls back to setting attributes if a property doesn't exist which aligns more intuitively with HTML. Note that in most cases, the DOM property and HTML attribute names are either the same or very similar. For example `value`/`checked` for inputs and the `tabindex` global attribute vs the `elem.tabIndex` property on HTML elements. Very rarely do they differ beyond case: the `elem.className` property for the `class` attribute or the `elem.htmlFor` property for the `for` attribute are among the few exceptions.
|
||||||
|
|
||||||
|
Similarly, React always uses the camel-cased style property names exposed in the DOM via properties of `elem.style` (like `cssHeight` and `backgroundColor`). Mithril supports both that and the kebab-cased CSS property names (like `height` and `background-color`) and idiomatically prefers the latter. Only `cssHeight`, `cssFloat`, and vendor-prefixed properties differ in more than case.
|
||||||
|
|
||||||
|
#### DOM events
|
||||||
|
|
||||||
|
React upper-cases the first character of all event handlers: `onClick` listens for `click` events and `onSubmit` for `submit` events. Some are further altered as they're multiple words concatenated together. For instance, `onMouseMove` listens for `mousemove` events. Mithril does not do this case mapping but instead just prepends `on` to the native event, so you'd add listeners for `onclick` and `onmousemove` to listen to those two events respectively. This corresponds much more closely to HTML's naming scheme and is much more intuitive if you come from an HTML or vanilla DOM background.
|
||||||
|
|
||||||
|
React supports scheduling event listeners during the capture phase (in the first pass, out to in, as opposed to the default bubble phase going in to out in the second pass) by appending `Capture` to that event. Mithril currently lacks such functionality, but it could gain this in the future. If this is necessary you can manually add and remove your own listeners in [lifecycle hooks](lifecycle.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### JSX vs hyperscript
|
### JSX vs hyperscript
|
||||||
|
|
@ -224,16 +271,16 @@ You can see the tradeoffs come into play in more complex trees. For instance, co
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
function SummaryView() {
|
function SummaryView() {
|
||||||
let tag, posts
|
let tag, posts;
|
||||||
|
|
||||||
function init({ attrs }) {
|
function init({ attrs }) {
|
||||||
Model.sendView(attrs.tag != null)
|
Model.sendView(attrs.tag != null);
|
||||||
if (attrs.tag != null) {
|
if (attrs.tag != null) {
|
||||||
tag = attrs.tag.toLowerCase()
|
tag = attrs.tag.toLowerCase();
|
||||||
posts = Model.getTag(tag)
|
posts = Model.getTag(tag);
|
||||||
} else {
|
} else {
|
||||||
tag = undefined
|
tag = undefined;
|
||||||
posts = Model.posts
|
posts = Model.posts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,7 +288,7 @@ function SummaryView() {
|
||||||
return m(".feed", [
|
return m(".feed", [
|
||||||
type,
|
type,
|
||||||
m("a", { href }, m("img.feed-icon[src=./feed-icon-16.gif]")),
|
m("a", { href }, m("img.feed-icon[src=./feed-icon-16.gif]")),
|
||||||
])
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -260,20 +307,31 @@ function SummaryView() {
|
||||||
tag != null
|
tag != null
|
||||||
? m(TagHeader, { len: posts.length, tag })
|
? m(TagHeader, { len: posts.length, tag })
|
||||||
: m(".summary-header", [
|
: m(".summary-header", [
|
||||||
m(".summary-title", "Posts, sorted by most recent."),
|
m(
|
||||||
|
".summary-title",
|
||||||
|
"Posts, sorted by most recent."
|
||||||
|
),
|
||||||
m(TagSearch),
|
m(TagSearch),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
m(".blog-list", posts.map((post) =>
|
m(
|
||||||
m(m.route.Link, {
|
".blog-list",
|
||||||
|
posts.map((post) =>
|
||||||
|
m(
|
||||||
|
m.route.Link,
|
||||||
|
{
|
||||||
class: "blog-entry",
|
class: "blog-entry",
|
||||||
href: `/posts/${post.url}`,
|
href: `/posts/${post.url}`,
|
||||||
}, [
|
},
|
||||||
m(".post-date", post.date.toLocaleDateString("en-US", {
|
[
|
||||||
|
m(
|
||||||
|
".post-date",
|
||||||
|
post.date.toLocaleDateString("en-US", {
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "long",
|
month: "long",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
})),
|
})
|
||||||
|
),
|
||||||
|
|
||||||
m(".post-stub", [
|
m(".post-stub", [
|
||||||
m(".post-title", post.title),
|
m(".post-title", post.title),
|
||||||
|
|
@ -281,10 +339,12 @@ function SummaryView() {
|
||||||
]),
|
]),
|
||||||
|
|
||||||
m(TagList, { post, tag }),
|
m(TagList, { post, tag }),
|
||||||
])
|
]
|
||||||
)),
|
)
|
||||||
])
|
)
|
||||||
}
|
),
|
||||||
|
]),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -292,16 +352,16 @@ Here's the exact equivalent of the above code, using JSX instead. You can see ho
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
function SummaryView() {
|
function SummaryView() {
|
||||||
let tag, posts
|
let tag, posts;
|
||||||
|
|
||||||
function init({ attrs }) {
|
function init({ attrs }) {
|
||||||
Model.sendView(attrs.tag != null)
|
Model.sendView(attrs.tag != null);
|
||||||
if (attrs.tag != null) {
|
if (attrs.tag != null) {
|
||||||
tag = attrs.tag.toLowerCase()
|
tag = attrs.tag.toLowerCase();
|
||||||
posts = Model.getTag(tag)
|
posts = Model.getTag(tag);
|
||||||
} else {
|
} else {
|
||||||
tag = undefined
|
tag = undefined;
|
||||||
posts = Model.posts
|
posts = Model.posts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -309,9 +369,11 @@ function SummaryView() {
|
||||||
return (
|
return (
|
||||||
<div class="feed">
|
<div class="feed">
|
||||||
{type}
|
{type}
|
||||||
<a href={href}><img class="feed-icon" src="./feed-icon-16.gif" /></a>
|
<a href={href}>
|
||||||
|
<img class="feed-icon" src="./feed-icon-16.gif" />
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -327,19 +389,23 @@ function SummaryView() {
|
||||||
{feed("RSS", "blog.rss.xml")}
|
{feed("RSS", "blog.rss.xml")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{tag != null
|
{tag != null ? (
|
||||||
? <TagHeader len={posts.length} tag={tag} />
|
<TagHeader len={posts.length} tag={tag} />
|
||||||
: (
|
) : (
|
||||||
<div class="summary-header">
|
<div class="summary-header">
|
||||||
<div class="summary-title">Posts, sorted by most recent</div>
|
<div class="summary-title">
|
||||||
|
Posts, sorted by most recent
|
||||||
|
</div>
|
||||||
<TagSearch />
|
<TagSearch />
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
|
|
||||||
<div class="blog-list">
|
<div class="blog-list">
|
||||||
{posts.map((post) => (
|
{posts.map((post) => (
|
||||||
<m.route.Link class="blog-entry" href={`/posts/${post.url}`}>
|
<m.route.Link
|
||||||
|
class="blog-entry"
|
||||||
|
href={`/posts/${post.url}`}
|
||||||
|
>
|
||||||
<div class="post-date">
|
<div class="post-date">
|
||||||
{post.date.toLocaleDateString("en-US", {
|
{post.date.toLocaleDateString("en-US", {
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
|
|
@ -350,7 +416,9 @@ function SummaryView() {
|
||||||
|
|
||||||
<div class="post-stub">
|
<div class="post-stub">
|
||||||
<div class="post-title">{post.title}</div>
|
<div class="post-title">{post.title}</div>
|
||||||
<div class="post-preview">{post.preview}...</div>
|
<div class="post-preview">
|
||||||
|
{post.preview}...
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TagList post={post} tag={tag} />
|
<TagList post={post} tag={tag} />
|
||||||
|
|
@ -358,32 +426,16 @@ function SummaryView() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### A note on event listeners
|
### Tips and tricks
|
||||||
|
|
||||||
While JSX events are usually named using camelCase, lowercase names should be used when JSX is used with Mithril.
|
#### Converting HTML to JSX
|
||||||
|
|
||||||
```jsx
|
|
||||||
m("main", [
|
|
||||||
m("input", { type: "text", oninput: ... }),
|
|
||||||
m("input", { type: "button", value: "Click me", onclick: ... })
|
|
||||||
])
|
|
||||||
|
|
||||||
// can be written as:
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<input type="text" oninput={...}/>
|
|
||||||
<input type="button" value="Click me" onclick={...}/>
|
|
||||||
</main>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Converting HTML
|
|
||||||
|
|
||||||
In Mithril.js, well-formed HTML is generally valid JSX. Little more than just pasting raw HTML is required for things to just work. About the only things you'd normally have to do are change unquoted property values like `attr=value` to `attr="value"` and change void elements like `<input>` to `<input />`, this being due to JSX being based on XML and not HTML.
|
In Mithril.js, well-formed HTML is generally valid JSX. Little more than just pasting raw HTML is required for things to just work. About the only things you'd normally have to do are change unquoted property values like `attr=value` to `attr="value"` and change void elements like `<input>` to `<input />`, this being due to JSX being based on XML and not HTML.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue