高效的Mobx形式(Part 3 高阶运用实例)

前两部份侧重于MobX的基础构建块。 有了这些块,我们如今能够经由过程MobX的角度最先处置惩罚一些实在场景。 这篇文章将是一系列运用我们迄今为止所见观点的例子。

固然,这不是一个详实的清单,但应当让你体会到运用MobX角度所须要的心思转变。 一切示例都是在没有@decorator (装潢器)语法的情况下竖立的。 这许可您在Chrome控制台,Node REPL或支撑临时文件的WebStorm等IDE中举行尝试。

转变头脑体式格局

当您进修某些库或框架背地的理论并尝试将其运用于您自身的题目时,您最初能够会画一个空缺。 它发作在像我如许的普通人身上,以至是最好的人。 写作界称之为“Writer’s block”,而在艺术家的天下里,它就是“Painter’s block”

我们须要的是从简朴到庞杂的例子来塑造我们的头脑体式格局。 只要看到运用程序,我们才最先设想处置惩罚我们自身的题目的要领。

关于MobX,它首先要相识您有一个reactive object-graph这一实际。 树的某些部份能够依赖于其他部份。 当树变异时,衔接的部份将作出回响反映并更新以回响反映变化。

头脑体式格局的转变是将手头的体系设想为一组回响反映性变动
+ 一组相应的结果。

结果能够是由于回响反映性变化而发作输出的任何事物。 让我们探究种种实际天下的例子,看看我们如何用MobX建模和表达它们。

Example 1: 发送主要操纵的剖析

题目:我们在运用程序中有一些必需纪录到服务器的一次性操纵。 我们愿望跟踪实行这些操纵的时候并发送剖析。

1、是对竖立状况模子。 我们的行为是有限的,我们只体贴它实行一次。 我们能够运用行为要领的称号竖立对应的布尔值范例状况, 这是我们可视察到的状况。

const actionMap = observable({
    login: false,
    logout: false,
    forgotPassword: false,
    changePassword: false,
    loginFailed: false
});

2、接下来,我们必需对这些行为状况发作的变化作出回响反映。 由于它们只在生命周期中发作过一次,所以我们不会运用历久运转的结果,如autorun()reaction()。 我们也不愿望这些结果在实行后存在。 好吧,这给我们留下了一个挑选:….
….
….

when

Object.keys(actionMap)
    .forEach(key => {
        when(
            () => actionMap[key],
            () => reportAnalyticsForAction(key)
        );
    });

function reportAnalyticsForAction(actionName) {
    console.log('Reporting: ', actionName);

    /* ... JSON API Request ... */
}

在上面的代码中,我们只是轮回遍历actionMap中的键并为每一个键设置when()副作用。 当tracker-function(第一个参数)返回true时,副作用将运转。 运转结果函数(第二个参数)后,when()将自动处置惩罚。 因而,没有从运用程序发送多个报告的题目!

3、我们还须要一个MobX行为来转变可视察状况。 请记着:永久不要直接修正您的observable。 一直经由过程action来做到这一点。
对上面的例子来讲,以下:

const markActionComplete = action((name) => {
    actionMap[name] = true;
});

markActionComplete('login');
markActionComplete('logout');

markActionComplete('login');

// [LOG] Reporting:  login
// [LOG] Reporting:  logout

请注重,纵然我将登录操纵标记触发两次,也没有发送日记报告。 圆满,这正是我们须要的结果。
它有两个缘由:

  1. login标记已为true,因而值没有变化
  2. 另外,when()副作用已被触发实行,因而不再发作追踪。

Example 2: 作为事情流程的一部份启动操纵

题目:我们有一个由几个状况构成的事情流程。 每一个状况都映照到某些使命,这些使命在事情流抵达该状况时实行。

1、从上面的形貌中能够看出,唯一可视察的值是事情流的状况。 须要为每一个状况运转的使命能够存储为简朴映照。 有了这个,我们能够模仿我们的事情流程:

class Workflow {
    constructor(taskMap) {
        this.taskMap = taskMap;
        this.state = observable({
            previous: null,
            next: null
        });

        this.transitionTo = action((name) => {
            this.state.previous = this.state.next;
            this.state.next = name;
        });

        this.monitorWorkflow();
    }

    monitorWorkflow() {
        /* ... */
    }
}

// Usage
const workflow = new Workflow({
    start() {
        console.log('Running START');
    },

    process(){
        console.log('Running PROCESS');
    },

    approve() {
        console.log('Running APPROVE');
    },

    finalize(workflow) {
        console.log('Running FINALIZE');

        setTimeout(()=>{
            workflow.transitionTo('end');
        }, 500);
    },

    end() {
        console.log('Running END');
    }
});

请注重,我们正在存储一个名为state的实例变量,该变量跟踪事情流的当前和先前状况。 我们还通报state->task的映照,存储为taskMap

2如今风趣的部份是关于监控事情流程。 在这类情况下,我们没有像前一个例子那样的一次性操纵。 事情流通常是长时候运转的,能够在运用程序的生命周期内。 这须要autorunreaction()

