Short explanation, it's going to be the most fun you've had working with state in any framework.
Long explanation, components have values and actions that can be invoked to change these values. We call this state.
To change the state of a component, you call an action that calls this.set
to change the value. State and its
transitions become an inseperable part of the component. Microstates make component state seperable from the component.
To learn what makes this seperation possible, checkout [How do Microstates work in Ember]()?
A Microstate is an object that is created from type and value. Type declares what states are created when a microstate is created from this type and how these microstates can be transitioned. Seperating state from the component makes many difficult things easy and fun.
Here are some benefits,
this.set
or this.get
in a Microstate.A microstate is an immmutable object that knows how to derive changed version of itself.
Microstates comes with 6 built-in types: Boolean
, Number
, String
, Array
, Object
and Any
. All other types you create yourself by composing primitive types into data structures that reflect the needs of your UI. You can declare custom types by using ES6 class syntax.
class Person {
age = Number;
name = String;
isEmberiño = Boolean;
}
These custom types can be composed further to create complex graphs of state that match complexity of your component. Microstates handles lazily materializing complex data structures allowing you to represent complex one directional graphs without worrying about penality of materializing state that your component might not be using.
Microstates have simple rules:
To learn more about Microstates, go to microstates/microstate.js. For a deeper funner introduction to Microstates checkout Charles Lowell's presentation at EmberATX meetup.
@microstates/ember
makes it easy to create microstates that cause Ember to re-render when a transition on the microstate is called. You can create a microstate in JavaScript using state
macro or in a template using state
helper. In both cases, you will get an object that will have transitions that you can invoke. When you call the transition, the state will update accordingly and your change will be reflected.
Here is one of the simplest Microstates you can make.
{{#let (state 10) as |counter|}}
{{counter.state}}
<button {{on "click" (noargs counter.increment)}} type="button">Increment</button>
{{/let}}
state
helper is polymorphic, meaning that it accepts arguments of different types. Based on the type of data that you pass to you,
you will get a microstate with different transitions that you can invoke in your template. For primitive types, you can create a microstate
by providing the initial value.
(noargs)
helper is something to prevent passing down the event since increment
and decrement
take an optional value.
It looks like this:
import { helper } from '@ember/component/helper';
export default helper(([act]) => {
return () => {
return act();
};
});
For custom type, you can use (type name)
helper to resolve the type via Ember's dependency injection system. Here is how we'd use our Person type.
// app/types/person.js
class Person {
age = Number;
name = String;
isEmberino = Boolean;
}
{{#let (state (type "person") initial) as |person|}}
{{person.name.state}} - {{person.age.state}} {{person.isEmberino.state}}
<input type="text" onchange={{tval person.name.set}}>
<button {{on "click" (noargs person.age.increment)}} type="button">Make older</button>
<button {{on "click" person.isEmberino.toggle}} type="button">Change projects</button>
{{/let}}
To understand how Microstates work in Ember we must decompose actions into their distict parts. These parts are,
Transition of state is done by changing properties on the component with this.set
. A rerender is initiated as a side-effect of
calling this.set
.
Microstates makes these two operations much clearer. Microstates by default are pure. They do not have any side-effects. However,
we still need to call this.set
when a transition computed the next state to initiate a rerender. Microstates provides a Store
mechanism that accepts a callback. We use this callback to invoke this.set
to initiate a rerender.
Here is what that would look like if we didn't use the macro that @microstates/ember
provides.
import Component from '@glimmer/component';
import { computed } from '@ember/object';
import { create, Store } from '@microstates/ember';
class Person {
name = String;
age = Number;
}
let microstate = create(Person, { name: 'Taras' });
export default class PersonManager extends Component {
@tracked state = Store(microstate, next => this.state = next);
}
Now any transition that you invoke on the state, will automatically create the next state and trigger a re-render. Here is the same component with the macro.
import Component from '@glimmer/component';
import { state } from '@microstates/ember';
class Person {
name = String;
age = Number;
}
export default class PersonManager extends Component {
@state(Person, { name: 'Taras' })
state;
}
Checkout the demos for examples of how Microstates can be used.
git clone
this repositorynpm install
bower install
ember server
npm test
(Runs ember try:testall
to test your addon against multiple Ember versions)ember test
ember test --server
ember build
For more information on using ember-cli, visit http://ember-cli.com/.