MobX Quick Start Guide [下]

Derivations, Actions, and Reactions

  • Computed properties (alson known as derivations) and their various options
  • Actions, with special focus on async actions
  • Reactions and the rules governing when MobX reacts

Observable State = (Core-mutable-State) + (Derived-readonly-State)

After all, a reaction in MobX looks at observables and produces side effects.

action = untracked( transaction( allowStateChanges( true, <mutating-function> ) ) )

This combination has the following much-intended effects:

  • Reducing excessive notifications
  • Improving efficiency by batching up a minial set of notifications
  • Minimizing executions of side effects, for observables that change several times in an action

setters are automatically wrapped by an action() by MobX. A setter for a computed-property is not really changing the computed-property directly. Instead, it is the inverse that mutates the dependent observables that make up the computed-property.

class ShoppingCart {
    /* ... */
    @action
    async submit() {
        this.asyncState = "pending";
        try {
            const response = await this.purchaseItems(this.items);
            runInAction(() => {
                this.asyncState = "completed";
            });
        } catch (ex) {
            console.log(ex);
            runInAction(() => {
                this.asyncState = "failed";
            });
        }
    }
}
复制代码

runInAction(fn) is just a convenience utility that is equivalent ot action(fn)().

class AuthStore {
    @observable loginState = "";
    login = flow(function*(username, password) {
        this.loginState = "pending";
        yield this.initializeEnvironment();
        this.loginState = "initialized";
        yield this.serverLogin(username, password);
        this.loginState = "completed";
        yield this.sendAnalytics();
        this.loginState = "reported";
        yield this.delay(3000);
    });
}

const promise = new AuthStore().login();
promise.cancel();
复制代码

Reactions

(Identify type of reaction)
=> Long Running? => No, one-time only => when()
                 => Yes => Selectively pick Observables? => No => autorun()
                                                         => Yes => reaction()

复制代码
import { autorun, reaction, when } from "mobx";

const disposer1 = autorun(() => {
    /* effect function */
});

const disposer2 = reaction(
    () => {
        /* tracking function returning data */
    },
    data => {
        /* effect function */
    }
);

const disposer3 = when(
    () => {
        /* predicate function */
    },
    predicate => {
        /* effect function */
    }
);

// Dispose pre-maturely
disposer1();
disposer2();
disposer3();
复制代码

Handling Real-World Use Cases

  • Form validation
  • Page routing
import Validate from "validate.js";

configure({ enforceActions: "strict" });

class UserEnrollmentData {
    @observable email = "";
    @observable password = "";
    @observable firstName = "";
    @observable lastName = "";
    @observable validating = false;
    @observable.ref errors = null;
    @observable enrollmentStatus = "none"; // none | pending | completed | failed

    /** @desc side effects */
    disposeValidation = null;

    constructor() {
        this.setupValidation();
    }

    setupValidation() {
        this.disposeValidation = reaction(
            () => {
                const { firstName, lastName, password, email } = this;
                return { firstName, lastName, password, email };
            },
            () => {
                this.validateFields(this.getFields());
            }
        );
    }

    cleanup() {
        this.disposeValidation();
    }

    @action setField(field, value) {
        this[field] = value;
    }

    getFields() {
        const { firstName, lastName, password, email } = this;
        return { firstName, lastName, password, email };
    }

    enroll = flow(function*() {
        this.enrollmentStatus = "pending";
        try {
            // Validation
            const fields = this.getFields();

            /** @desc Validation is actually a side effect that is triggered any time the fields change, but can also be invoked directly as an action. */
            yield this.validateFields(fields);
            if (this.errors) {
                throw new Error("Invalid fields");
            }
            // Enrollment
            yield enrollUser(fields);

            this.enrollmentStatus = "completed";
        } catch (err) {
            this.enrollmentStatus = "failed";
        }
    });

    validateFields = flow(function*(fields) {
        this.validating = true;
        this.errors = null;

        try {
            yield Validate.async(fields, rules);
            this.errors = null;
        } catch (err) {
            this.errors = err;
        } finally {
            this.validating = false;
        }
    });
}
复制代码

Special API for Special Cases

  • Direct manipulation with the object API

  • Using inject() and observe() to hook into the internal MobX eventing system

  • Special utility functions and tools that will help in debugging

  • Quick mention of some miscellaneous APIs

  • get(thing, key): Retrieves the value under the key. This key can even be nonexistent. When used in a reaction, it will trigger a re-execution when that key becomes available.

  • set(thing, key, value) or set(thing, { key: value }): sets a value for the key. The second form is better for setting multiple key-value pairs at once. Conceptually, it is very similar to Object.assign(), but with the addition of being reactive.

  • has(thing, key): Gives back a boolean indicating if the key is present.

  • remove(thing, key): Removes the given key and its value.

  • values(thing): Gives an array of values.

  • keys(thing): Gives an array containing all the keys. Note that this only applies to observable objects and maps.

  • entries(thing): Gives back an array of key-value pairs, where each pair is an array of two elements ([key, value])

