Docs: mithril.component.m - Recommended changes
These changes make the the text clearer to me. I did not want to change the voice of the text, but I tried to clear out some verbosity and improve consistency. I did my best not to interfere with the logic of the prose.
This document seems written to stand on its own, so I recommend the following additions:
- a general explanation of the args param as in `controller: function(args) {` and `view: function(ctrl, args) { ...`
- a specific explanation of how {data: args} in m.request() becomes url parameters (mithril.request.md could use the same treatment)
- an explanation of the extras param as in `controller: function(args, extras) {` and `view: function(ctrl, args, extras) { ...`
This commit is contained in:
parent
ea8853eec2
commit
62e38287f7
1 changed files with 25 additions and 20 deletions
|
|
@ -26,7 +26,7 @@ Components are building blocks for Mithril applications. They allow developers t
|
|||
|
||||
### Rendering components
|
||||
|
||||
In Mithril, a component is nothing more than an object that has a `view` functions and, optionally, a `controller` function.
|
||||
In Mithril, a component is nothing more than an object that has a `view` function and, optionally, a `controller` function.
|
||||
|
||||
```javascript
|
||||
var MyComponent = {
|
||||
|
|
@ -41,9 +41,14 @@ var MyComponent = {
|
|||
m.mount(document.body, MyComponent) // renders <h1>Hello</h1> into <body>
|
||||
```
|
||||
|
||||
The `controller` function, if present, creates an object that is meant to contain methods for a view to call. Those methods (and the `controller` function itself) may call model methods, and the controller may be used to store contextual data returned from model methods (for example, a [promise](mithril.deferred.md) from a [request](mithril.request.md)), or a reference to a view model.
|
||||
The optional `controller` function creates an object that may be used in the following recommended ways:
|
||||
|
||||
The `view` function creates a representation of a template that may consume model data and call controller methods to affect the model.
|
||||
- It can contain methods meant to be called by a `view`.
|
||||
- It can call model methods directly or from methods inside the resulting object.
|
||||
- It can store contextual data returned from model methods (i.e. a [promise](mithril.deferred.md) from a [request](mithril.request.md)).
|
||||
- It can hold a reference to a view model.
|
||||
|
||||
The `view` has access to methods and properties that the controller chooses to expose in the returned object. With those methods and properties, it creates a template that can consume model data and call controller methods to affect the model. This is the recommended way for views and models to exchange data.
|
||||
|
||||
```javascript
|
||||
//a simple MVC example
|
||||
|
|
@ -77,7 +82,7 @@ m.mount(document.body, MyComponent)
|
|||
//the number increments when the link is clicked
|
||||
```
|
||||
|
||||
Note that there's no requirement to tightly couple a controller and view while organizing code. It's perfectly valid to define controllers and views separately, and only bring them together when mounting them:
|
||||
Note that there is no requirement to tightly couple a controller and view while organizing code. It's perfectly valid to define controllers and views separately, and only bring them together when mounting them:
|
||||
|
||||
```javascript
|
||||
//controller.js
|
||||
|
|
@ -94,13 +99,17 @@ var view = function(ctrl) {
|
|||
m.mount(document.body, {controller: controller, view: view}) // renders <h1>Hello</h1>
|
||||
```
|
||||
|
||||
There are three ways to render a component: via [`m.route`](mithril.route.md) (if you are building a single-page application that has multiple pages), [`m.mount`](mithril.mount.md) (if your app only has one page), and [`m.render`](mithril.render.md) (if you are integrating Mithril's rendering engine into a larger framework and wish to manage redrawing yourself).
|
||||
There are three ways to render a component:
|
||||
|
||||
When a component is rendered, the `controller` function is called, then the `view` function is called. The return value of the `controller` function is passed to `view` as its first argument.
|
||||
- [`m.route`](mithril.route.md) (if you are building a single-page application that has multiple pages)
|
||||
- [`m.mount`](mithril.mount.md) (if your app only has one page)
|
||||
- [`m.render`](mithril.render.md) (if you are integrating Mithril's rendering engine into a larger framework and wish to manage redrawing yourself).
|
||||
|
||||
The `controller` function is called *once* when the component is rendered. Subsequently, the `view` function is called and will be called again anytime a redraw is required. The return value of the `controller` function is passed to the `view` as its first argument.
|
||||
|
||||
#### Optional controller
|
||||
|
||||
The `controller` function is optional and defaults to an empty function.
|
||||
The `controller` function is optional and defaults to an empty function: //controller: function() {}
|
||||
|
||||
```javascript
|
||||
//a component without a controller
|
||||
|
|
@ -115,7 +124,7 @@ m.mount(document.body, MyComponent) // renders <h1>Hello</h1>
|
|||
|
||||
#### Controller as a class constructor
|
||||
|
||||
A controller can also be used as a class constructor (i.e. it's possible to attach properties to the `this` object within the constructor, instead of returning a value.
|
||||
A controller can also be used as a class constructor (i.e. it's possible to attach properties to the `this` object within the constructor, instead of returning a value).
|
||||
|
||||
```javascript
|
||||
var MyComponent = {
|
||||
|
|
@ -134,15 +143,15 @@ m.mount(document.body, MyComponent) // renders <h1>Hello</h1>
|
|||
|
||||
The `view` function does not create a DOM tree when called. The return value of the view function is merely a plain Javascript data structure that represents a DOM tree. Internally, Mithril uses this data representation of the DOM to probe for data changes and update the DOM only where necessary. This rendering technique is known as *virtual DOM diffing*.
|
||||
|
||||
Later, any time event handlers are triggered by user input (or any time a redraw is required), the view function is run again and its return value is used to diff against the previous virtual DOM tree.
|
||||
The view function is run again whenever a redraw is required (i.e. whenever event handlers are triggered by user input). Its return value is used to diff against the previous virtual DOM tree.
|
||||
|
||||
It may sound expensive to recompute an entire view any time there's a change to be displayed, but this operation actually turns out to be quite fast, compared to rendering strategies used by older frameworks. Mithril's diffing algorithm makes sure expensive DOM operations are only performed if absolutely necessary, and as an extra benefit, the global nature of the redraw makes it easy to reason about and troubleshoot the state of the application.
|
||||
It may sound expensive to recompute an entire view any time there's a change to be displayed, but this operation actually turns out to be quite fast, compared to rendering strategies used by older frameworks. Mithril's diffing algorithm makes sure expensive DOM operations are performed only if absolutely necessary, and as an extra benefit, the global nature of the redraw makes it easy to reason about and troubleshoot the state of the application.
|
||||
|
||||
---
|
||||
|
||||
### Parameterized components
|
||||
|
||||
Components can have arguments "preloaded". In practice, what this means is that calling `m.component(MyComponent, {foo: "bar"})` will return a component that behaves exactly the same as `MyComponent`, but that binds `{foo: "bar"}` as an argument to both the `controller` and `view` functions.
|
||||
Components can have arguments "preloaded". In practice, this means that calling `m.component(MyComponent, {foo: "bar"})` will return a component that behaves exactly the same as `MyComponent`, but `{foo: "bar"}` will be bound as an argument to both the `controller` and `view` functions.
|
||||
|
||||
```javascript
|
||||
//declare a component
|
||||
|
|
@ -166,7 +175,7 @@ m.render(document.body, component.view(ctrl)) // render the virtual DOM tree man
|
|||
//<body><h1>Hello world this is a test</h1></body>
|
||||
```
|
||||
|
||||
The first parameter after the component object is meant to be used as an attribute map and should be an object (e.g. `{name: "world"}`, above). Subsequent parameters have no restrictions (e.g. `"this is a test"`)
|
||||
The first parameter after the component object is meant to be used as an attribute map and should be an object (e.g. `{name: "world"}`). Subsequent parameters have no restrictions (e.g. `"this is a test"`)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -203,7 +212,7 @@ m.mount(document.body, App)
|
|||
// </div>
|
||||
```
|
||||
|
||||
Components can be placed anywhere a regular element would. If you have components inside of a sortable list, you can - and should - add `key` attributes to your components to ensure that DOM elements are merely moved, if possible, instead of being recreated from scratch:
|
||||
Components can be placed anywhere a regular element can. If you have components inside a sortable list, you should add `key` attributes to your components to ensure that DOM elements are not recreated from scratch, but merely moved when possible. Keys must be unique within a list of sibling DOM elements, and they must be either a string or a number:
|
||||
|
||||
```javascript
|
||||
var App = {
|
||||
|
|
@ -235,17 +244,13 @@ var MyComponent = {
|
|||
m.mount(document.body, App)
|
||||
```
|
||||
|
||||
Keys must be unique within a list of sibling DOM elements, and they must be either a string or a number.
|
||||
|
||||
---
|
||||
|
||||
### Dealing with state
|
||||
|
||||
#### Stateless components
|
||||
|
||||
A component is said to be stateless when it does not store data internally. Instead, it's composed of [pure functions](http://en.wikipedia.org/wiki/Pure_function). It's a good practice to make components stateless because they are more predictable, and easier to reason about, test and troubleshoot.
|
||||
|
||||
Instead of copying arguments to the controller object (thereby creating internal state in the component), and then passing the controller object to the view, it is often desirable that views always update based on the most current list of arguments being passed to a component.
|
||||
Instead of copying arguments to the controller object and then passing the controller object to the view (thereby creating internal state in the component), it is often desirable that views update based on the current value of arguments passed to a component.
|
||||
|
||||
The following example illustrates this pattern:
|
||||
|
||||
|
|
@ -314,9 +319,9 @@ Note that the sample component above is illustrative. Ideally, temperature conve
|
|||
|
||||
### Stateful components
|
||||
|
||||
Usually it's recommended that you store application state outside of components (either in a view-model or at the top-level component). Components *can* be stateful, but the purpose of component state is to prevent the pollution of the model layer with aspects that are inherently about the component. For example, an autocompleter component may need to internally store a flag to indicate whether the dropdown is visible, but this kind of state is not relevant to an application's business logic.
|
||||
Usually it's recommended that you store application state outside of components (either in a view-model or in the top-level component in the case of nested components). Components *can* be stateful, but the purpose of component state is to prevent the pollution of the model layer with aspects that are inherently related to the component. For example, an autocompleter component may need to internally store a flag to indicate whether the dropdown is visible, but this kind of state is not relevant to an application's business logic.
|
||||
|
||||
You may also elect to use component state for application state that is not meaningful outside the scope of a single component. For example, you might have a `UserForm` component that lives alongside other unrelated components on a bigger page, but it probably doesn't make sense for the parent page to be aware of the unsaved user entity stored within the `UserForm` component.
|
||||
You might also elect to maintain component state when it's not meaningful outside the scope of a single component. For example, you might have a `UserForm` component that lives alongside other unrelated components on a bigger page, but it probably doesn't make sense for the parent page to be aware of the unsaved user data stored within the `UserForm` component.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue