MobX

Занятие 8

Профессия
Node.js & React.js developer
продвинутый курс

1

  • MobX
  • Observable
  • Computed
  • Reaction
  • Action
  • DevTools

План

2

MobX

MobX

Michel Weststrate

MobX

MobX

MobX

TypeScript

ES6

ES5

Imports

import { observable, computed, toJS } from "mobx";
import { observer, inject } from "mobx-react";

JS Fiddle

3

Observable

states, stores

Observable

Observable

Observable values can be

  • JS primitives
  • references
  • plain objects
  • class instances
  • arrays
  • maps

JS Maps

var myMap = new Map();

var keyString = 'a string',
    keyObj = {},
    keyFunc = function() {};

// setting the values
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, 'value associated with keyObj');
myMap.set(keyFunc, 'value associated with keyFunc');
myMap.size; // 3

...

JS Maps

...

myMap.get(keyString);    // "value associated with 'a string'"
myMap.get(keyObj);       // "value associated with keyObj"
myMap.get(keyFunc);      // "value associated with keyFunc"

myMap.get('a string');   // "value associated with 'a string'"
                         // because keyString === 'a string'
myMap.get({});           // undefined, because keyObj !== {}
myMap.get(function() {}) // undefined, because keyFunc !== function () {}

JS Maps

Observable Primitives

const int = observable(20);
const str = observable("Some string");

Observable Arrays

const todos = observable([
    { title: "Spoil tea", completed: true },
    { title: "Make coffee", completed: false }
]);

Observable Objects

const person = observable({
    // observable properties:
    name: "John",
    age: 42,
    showAge: false,

    // computed property:
    get labelText() {
        return this.showAge ? 
          `${this.name} (age: ${this.age})` : 
          this.name;
    },
});

toJS

temperature.get()
todo.toJS() or todo.slice()
person ???

import { toJS } from 'mobx';

console.log(toJS(null));
console.log(toJS(123));
console.log(toJS(temperature));
console.log(toJS(todos));
console.log(toJS(person));

// Note: this method was named toJSON before MobX 2.2

fromJS??

personJson = {
  id: 1,
  name: 'Steve Jobs',
};
person = observable(personJson);

personJson = getNewPersonJson();
person = observable(personJson); // bad

??

import { extendObservable } from 'mobx';
extendObservable(person, personJson);

extendObservable

var Person = function(firstName, lastName) {
    // initialize observable properties on a new instance
    extendObservable(this, {
        firstName: firstName,
        lastName: lastName
    });
}

var matthew = new Person("Matthew", "Henry");

// add a observable property to an already observable object
extendObservable(matthew, {
    age: 353
});

extendObservable

class Person {
  constructor(props) {
    extendObservable(this, props);
  }
}

toJS, toJSON, fromJS, fromJS...

import {
  createModelSchema, primitive, reference,
  list, object, identifier,
  serialize, deserialize
} from "serializr";


class Message { ... }
class User { ... }

// Create model schemas
createModelSchema(Message, {
    message : primitive(),
    author  : reference(User, findUserById),
    comments: list(object(Message))
});

const message = deserialize(Message, {
    message : "Hello world",
    author  : 17,
    comments: [
        {
            message: "Welcome!",
            author : 23
        }
    ]
});

4

Computed

derivation

computed in observable

const box = observable({
    length: 2,
    get square() {
        return this.length * this.length;
    },
    set square(value) {
        this.length = Math.sqrt(value);
    }
});

@computed

import {observable, computed} from "mobx";

class OrderLine {
    @observable price = 0;
    @observable amount = 1;

    constructor(price) {
        this.price = price;
    }

    @computed get total() {
        return this.price * this.amount;
    }
}

computed function

import {observable, computed} from "mobx";
var name = observable("John");

var upperCaseName = computed(() =>
    name.get().toUpperCase()
);

var disposer = upperCaseName.observe(
 change => console.log(change.newValue)
);

name.set("Dave");
// prints: 'DAVE'

Don't confuse computed with autorun.

5

Reactions

observe, observer, autorun, rerender

Observe

import { observable } from 'mobx';

const cityName = observable("Vienna");

function printCity() {
  console.log(cityName.get());
}
cityName.observe(printCity);


cityName.set("Amsterdam");
cityName.set("London");

Autorun

var numbers = observable([1,2,3]);
var sum = computed(() => numbers.reduce((a, b) => a + b, 0));

var disposer = autorun(() => console.log(sum.get()));
// prints '6'
numbers.push(4);
// prints '10'