import {
    autorun,
    observable,
    set,
    get,
    has,
    toJS,
    runInAction,
    remove,
    values,
    entries,
    keys
} from "mobx";
class Todo {
    @observable description = "";
    @observable done = false;
    constructor(description) {
        this.description = description;
    }
}

const firstTodo = new Todo("Write Chapter");
const todos = observable.array([firstTodo]);
const todosMap = observable.map({
    "Write Chapter": firstTodo
});

// Reactions to track changes
autorun(() => {
    console.log(`metadata present: ${has(firstTodo, "metadata")}`);
    console.log(get(firstTodo, "metadata"), get(firstTodo, "user"));
    console.log(keys(firstTodo));
});
autorun(() => {
    // Arrays
    const secondTodo = get(todos, 1);
    console.log("Second Todo:", toJS(secondTodo));
    console.log(values(todos), entries(todos));
});

// Granular changes
runInAction(() => {
    set(firstTodo, "metadata", "new Metadata");
    set(firstTodo, { metadata: "meta update", user: "Pavan Podila" });
    set(todos, 1, new Todo("Get it reviewed"));
});
runInAction(() => {
    remove(firstTodo, "metadata");
    remove(todos, 1);
});

// metadata present: false
// undefined undefined
// (2) ["description", "done"]

// Second Todo: undefined
// [Todo] [Array(2)]

// metadata present: true
// meta update Pavan Podila
// (4) ["description", "done", "metadata", "user"]

// Second Todo: {description: "Get it reviewed", done: false}
// (2) [Todo, Todo] (2) [Array(2), Array(2)]

// metadata present: false
// undefined "Pavan Podila"

// (3) ["description", "done", "user"]
// Second Todo: undefined
// [Todo] [Array(2)]
复制代码
  • disposer = onBecomeObserved(observable, property?: string, listener: () => void)
  • disposer = onBecomUnobserved(observable, property?: string, listener: () => void)
import {
    onBecomeObserved,
    onBecomeUnobserved,
    observable,
    autorun
} from "mobx";
const obj = observable.box(10);
const cart = observable({
    items: [],
    totalPrice: 0
});
onBecomeObserved(obj, () => {
    console.log("Started observing obj");
});
onBecomeUnobserved(obj, () => {
    console.log("Stopped observing obj");
});
onBecomeObserved(cart, "totalPrice", () => {
    console.log("Started observing cart.totalPrice");
});
onBecomeUnobserved(cart, "totalPrice", () => {
    console.log("Stopped observing cart.totalPrice");
});
const disposer = autorun(() => {
    console.log(obj.get(), `Cart total: ${cart.totalPrice}`);
});
setTimeout(disposer);
obj.set(20);
cart.totalPrice = 100;

// Started observing obj
// Started observing cart.totalPrice
// 10 "Cart total: 0"
// 20 "Cart total: 0"
// 20 "Cart total: 100"
// Stopped observing cart.totalPrice
// Stopped observing obj
复制代码

diposer = intercept(observable, property?, interceptor: (change = { type, object, newValue, oldValue }) => change | null)

Inside the interceptor callback, you have the opportunity to finalize the type of change you actually want to apply.

  • Return null and discard the change
  • Update with a different value
  • Throw an error indicating an exceptional value
  • Return as-is and apply the change
import { intercept, observable } from "mobx";
const theme = observable({
    color: "light",
    shades: []
});

const disposer = intercept(theme, "color", change => {
    console.log("Intercepting:", change);
    // Cannot unset value, so discard this change
    if (!change.newValue) {
        return null;
    }
    // Handle shorthand values
    const newTheme = change.newValue.toLowerCase();
    if (newTheme === "l" || newTheme === "d") {
        change.newValue = newTheme === "l" ? "light" : "dark"; // setthe correct value
        return change;
    } // check for a valid theme
    const allowedThemes = ["light", "dark"];
    const isAllowed = allowedThemes.includes(newTheme);
    if (!isAllowed) {
        throw new Error(`${change.newValue} is not a valid theme`);
    }
    return change; // Correct value so return as-is
});
复制代码

observe(observable, property?, observer: (change) => {})

import { observe, observable } from "mobx";
const theme = observable({
    color: "light",
    shades: []
});
const disposer = observe(theme, "color", change => {
    console.log(
        `Observing ${change.type}`,
        change.oldValue,
        "-->",
        change.newValue,
        "on",
        change.object
    );
});
theme.color = "dark";
复制代码

