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,635