MobX Tips

1,068 阅读3分钟

运行机制

1、单向数据流

2、带副作用(比如网络请求,日志等)的数据流

3、由于UI和副作用处理器干的事情很像,它们都会接收状态的通知,最后通过Actions来变更状态,所以可以认为它们都是观察者,它们在观察状态,一旦状态发生了改变,它们会做出一些相应的响应最后简化图为:

概念 简述 通常指代对象
observables 状态 是可以被观察到变化的对象
observers 观察者 UI和副作用
actions 事件行为 发送的消息、通知等

跨组件交互

1、在父组件创建store,然后分别通过属性传入子组件中

<div>
    <Main store={newState} />
    <AllNum store={newState} />
</div>

2、那如果组件树比较深怎么办呢? React15版本的新特性context,它可以将父组件中的值传递到任意层级深度的子组件中。

@observer

1、被修饰的子组件会按需触发渲染次数

@observable

1、React组件中可以直接添加@observable修饰的变量

autorun

1、状态发生变化时自动执行,使用其返回值函数可以将其注销掉数。不在autorun内被监听的状态发生变化时,不会导致autorun的触发

import {
    observable, action, computed, autorun,
} from 'mobx';

export default class CycleLoanWithdrawStore {
    @observable testV = 0;

    @action testValue() {
        this.testV += 1;
        // this.currentLoanPeriod += 1;
    }

    constructor() {
        autorun(() => console.log(this.testV)); 
    }
}    

2、同样可以放到@observer的UI的constructor中

import {autorun} from "mobx";

@observer
export default class XXComponent extends React.Component {

    constructor(props) {
        super(props);

        this.store = new XXComponentStore();
        
        autorun(() => console.log(this.store.testV));
    }

observers的reactions(autorun、reaction、when)使用判断

  • 判断副作用是否需要执行多次,只需要执行一次使用when
  • 需要执行多次,每次依赖更新都要执行就使用autorun
  • 需要执行多次,需要对依赖做进一步处理再决定做不做后续的处理则用reaction

ReactNative中MobX的实践示例

MobX with React Native, Simplified

mobx-react 渲染性能优化原理

  • 该文有说道使用mobx后,mobx会为react组件提供一个精确的、细粒度的shouldComponentUpdate函数,所以理论上不在需要手动处理shouldComponentUpdate函数
  • mobx提供的transaction功能,能够将多个observables的变更打包成一个事务,减少渲染次数【建议直接使用action,因为action内部就是使用transaction的
import {observable, action, computed, autorun, transaction} from 'mobx';

export default class TestStore {
    constructor() {
        autorun(() => {
            console.log(this.numbers.length, 'numbers!'); 
            // 只会打印一次,最终的结果: 3 "numbers!"
        });
    }

    @observable numbers = [];

    change() { // 未使用action时
        transaction(() => {
            this.numbers.push(1);
            this.numbers.push(2);
            transaction(() => {
                this.numbers.push(3);
            })
        })
    }
}

...

// 在检测组件中调用TestStore的change方法

runInAction 使用例子

// 监测响应组件
@observer
export default class TestComponet extends React.Component {
    constructor(props) {
        super(props);
        this.store = new TestStore();
    }

    render() {
        console.log('render');
        return (
            <View style={{
                flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: 'red',
            }}>
                <Text style={{ fontSize: 20 }} >{this.store.firstName}</Text>
                <Text style={{ fontSize: 20 }} >{this.store.lastName}</Text>
                <Text style={{ fontSize: 15, borderWidth: 1, borderColor: 'black' }}
                    onPress={() => this.store.change()}
                >
                    change btn
                </Text>
            </View>
        );
    }
}
// store observable 组件
import {observable, action, computed, runInAction} from 'mobx';

export default class TestStore {
    @observable firstName = '';
    @observable lastName = '';

    @action async change() {
        const res = await new Promise((resolve, reject) => {
            setTimeout(() => resolve({ code: '200100' }), 2000);
        });
        console.log('start - setTimeout');
        setTimeout(() => {
        
        // 这里会触发检测组件的render函数2次
        // this.firstName = res.code;
        // this.lastName = `测试用${(Math.random() * 1000).toFixed(2)} `;
        
        // log:
        // start - setTimeout
        // render
        // render
        // end - setTimeout
        
        
        // 只会触发检测组件的render函数1次
            runInAction(() => {
                this.firstName = res.code;
                this.lastName = `测试用${(Math.random() * 1000).toFixed(2)} `;
            })
            console.log('end - setTimeout');
        }, 2000);
        
        // log:
        // start - setTimeout
        // render
        // end - setTimeout
    }
}
  • 异步后的回调同样会多次触发render,失去action原有transaction效果
    // ...
    @action async change() {
        const res = await new Promise((resolve, reject) => {
            setTimeout(() => resolve({ code: '200100' }), 2000);
        });
        console.log('after await');
        this.firstName = res.code;
        this.lastName = `测试用${(Math.random() * 1000).toFixed(2)} `;

        console.log('end change');
    }
    // ...
    // log:
    // after await
    // render
    // render
    // end change
  • runInAction后的恢复action原有transaction效果
    // ...
    @action async change() {
        const res = await new Promise((resolve, reject) => {
            setTimeout(() => resolve({ code: '200100' }), 2000);
        });
        console.log('after await');
        runInAction(() => {
            this.firstName = res.code;
            this.lastName = `测试用${(Math.random() * 1000).toFixed(2)} `;
        });
        console.log('end change');
    }
    // ...
    // log:
    // after await
    // render
    // end change