The signature is exactly like intercept(), but the behavior is quite different. observe() is invoked after the change has been applied to the observable.

An interesting characteristic is that observe() is immune to transactions. What this means is that the observer callback is invoked immediately after a mutation and does not wait until the transaction completes.

If you wanted to observe changes happening across all observables without having to individually set up the observe() handlers.

disposer = spy*(listner: (event = { type = update | add | delete | create | action | reaction | compute | error , name , oldValue, newValue, spyReportStart , spyReportEnd , ... }) => {})

trace() is a utility that is specifically focused on computed properties, reactions, and component renders.

trace(thing?, property?, enterDebugger? /_ a Boolean flag indicating whether you want to step into the debugger automatically _/)

Visual debugging with mobx-react-devtools

import DevTools from "mobx-react-devtools";
import React from "react";
export class MobXBookApp extends React.Component {
    render() {
        return (
            <Fragment>
                <DevTools />
                <RootAppComponent />
            </Fragment>
        );
    }
}
复制代码

A few other APIs:

  • Querying the reactive system: isObservableObject(thing), isObservableArray(thing), isObservableMap(thing), isObservable(thing), isObservableProp(thing, property?), isBoxedObservable(thing), isAction(func), isComputed(thing), isComputedProp(thing, property?)
  • Give you the internal representation of the observables and reactions: getAtom(thing, property?), getDependencyTree(thing, property?), getObserverTree(thing, property?)

Exploring mobx-utils and mobx-state-tree

  • mobx-utils for a tool belt of utility functions
  • mobx-state-tree(MST) for an opinionated MobX

mobx-utils is an NPM package that gives you several standard utilities to handle common use cases in MobX.

  • fromPromise()
  • lazyObservable()
  • fromResource()
  • now()
  • createViewModel()

newPromise : { state: 'pending' | 'fulfilled' | 'rejected', value, case({pending, fulfilled, rejected}) } = fromPromise(promiseLike)

import { fromPromise, PENDING, FULFILLED, REJECTED } from "mobx-utils";
class Worker {
    operation = null;
    start() {
        this.operation = fromPromise(this.performOperation());
    }
    p;
    erformOperation() {
        return new Promise((resolve, reject) => {
            const timeoutId = setTimeout(() => {
                clearTimeout(timeoutId);
                Math.random() > 0.25
                    ? resolve("200 OK")
                    : reject(new Error("500 FAIL"));
            }, 1000);
        });
    }
}

import { fromPromise, PENDING, FULFILLED, REJECTED } from "mobx-utils";
import { observer } from "mobx-react";
import React, { Fragment } from "react";
import { CircularProgress, Typography } from "@material-ui/core/es/index";
@observer
export class FromPromiseExample extends React.Component {
    worker;
    constructor(props) {
        super(props);
        this.worker = new Worker();
        this.worker.start();
    }
    render() {
        const { operation } = this.worker;
        return operation.case({
            [PENDING]: () => (
                <Fragment>
                    <CircularProgress size={50} color={"primary"} />
                    <Typography variant={"title"}>
                        Operation in Progress
                    </Typography>
                </Fragment>
            ),
            [FULFILLED]: value => (
                <Typography variant={"title"} color={"primary"}>
                    Operation completed with result: {value}
                </Typography>
            ),
            [REJECTED]: error => (
                <Typography variant={"title"} color={"error"}>
                    Operation failed with error: {error.message}
                </Typography>
            )
        });
    }
}
复制代码

result = lazyObservable(sink => {}, initialValue)

resource = fromResource(subscriber: sink => {}, unsubscriber: () => {}, initialValue)

viewModel = createViewModel(model)

  • To finalize the updated values on the original model, you must call the submit() method of viewModel. To reverse any changes, you can invoke the reset() method. To revert a single property, use resetProperty(propertyName: string).
  • To check if viewModel is dirty, use the isDirty property. To check if a single property is dirty, use isPropertyDirty(propertyName: string).
  • To get the original model, use the handy model() method.

The advantage of using createViewModel() is that you can treat the whole editing process as a single transaction. It is final only when submit() is invoked. This allows you to cancel out prematurely and retain the original model in its previous state.

class FormData {
    @observable name = "<Unnamed>";
    @observable email = "";
    @observable favoriteColor = "";
}
const viewModel = createViewModel(new FormData());
autorun(() => {
    console.log(
        `ViewModel: ${viewModel.name}, Model: ${viewModel.model.name}, Dirty: ${
            viewModel.isDirty
        }`
    );
});
viewModel.name = "Pavan";
viewModel.email = "pavan@pixelingene.com";
viewModel.favoriteColor = "orange";
console.log("About to reset");
viewModel.reset();
viewModel.name = "MobX";
console.log("About to submit");
viewModel.submit();

