MobX

MobX

Externals
About The title

"Revenge Of The Observable!"
whomai
vitali.pe@gmail.com

day 0
whomai
vitali.pe@gmail.com


day 0
day 1546
MobX

MobX
A small library to manage state changes automatically

MobX
A small library to manage state changes automatically

MobX
A small library to manage state changes automatically

What Is "state"?
What Is "state"?
the particular condition that something is in at a specific time
What Is "state"?
the particular condition that something is in at a specific time
What Is "state"?
the particular condition that something is in at a specific time
What Is "state"?
the particular condition that something is in at a specific time
structure + changes
data
What Is "state"?
the particular condition that something is in at a specific time
structure + changes
data
What Is "state"?
the particular condition that something is in at a specific time
structure + changes
data

React

Solves This Problem For UI State
Kinda...
UI Tree

UI Tree

UI Tree

UI Tree


Problem:
Problem:
Problem:
any nontrivial app state is a graph
TodoMVC
TodoMVC
Not Only React




Ideas
1. Manually compose data in the tree.
Ideas
1. Manually compose data in the tree.
Ideas
1. Manually compose data in the tree.
2. Use a DSL to query for data.
Use a DSL to query for data.
(for example GraphQL)
{
user(id: 1) {
age
friends {
name
}
}
}
UI Tree
{
user(id: 42) {
name
friends {
age
}
}
}
-
String based DSL
-
No clear schema (by design)
-
Works well on dense graphs
-
Solves a more general problem
Ideas
1. Manually compose data in the tree.
2. Use a DSL to query for data.
3. Save state outside of the tree.
Save state outside of the tree.
A.K.A "The Reagent Way"
(def click-count (r/atom 0))
(defn counting-component []
[:div
"The atom " [:code "click-count"] " has value: "
@click-count ". "
[:input {:type "button" :value "Click me!"
:on-click #(swap! click-count inc)}]])
Save state outside of the tree.
A.K.A "The Reagent Way"
(def click-count (r/atom 0))
(defn counting-component []
[:div
"The atom " [:code "click-count"] " has value: "
@click-count ". "
[:input {:type "button" :value "Click me!"
:on-click #(swap! click-count inc)}]])
Ideas
1. Manually compose data in the tree.
2. Use a DSL to query for data.
3. Save state outside of the tree.
4. Make it look like a tree.
Make it look like a tree
UI Tree
Make it look like a tree
"ViewModel" in MVVM
($scope)
"Cursors" (om, Reagent,etc)
(def foo-cursor (cursor app-state [:foo]))
(defn inside-foo-cursor []
[:div (str "Inside foo-cursor: " @foo-cursor)])
MobX

A small library to manage state changes automatically
MobX

4 simple consepts, no magic

Observable State
var me = mobx.observable({
firstName : "vitali",
lastName : "Perchonok",
favoriteNumbers : [0],
age : 29
})

Observable State
var me = mobx.observable({
firstName : "vitali",
lastName : "Perchonok",
favoriteNumbers : [0],
age : 29
})
mobx.autorun(
() => console.log("your name:",
me.firstName, me.lastName))

Observable State
var me = mobx.observable({
firstName : "vitali",
lastName : "Perchonok",
favoriteNumbers : [0],
age : 29
})
mobx.autorun(
() => console.log("your name:",
me.firstName, me.lastName))

Observable State
var me = mobx.observable({
firstName : "vitali",
lastName : "Perchonok",
favoriteNumbers : [0],
age : 29
})
mobx.autorun(
() => console.log("your name:",
me.firstName, me.lastName))

me.fullName = "Other";
// console: your name: Other Perchonok
me.lastName = "Name";
// console: your name: Other Name
me.age = 30;
me.favoriteNumbers.push(12);
// no effect

Observable State
Computed State
var me = mobx.observable({
....
fullName : function() {
return [this.firstName, this.lastName].join(" ")
}
})

Computed State
var me = mobx.observable({
....
fullName : function() {
return [this.firstName, this.lastName].join(" ")
}
})
mobx.autorun(() => console.log("your name:", me.fullName))

Computed State
var me = mobx.observable({
....
fullName : function() {
return [this.firstName, this.lastName].join(" ")
}
})
mobx.autorun(() => console.log("your name:", me.fullName))

Computed State
var me = mobx.observable({
....
fullName : function() {
return [this.firstName, this.lastName].join(" ")
}
})
mobx.autorun(() => console.log("your name:", me.fullName))

Computed State
var me = mobx.observable({
....
fullName : function() {
return [this.firstName, this.lastName].join(" ")
}
})
mobx.autorun(() => console.log("your name:", me.fullName))

Computed State
var me = mobx.observable({
....
fullName : function() {
console.log("computed full name!");
return [this.firstName, this.lastName].join(" ")
}
})
mobx.autorun(() => console.log("your name:", me.fullName))

me.fullName = "Other";
// console: computed full name!
// console: your name: Other Perchonok
me.lastName = "Name";
// console: computed full name!
// console: your name: Other Name
me.fullName;
me.fullName;
// no effect

Computed State
me.fullName = "Other";
// console: computed full name!
// console: your name: Other Perchonok
me.lastName = "Name";
// console: computed full name!
// console: your name: Other Name
me.fullName;
me.fullName;
// no effect

Computed State
var me = mobx.observable({
...
rename : action(function (first, last) {
this.firstName = first;
this.lastName = last;
})
})
Actions

var me = mobx.observable({
...
rename : action("rename user",
function (first, last) {
this.firstName = first;
this.lastName = last;
})
})
Actions

var me = mobx.observable({
...
rename : action("rename user",
function (first, last) {
this.firstName = first;
this.lastName = last;
})
})
Actions

me.rename("Other", "name");
// console: computed full name!
// console: your name: Other Name
me.fullName;
me.fullName;
// no effect
Actions

me.rename("Other", "name");
// console: computed full name!
// console: your name: Other Name
mobx.useStrict(true);
me.firstName = "boo"; // throws:
// [mobx] Invariant failed: It is not allowed to
// create or change state outside an `action`
Actions

class Person {
@observable firstName = "vitali";
@observable lastName = "Perchonok";
...
@computed get fullName() {
return [this.firstName, this.lastName].join(" ");
}
@action("rename user") rename(first, last) {
this.firstName = first;
this.lastName = last;
}
}

ES6+
MobX

4 simple consepts, no magic

Simple things should be simple, complex things should be possible.
Alan Kay



+
"mobx-react"
class Crap extends React.Component {
render() {
return <Layout>
<Component data={this.props.user}/>
<SimpleWidget />
</layout>
}
}
export default observer(Crap);

observer()
class Crap extends React.Component {
render() {
return <Layout>
<Component data={this.props.user}/>
<SimpleWidget />
</layout>
}
}
export default observer(Crap);

observer()
var StatelessCrap = observer(({user}) => {
return <Layout>
<Component data={user}/>
<SimpleWidget />
</layout>
})
export default StatelessCrap ;

observer()
@observer
class Crap extends React.Component {
render() {
return <Layout>
<Component data={this.props.user}/>
<SimpleWidget />
</layout>
}
}
export default Crap;

@observer
@observer
class Crap extends React.Component {
render() {
return <Layout>
<Component data={this.props.user}/>
<SimpleWidget />
</layout>
}
}
export default Crap;

@observer

@observer
makes render() autorun

@observer
makes render() autorun
adds "pure render" mixin
class Counter extends React.Component {
@observable times = 0
render() {
var addOne = ()=> this.times++;
return <div>
<label>clicked {this.times} times</label>
<button onClick={addOne}>+1</button>
</div>
}
}

Local State
class Counter extends React.Component {
@observable times = 0
render() {
var addOne = ()=> this.times++;
return <div>
<label>clicked {this.times} times</label>
<button onClick={addOne}>+1</button>
</div>
}
}

Local State
Questions?
MobX
By Vitali Perchonok
MobX
- 1,672