只要在转换到状况时才会实行状况使命。 因而我们须要守候对this.state.next举行变动才运转任何副作用(使命)。 守候变动示意运用reaction()由于它仅在跟踪的可视察值变动值时才会运转。 所以我们的监控代码以下所示:

class Workflow {
    /* ... */
    monitorWorkflow() {
        reaction(
            () => this.state.next,
            (nextState) => {
                const task = this.taskMap[nextState];
                if (task) {
                    task(this);
                }
            }
        )
    }
}

reaction()第一个参数是跟踪函数,在这类情况下只返回this.state.next。 当跟踪功用的返回值转变时,它将触发结果功用。 结果函数检察当前状况,从this.taskMap查找使命并简朴地挪用它。

请注重,我们还将事情流的实例通报给使命。 这可用于将事情流转换为其他状况。

workflow.transitionTo('start');

workflow.transitionTo('finalize');

// [LOG] Running START
// [LOG] Running FINALIZE
/* ... after 500ms ... */
// [LOG] Running END

风趣的是,这类存储一个简朴的observable的手艺,比方this.state.next和运用reaction()来触发副作用,也能够用于:

  1. 经由过程react-router举行路由
  2. 在演示运用程序中导航
  3. 基于形式在差别视图之间切换

Example 3: 输入变动时实行表单考证

题目:这是一个典范的Web表单用例,您须要考证一堆输入。 假如有用,许可提交表单。

1、让我们用一个简朴的表单数据类对其举行建模,其字段必需经由考证。

class FormData {
    constructor() {
        extendObservable(this, {
            firstName: '',
            lastName: '',
            email: '',
            acceptTerms: false,

            errors: {},

            get valid() { // this becomes a computed() property
                return (this.errors === null);
            }
        });

        this.setupValidation(); // We will look at this below
    }
}

extendObservable()API是我们之前从未见过的。 经由过程在我们的类实例(this)上运用它,我们获得一个ES5相当于竖立一个@observable类属性。

class FormData {
    @observable firstName = '';
    /* ... */
}

2、接下来,我们须要看管这些字段什么时候发作变化并运转一些考证逻辑。 假如考证经由过程,我们能够将实体标记为有用并许可提交。 运用盘算属性跟踪有用性自身:有用。

由于考证逻辑须要在FormData的生命周期内运转,因而我们将运用autorun()。 我们也能够运用reaction()但我们想马上运转考证而不是守候第一次变动。

class FormData {
    setupValidation() {
        autorun(() => {
            // Dereferencing observables for tracking
            const {firstName, lastName, email, acceptTerms} = this;
            const props = {
                firstName,
                lastName,
                email,
                acceptTerms
            };

            this.runValidation(props, {/* ... */})
                .then(result => {
                    this.errors = result;
                })
        });
    }

    runValidation(propertyMap, rules) {
        return new Promise((resolve) => {
            const {firstName, lastName, email, acceptTerms} = propertyMap;

            const isValid = (firstName !== '' && lastName !== '' && email !== '' && acceptTerms === true);
            resolve(isValid ? null : {/* ... map of errors ... */});
        });
    }

}

在上面的代码中,autorun()将在跟踪的observables发作变动时自动触发。 请注重,要使MobX准确跟踪您的observable,您必需运用消除援用。

runValidation()是一个异步挪用,这就是我们返回一个promise的缘由。 在上面的示例中,它并不主要,但在实际天下中,您能够会挪用服务器举行一些特别考证。 当结果返回时,我们将设置毛病observable,这将反过来更新有用的盘算属性。

假如你有一个耗时较大的考证逻辑,你以至能够运用autorunAsync(),它有一个参数能够耽误实行去发抖。

2、好吧,让我们的代码付诸行为。 我们将设置一个简朴的控制台纪录器(经由过程autorun())并跟踪有用的盘算属性。

const instance = new FormData();

// Simple console logger
autorun(() => {
    // input的每一次输入,结果都邑触发error变动,autorun随即实行
    const validation = instance.errors;

    console.log(`Valid = ${instance.valid}`);
    if (instance.valid) {
        console.log('--- Form Submitted ---');
    }

});

// Let's change the fields
instance.firstName = 'Pavan';
instance.lastName = 'Podila';
instance.email = 'pavan@pixelingene.com';
instance.acceptTerms = true;

//     输出日记以下
//     Valid = false
//    Valid = false
//    Valid = false
//    Valid = false
//    Valid = false
//    Valid = true
//    --- Form Submitted ---

由于autonrun()马上运转,您将在开首看到两个分外的日记,一个用于instance.errors,一个用于instance.valid,第1-2行。 其他四行(3-6)用于现场的每次变动。

每一个字段变动都邑触发runValidation(),每次都邑在内部返回一个新的毛病对象。 这会致使instance.errors的援用发作变动,然后触发我们的autorun()以纪录有用标志。 末了,当我们设置了一切字段时,instance.errors变成null(再次变动援用)并纪录终究的“Valid = true”。

