Add m.censor, adjust m.route.Link to use it (#2538)

Also, restructure a few things for better code reuse.
This commit is contained in:
Isiah Meadows 2019-09-30 18:44:39 -04:00 committed by GitHub
parent 3fa1630f91
commit 34f4363357
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 488 additions and 55 deletions

147
docs/censor.md Normal file
View file

@ -0,0 +1,147 @@
# censor(object, extra)
- [Description](#description)
- [Signature](#signature)
- [How it works](#signature)
---
### Description
Returns a shallow-cloned object with lifecycle attributes and any given custom attributes omitted.
```javascript
var attrs = {one: "two", enabled: false, oninit: function() {}}
var censored = m.censor(attrs, ["enabled"])
// {one: "two"}
```
---
### Signature
`censored = m.censor(object, extra)`
Argument | Type | Required | Description
------------ | ------------------------------------------ | -------- | ---
`object` | `Object` | Yes | A key-value map to be converted into a string
`extra` | `Array<String>` | No | Additional properties to omit.
**returns** | `Object` | | The original object if no properties to omit existed on it, a shallow-cloned object with the removed properties otherwise.
[How to read signatures](signatures.md)
---
### How it works
Ordinarily, you don't need this method, and you'll just want to specify the attributes you want. But sometimes, it's more convenient to send all attributes you don't know to another element. This is often perfectly reasonable, but it can lead you into a major trap with lifecycle methods getting called twice.
```javascript
function SomePage() {
return {
view: function() {
return m(SomeFancyView, {
oncreate: function() {
sendViewHit(m.route.get(), "some fancy view")
},
})
},
}
}
function SomeFancyView() {
return {
view: function(vnode) {
return m("div", vnode.attrs, [ // !!!
// ...
])
},
}
}
```
This looks benign, but this creates a problem: you're sending two hits each time this view is navigated. This is where `m.censor` come in: it lets you strip that `oncreate` from the attributes so it only gets called once and so the caller can remain sane and rest assured they aren't dealing with super weird bugs because of it.
```javascript
// Fixed
function SomeFancyView() {
return {
view: function(vnode) {
return m("div", m.censor(vnode.attrs), [
// ...
])
},
}
}
```
You can also run into similar issues with keys:
```javascript
function SomePage() {
return {
view: function() {
return m(Layout, {
pageTitle: "Some Page",
key: someKey,
}, [
// ...
])
},
}
}
function Layout() {
return {
view: function(vnode) {
return [
m("header", [
m("h1", "My beautiful web app"),
m("nav"),
]),
m(".body", vnode.attrs, [ // !!!
m("h2", vnode.attrs.pageTitle),
vnode.children,
])
]
},
}
}
```
This would end up [throwing an error](keys.md#avoid-mixing-keyed-and-non-keyed-vnodes-in-the-same-array) because here's what Mithril sees when creating the `Layout` vnode:
```javascript
return [
m("header", [
m("h1", "My beautiful web app"),
m("nav"),
]),
m(".body", {pageTitle: "Some Page", key: someKey}, [
m("h2", "Some Page"),
[/* ... */],
])
]
```
You wouldn't likely catch that at first glance, especially in much more real-world scenarios where there might be indirection and/or other issues. To correct this, you similarly have to censor out the `key:` attribute. You can also censor out the custom `pageTitle` attribute, too, since it doesn't provide any real value being in the DOM.
```javascript
// Fixed
function Layout() {
return {
view: function(vnode) {
return [
m("header", [
m("h1", "My beautiful web app"),
m("nav"),
]),
m(".body", m.censor(vnode.attrs, ["pageTitle"]), [
m("h2", vnode.attrs.pageTitle),
vnode.children,
])
]
},
}
}
```

View file

@ -25,6 +25,7 @@
- For a better debugging experience with `m.route` route resolvers, errors on `onmatch` in the default route are left unhandled and errors in `onmatch` in other routes are logged to the console before redirecting. ([#2536](https://github.com/MithrilJS/mithril.js/pull/2536) [@isiahmeadows](https://github.com/isiahmeadows))
- Bug fix with `m.redraw` where if you removed a root that was previously visited in the current redraw pass, it would lose its place and skip the next root.
- Add `params:` attribute to `m.route.Link`. ([#2537](https://github.com/MithrilJS/mithril.js/pull/2537) [@isiahmeadows](https://github.com/isiahmeadows))
- Add `m.censor`. ([#2538](https://github.com/MithrilJS/mithril.js/pull/2538) [@isiahmeadows](https://github.com/isiahmeadows))
-->

View file

@ -41,7 +41,7 @@ A splat argument means that if the argument is an array, you can omit the square
In the example at the top, this means that `m("div", {id: "foo"}, ["a", "b", "c"])` can also be written as `m("div", {id: "foo"}, "a", "b", "c")`.
Splats are useful in some compile-to-js languages such as Coffeescript, and also allow helpful shorthands for some common use cases.
Splats are useful in some compile-to-JS languages such as CoffeeScript, and also allow helpful shorthands for some common use cases.
---