// ViewModel: <Unnamed>, Model: <Unnamed>, Dirty: false
// ViewModel: Pavan, Model: <Unnamed>, Dirty: true
// About to reset...
// ViewModel: <Unnamed>, Model: <Unnamed>, Dirty: false
// ViewModel: MobX, Model: <Unnamed>, Dirty: true
// About to submit...
// ViewModel: MobX, Model: MobX, Dirty: false
复制代码

Mobx Internals

  • The layered architecture of MobX
  • Atoms and ObservableValues
  • Derivations and reactions
  • What is Transparent Functional Reactive Programming?

Atoms => ObservableValue, ComputedValue and Derivations, Observable{Object, Array, Map} and APIs

  • Atoms: Atoms are the foundation of MobX observables. As the name suggests, they are the atomic pieces of the observable dependency tree. It keeps track of its observers but does not actually store any value.
  • ObservableValue, ComputedValue, and Derivations: ObservableValue extends Atom and provides the actual storage. It is also the core implementation of boxed Observables. In parallel, we have derivations and reactions, which are the observers of the atoms. They respond to changes in atoms and schedule reactions. ComputedValue builds upon the derivations and also acts as an observable.
  • Observable{Object, Array, Map} and APIs: These data structures buildon top of Observable Value and use it to represent their properties and values. This also acts as the API layer of MobX, the primary means of interfacing with the library from the consumer's standpoint.

The reactive system of MobX is backed by a graph of dependencies that exist between the observables. One observable's value could depend on a set of observables, which in turn could depend on other observables.

An atom serves two purposes:

  • Notify when it is read. This is done by calling reportObserved()
  • Notify when it is changed. This is done by calling reportChanged()

Internally, an atom keeps track of its observers and informs them of the changes.

class Atom {
    observers = [];

    reportObserved() {}
    reportChanged() {}

    /* ... */
}
复制代码
import { autorun, $mobx, getDependencyTree } from "mobx";
const cart = new ShoppingCart();
const disposer = autorun(() => {
    console.log(cart.description);
});
const descriptionAtom = cart[$mobx].values.get("description");
console.log(getDependencyTree(descriptionAtom));

// {
//   name: 'ShoppingCart@16.description',
//     dependencies: [
//       { name: 'ShoppingCart@16.items' },
//       { name: 'ShoppingCart@16.items' },
//       {
//         name: 'ShoppingCart@16.coupons',
//         dependencies: [
//           {
//             name: 'CouponManager@19.validCoupons',dependencies: [{ name: 'CouponManager@19.coupons' }],
//           },
//         ],
//     },
//   ],
// };
复制代码

createAtom(name, onBecomeObservedHandler, onBecomeUnobservedHandler)

class ComputedValue {
    get() {
        /* ... */
        reportObserved(this);
        /* ... */
    }
    set(value) {
        /* rarely applicable */
    }
    observe(listener, fireImmediately) {}
}
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Build web applications with MongoDB, ExpressJS, React, and Node Key Features Build applications with the MERN stack Work with each component of the MERN stack Become confident with MERN and ready for more! Book Description The MERN stack is a collection of great tools—MongoDB, Express.js, React, and Node—that provide a strong base for a developer to build easily maintainable web applications. With each of them a JavaScript or JavaScript-based technology, having a shared programming language means it takes less time to develop web applications. This book focuses on providing key tasks that can help you get started, learn, understand, and build full-stack web applications. It walks you through the process of installing all the requirements and project setup to build client-side React web applications, managing synchronous and asynchronous data flows with Redux, and building real-time web applications with Socket.IO, RESTful APIs, and other concepts. This book gives you practical and clear hands-on experience so you can begin building a full-stack MERN web application. Quick Start Guides are focused, shorter titles that provide a faster paced introduction to a technology. They are for people who don't need all the detail at this point in their learning curve. The presentation has been streamlined to concentrate on the things you really need to know. What you will learn Get started with the MERN stack Install Node.js and configure MongoDB Build RESTful APIs with Express.js and Mongoose Build real-time applications with Socket.IO Manage synchronous and asynchronous data flows with Redux Build web applications with React Who this book is for The book is for JavaScript developers who want to get stated with the MERN Stack. Table of Contents Introduction to MERN stack Building a Web Server with ExpressJS Building a RESTful API Building a Live Query API server Managing Synchronous and Asynchronous data flow with Redux Building Web Applications with React

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值