4、简而言之,我们经由过程使表单字段可视察来举行表单考证。 我们还添加了分外的errors属性和有用的盘算属性来跟踪有用性。 autorun()经由过程将一切内容绑缚在一起来节省时候。

Example 4: 跟踪一切已注册的组件是不是已加载

题目: 我们有一组已注册的组件,我们愿望在一切组件都加载后跟踪。 每一个组件都将公然一个返回 promise的load()要领。 假如promise剖析,我们将组件标记为已加载。 假如它谢绝,我们将其标记为失利。 当一切这些都完成加载时,我们将报告全部集是不是已加载或失利。

1、我们先来看看我们正在处置惩罚的组件。 我们正在竖立一组随机报告其负载状况的组件。 另请注重,有些是异步的。

const components = [
    {
        name: 'first',
        load() {
            return new Promise((resolve, reject) => {
                Math.random() > 0.5 ? resolve(true) : reject(false);
            });
        }
    },
    {
        name: 'second',
        load() {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    Math.random() > 0.5 ? resolve(true) : reject(false);
                }, 1000);
            });
        }
    },
    {
        name: 'third',
        load() {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    Math.random() > 0.25 ? resolve(true) : reject(false);
                }, 500);
            });
        }
    },
];

2、下一步是为Tracker设想可视察状况。 组件的load()不会按特定递次完成。 所以我们须要一个可视察的数组来存储每一个组件的加载状况。 我们还将跟踪每一个组件的报告状况。

当一切组件都已报告时,我们能够关照组件集的终究加载状况。 以下代码设置了可视察量。

class Tracker {
    constructor(components) {
        this.components = components;

        extendObservable(this, {

            // Create an observable array of state objects,
            // one per component
            states: components.map(({name}) => {
                return {
                    name,
                    reported: false,
                    loaded: undefined
                };
            }),

            // computed property that derives if all components have reported
            get reported() {
                return this.states.reduce((flag, state) => {
                    return flag && state.reported;
                }, true);
            },

            // computed property that derives the final loaded state 
            // of all components
            get loaded() {
                return this.states.reduce((flag, state) => {
                    return flag && !!state.loaded;
                }, true);
            },

            // An action method to mark reported + loaded
            mark: action((name, loaded) => {
                const state = this.states.find(state => state.name === name);

                state.reported = true;
                state.loaded = loaded;
            })

        });

    }
}

我们回到运用extendObservable()来设置我们的可视察状况。 reportedload的盘算属性跟踪组件完成其加载的时候。 mark()是我们转变可视察状况的行为要领。

趁便说一句,发起在须要从您的observables派生值的任何地方运用computed。 将其视为发作代价的可视察物。 盘算值也会被缓存,从而进步机能。 另一方面,autorunreaction不会发作代价。 相反,它们供应了竖立副作用的敕令层。

3、为了启动跟踪,我们将在Tracker上竖立一个track()要领。 这将触发每一个组件的load()并守候返回的Promise剖析/谢绝。 基于此,它将标记组件的负载状况。

when()一切组件都已reported时,跟踪器能够报告终究加载的状况。 我们在这里运用,由于我们正在守候前提变成真(this.reported)。 报告的副作用只须要发作一次,异常合适when()

以下代码担任以上事项:

class Tracker {
    /* ... */ 
    track(done) {
        when(
            () => this.reported,
            () => {
                done(this.loaded);
            }
        );

        this.components.forEach(({name, load}) => {
            load()
                .then(() => {
                    this.mark(name, true);
                })
                .catch(() => {
                    this.mark(name, false);
                });
        });
    }

    setupLogger() {
        autorun(() => {
            const loaded = this.states.map(({name, loaded}) => {
                return `${name}: ${loaded}`;
            });

            console.log(loaded.join(', '));
        });
    }
}

setupLogger()实际上不是处置惩罚方案的一部份,但用于纪录报告。 这是相识我们的处置惩罚方案是不是有用的好要领。

4、如今我们来测试一下:

const t = new Tracker(components);
t.setupLogger();
t.track((loaded) => {
    console.log('All Components Loaded = ', loaded);
});

// first: undefined, second: undefined, third: undefined
// first: true, second: undefined, third: undefined
// first: true, second: undefined, third: true
// All Components Loaded =  false
// first: true, second: false, third: true

纪录的输出显现其按预期事情。 在组件报告时,我们纪录每一个组件的当前加载状况。 当一切人报告时,this.reported变成true,我们看到“All Components Loaded”音讯。

愿望上面的一些例子让你体会到在MobX中的思索。

  1. 设想可视察状况
  2. 设置变异行为要领以变动可视察状况
  3. 放入跟踪功用(when,autorun,reaction)以相应可视察状况的变化

上述公式应当适用于须要在发作变化后跟踪某些内容的庞杂场景,这能够致使反复1-3步骤。

《高效的Mobx形式(Part 3 高阶运用实例)》

    原文作者:hsgshh5259
    原文地址: https://segmentfault.com/a/1190000016298558
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