- What it is
- Why do we need it
- How do we use it
- How it works
WHAT is Mobx
- Data binding/reactive programming library
- Angular, vuejs
- Can be used in react, angular(js), vuejs etc
- Backbone - M observable eXtension
@MWeststrate
Object.observe is dead, long live ES6 Proxies
- When something happen, do something else
- Promise
How it is usually done
- relying on some template engine (vuejs, angular)
- not encoded in the actual code
- callback (
async.js
,even-emitter
,$.on
, scope.$watch) Rx.Observable
declare const rateStream, impressionStream: Observable<number>
const lineItemStream = Observable
.combineLatest(rateStream, impressionStream)
.map([rate, impression]) => ({
cost: rate * inpression
})
// ..
lineItemStream.subscribe(
//...
)
// Very pervasive style
--
import { observable } from 'mobx'
class LineItem {
@observable rate = 0.2
@observable impression = 1
get cost() {
return this.rate * this.impression
}
// ...
const lineItem = new LineItem
require('mobx').autorun(_ =>
console.log("Cost change", lineItem.cost)
);
Transparent reactive programming
(function tick() {
lineItem.impression++
setTimeout(tick, 1000)
})()
// Cost change 0.4
// Cost change 0.6
// Cost change 0.8
// Cost change 1
import {observer} from 'mobx-react'
@observer
class LineItem extends React.Component {
render() {
return <span>cost: {this.props.cost}</span>
}
}
// or as function
const LineItem = observer(({cost}) =>
<span>cost: {cost}</span>
)
The component does not care whether the props is "observable" or not
type LineIem = {
props: {cost: any}
| Observable<{cost: any}>
}
- Front-end data store
- Composability in Redux
combineReducer
createSelector
(from another library)import {selectors, actions} from '../a/subdirectory'
- A lot of name to be given that are just kind of the same
- Code is verbose, yet obscure
- Modeling domain stores
class LineItemStore {
lineItemsById = observable.map()
}
class Api {
updateLineItem(patch: LineItem): Promise<LineItem>
}
class CampaignService {
constructor(
public lineItemStore: LineItemStore
private lineItemApi: Api
) { }
async updateLineItem(patch) {
const lineItem = await this.lineItemApi.updateLineItem(patch)
return this.lineItemStore.lineItemsById.set(lineItem.id, lineItem)
}
}
--
@observer
import { observer } from 'mobx-react'
const Campaign = obsever(
({ campaignService: { lineItemStore } }) =>
<ol>
{lineItemStore.lineItemsById.keys().map(id =>
<LineItem key={id} lineItem={lineItemsById.get(id)} />) // on-time binding
}
</ol>
)
--
- Dependency injection
<Provider
campaignService={new CampaignService(// ...
>
<App />
</Provider>
// can have multiple Provider
@inject('campaignService')
@observe
class Campaign {
const name = observable('Product name')
const product = observable({
name: 'Product name',
publisherId: 01383,
})
product.name = 'new name' // for existing property
extendObservable(object, patch) // instead of Object.extends
const hashMap = observable(new Map) // instead of plain objects
for (const key of hashMap) { // ES6 iterable compliant
//...
const products = observable( [ ] )
_.map(products, console.info)
- Modeling views
@observer
class EditableLineItem extends React.Component {
impression = observable(new LineItem)
updateImpression = () => {
const lineItemStore = this.props
lineItemStore.updateLineItem({ id: this.props.id, impression: this.impression })
this.impression = null;
}
//...
render() {
return <div>
<input
value={this.lineItem.impression}
onChange={({target}) => this.impression = target.value}
/>
<button onClick={this.updateImpression}>Save</button>
</div>
}
}
can do completely away with setState
--
class extends React.Component {
constructor(props) {
props.publisherApiPromise.then(
publishersById => this.setState({ publishersById })
)
}
render() {
return this.state.publishersById ? 'loaded': 'loading..'
}
}
import { fromPromise } from 'mobx-utils'
class extends React.Component {
publishersById = fromPromise(this.props.publisherApiPromise)
render() {
return this.publishersById.value ? 'loaded': 'loading..'
}
}
--
class extends React.Component {
componentDidMount() {
this.props.fetch().then(data => this.setState(data))
}
render() {
return <div>
{this.state.data}
</div>
}
}
import { lazyObservable } from 'mobx-utils'
class extends React.Component {
data = lazyObservable(cb => this.props.fetch().then(cb))
render() {
return <div>
{this.props.show && data.current()}
</div>
}
}
- Intercepting access to properties
$ node
> const { observable } = require('mobx')
> observable({
> impression: 1,
> rate: 0.2,
> })
{ impression: [Getter/Setter], rate: [Getter/Setter] }
- Tracking data dependencies
autorun(function tracked() {
console.log(lineItem/**/.impression/**/, lineItem/**/.rate/**/)
})
- Pros
- Does not impose a coding style on the code base
- Fits well with familiar patterns (business objects, facades...)
- Fit well with unidirectional data flow
- Cons
- Implicit
- Inconsistencies
obervable.prop obsevable.get()
- Dereferencing (more like a gotcha)
function Cost({ cost }) { return cost } function Cost({ lineItem }) { return lineItem.cost }
- Immutability