disposer();
numbers.push(5);

Autorun

var numbers = observable([1,2,3]);
var sum = computed(() => numbers.reduce((a, b) => a + b, 0));

let showSum = false;

var disposer = autorun(() => {
  if (showSum) {
    console.log(sum.get()
  } else {
    console.log('showSum === false')
  }
}));
// prints 'showSum === false'

showSum = true;

numbers.push(4);
// nothing
numbers.push(5);
// nothing

Observer

import {observer} from "mobx-react";

// ---- ES5 syntax ----

const TodoView = observer(React.createClass({
    displayName: "TodoView",
    render() {
        return <div>{this.props.todo.title}</div>
    }
}));

// ---- ES6 syntax ----

const TodoView  = observer(class TodoView extends React.Component {
    render() {
        return <div>{this.props.todo.title}</div>
    }
})

Observer

// ---- ESNext syntax with decorators ----

@observer class TodoView extends React.Component {
    render() {
        return <div>{this.props.todo.title}</div>
    }
}

// ---- or just use a stateless component function: ----

const TodoView = observer(({todo}) => <div>{todo.title}</div>)

<Observer>

class App extends React.Component {
  render() {
     return (
         <div>
            {this.props.person.name}
            <Observer>
                {() => <div>{this.props.person.name}</div>}
            </Observer>
        </div>
     )
  }
}

const person = observable({ name: "John" })

React.render(<App person={person} />, document.body)
person.name = "Mike" // will cause the Observer region to re-render

SSR

componentWillReact

import {observer} from "mobx-react";

@observer class TodoView extends React.Component {
    componentWillReact() {
        console.log("I will re-render, since the todo has changed!");
    }

    render() {
        return <div>{this.props.todo.title}</div>
    }
}

PropTypes

import { PropTypes } from "mobx-react"
  • observableArray
  • observableArrayOf(React.PropTypes.number)
  • observableMap
  • observableObject
  • arrayOrObservableArray
  • arrayOrObservableArrayOf(React.PropTypes.number)
  • objectOrObservableObject

<Provider>

class Message extends React.Component {
  render() {
    return (
      <div>
        {this.props.text} <Button>Delete</Button>
      </div>
    );
  }
}

class MessageList extends React.Component {
  render() {
    const children = this.props.messages.map((message) =>
      <Message text={message.text} />
    );
    return <Provider color="red">
        <div>
            {children}
        </div>
    </Provider>;
  }
}

@inject

@inject("color") @observer
class Button extends React.Component {
  render() {
    return (
      <button style={{background: this.props.color}}>
        {this.props.children}
      </button>
    );
  }
}

@inject

@inject("color", "user", "theme")

@inject((stores) => ({
   myUser: stores.user,
}))

@inject

const Button = ({ user, myUser, name }) => (
  <div>
    {myUser._id === user._id ? <Edit /> : null}
    {name}
  </div>
);

const EditProfile = inject(
    stores => ({
        name: stores.user.name,
        myUser: stores.user
    })
)(Button)

6

Action

Action

Action

class Box {
    @observable width = 10;
    @observable height = 5;
    @observable length = 2;

    @action
    add(x = 1) {
      this.width += x;
      this.height += x;
      this.height += x;
    }
    
    sub(x = 1) {
      this.width -= x;
      this.height -= x;
      this.height -= x;
    }

    get volume() {
    	return this.width * this.height * this.length
    }
};
const box = new Box({})

runInAction

@action /*optional*/ updateDocument = async () => {
    const data = await fetchDataFromUrl();
    /* required in strict mode to be allowed to update state: */
    runInAction("update state after fetching data", () => {
        this.data.replace(data);
        this.isSaving = true;
    })
}

7

DevTools

DevTools

DevTools

npm install mobx-react-devtools


import DevTools from 'mobx-react-devtools'

const App = () => (
  <div>
    ...
    { __DEV__ && <DevTools /> }
  </div>
)

Redux Dev Tools

Redux Dev Tools

Redux DevTools Extension

Redux & MobX

8

Summary

Summary

9

Про практику

9

  • 15 Апреля - Занятие про командную разработку
  • 22 Апреля - пропускаем теорию
  • Командные задачи
  • Созвоны с командами
  • LSK/Example2 - lsk.mgbeta.ru
  • Я проверяю индивидуальные задачи

Игорь Суворов

Thanks!

any questions?

программист-предприниматель

MobX

By Igor Suvorov

MobX

* template

  • 1,000