美文网首页
React Native 中使用Mobx实践

React Native 中使用Mobx实践

作者: RmondJone | 来源:发表于2019-10-15 18:36 被阅读0次

    一、为什么我们需要使用Mobx?

    • Mobx 是 flux 实现的后起之秀. 以更简单的时候和更少的概念, 让 flux 使用起来变得更简单.
    • 相比 Redux 有mutation, action, dispatch 等概念. Mobx则更加简洁, 更符合对Store 增删改查的操作概念.
    • Mobx是学习成本更低,性能更好的状态解决方案。
    • Mobx代码量少,与TS结合性更好,可以使用更少的代码实现更复杂的页面
    • 使用好Mobx可以极大的降低页面的不必要的重绘次数

    二、React Native工程集成Mobx以及TS环境的搭建

    (1) 安装Mobx、Mobx-React

     npm i mobx mobx-react
    

    (2)安装TS

    npm i typescript
    

    (3)安装TS BaBle解析器

    在package.json中输入如下依赖,并重新执行npm install

    "devDependencies": {
        "@babel/core": "^7.5.5",
        "@babel/plugin-proposal-decorators": "^7.4.4",
        "@babel/preset-typescript": "^7.3.3",
        "@babel/runtime": "^7.5.5",
        "@types/react-dom": "^16.8.5",
        "@types/react": "^16.9.2",
        "@types/react-native": "^0.60.7",
        "babel-jest": "^24.9.0",
        "babel-plugin-transform-class-properties": "^6.24.1",
        "jest": "^24.9.0",
        "metro-react-native-babel-preset": "^0.56.0",
        "react-test-renderer": "16.8.6"
      },
    

    (4)配置Bable插件

    在.babelrc文件中应用以下插件脚本,如没有则在工程根目录新建

    {
      "presets": [
        "@babel/preset-typescript",
        [
          "module:metro-react-native-babel-preset"
        ]
      ],
      "plugins": [
        [
          "@babel/plugin-proposal-decorators",
          {
            "legacy": true
          }
        ],
        "transform-class-properties"
      ]
    }
    

    (5)配置TS校验规则

    在tsconfig.json文件中应用以下校验配置,如没有则在工程根目录新建

    {
      "compilerOptions": {
        "target": "es2017",
        "module": "commonjs",
        "jsx": "preserve",
        "strict": true,
        "noImplicitAny": false,
        "moduleResolution": "node",
        "allowSyntheticDefaultImports": true,
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        "esModuleInterop": true,
        "removeComments": false,
        "resolveJsonModule": true,
        "isolatedModules": true,
        "allowJs": true,
        "checkJs": true
      },
      "exclude": [
        "node_modules"
      ],
      "types": [
        "react",
        "react-dom",
        "react-native"
      ]
    }
    

    具体字段含义参照https://www.html.cn/doc/typescript/doc/handbook/tsconfig.json.html

    三、Mobx中一些常用的概念

    (1)observable和autorun

    import { observable, autorun } from 'mobx';
    
    const value = observable(0);
    const number = observable(100);
    
    autorun(() => {
      console.log(value.get());
    });
    
    value.set(1);
    value.set(2);
    number.set(101);
    
    

    可以看到,控制台中依次输出0,1,2。
    observable可以用来观测一个数据,这个数据可以数字、字符串、数组、对象等类型(相关知识点具体会在后文中详述),而当观测到的数据发生变化的时候,如果变化的值处在autorun中,那么autorun就会自动执行。
    上例中的autorun函数中,只对value值进行了操作,而并没有number值的什么事儿,所以number.set(101)这步并不会触发autorun,只有value的变化才触发了autorun。

    (2)计算属性——computed

    假如现在我们一个数字,但我们对它的值不感兴趣,而只关心这个数组是否为正数。这个时候我们就可以用到computed这个属性了。

    const number = observable(10);
    const plus = computed(() => number.get() > 0);
    
    autorun(() => {
      console.log(plus.get());
    });
    
    number.set(-19);
    number.set(-1);
    number.set(1);
    

    依次输出了true,false,true。
    第一个true是number初始化值的时候,10>0为true没有问题。
    第二个false将number改变为-19,输出false,也没有问题。
    但是当-19改变为-1的时候,虽然number变了,但是number的改变实际上并没有改变plus的值,所以没有其它地方收到通知,因此也就并没有输出任何值。
    直到number重新变为1时才输出true。

    实际项目中,computed会被广泛使用到。

    (3)action,runInAction

    mobx推荐将修改被观测变量的行为放在action中。
    来看看以下例子:

    import {observable, action} from 'mobx';
    class Store {
      @observable number = 0;
      @action add = () => {
        this.number++;
      }
    }
    
    const newStore = new Store();
    newStore.add();
    

    以上例子使用了ES7的decorator,在实际开发中非常建议用上它,它可以给你带来更多的便捷

    接下来说一个重点action只能影响正在运行的函数,而无法影响当前函数调用的异步操作
    比如官网中给了如下例子

    @action createRandomContact() {
      this.pendingRequestCount++;
      superagent
        .get('https://randomuser.me/api/')
        .set('Accept', 'application/json')
        .end(action("createRandomContact-callback", (error, results) => {
          if (error)
            console.error(error);
          else {
            const data = JSON.parse(results.text).results[0];
            const contact = new Contact(this, data.dob, data.name, data.login.username, data.picture);
            contact.addTag('random-user');
            this.contacts.push(contact);
            this.pendingRequestCount--;
          }
      }));
    }
    

    重点关注程序的第六行。在end中触发的回调函数,被action给包裹了,这就很好验证了上面加粗的那句话,action无法影响当前函数调用的异步操作,而这个回调毫无疑问是一个异步操作,所以必须再用一个action来包裹住它,这样程序才不会报错。

    如果你使用async function来处理业务,那么我们可以使用runInAction这个API来解决之前的问题。

    import {observable, action, useStrict, runInAction} from 'mobx';
    useStrict(true);
    
    class Store {
      @observable name = '';
      @action load = async () => {
        const data = await getData();
        runInAction(() => {
          this.name = data.name;
        });
      }
    }
    

    四、结合React Native 使用

    在React中,我们一般会把和页面相关的数据放到state中,在需要改变这些数据的时候,我们会去用setState这个方法来进行改变。
    先设想一个最简单的场景,页面上有个数字0和一个按钮。点击按钮我要让这个数字增加1,就让我们要用Mobx来处理这个试试。

    import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
    import React, {PureComponent} from 'react'
    import {observer} from 'mobx-react'
    import {action, observable} from 'mobx'
    
    
    class MyState {
        @observable num = 1
    
        @action
        public addNum() {
            this.num++
        }
    }
    
    const state = new MyState()
    
    interface Props {
    
    }
    
    interface State {
        
    }
    
    /**
     * 注释:
     * 时间: 2019/8/21 0021 11:52
     * @author 郭翰林
     */
    @observer
    export default class App extends PureComponent<Props, State> {
    
        static propTypes = {}
    
        constructor(props) {
            super(props)
        }
    
        render() {
            return (
                <View style={{flex: 1, justifyContent: "space-between", alignItems: "center"}}>
                    <View>
                        <Text style={{color: 'red', fontSize: 18, fontWeight: "bold"}}>
                            {state.num}
                        </Text>
                    </View>
                    <TouchableOpacity
                        style={styles.buttonStyle}
                        onPress={() => {
                            state.addNum()
                        }}>
                        <Text style={{color: '#ffffff', fontSize: 14}}>
                            增加
                        </Text>
                    </TouchableOpacity>
                </View>
            )
        }
    
    }
    
    const styles = StyleSheet.create({
        buttonStyle: {
            justifyContent: "center",
            alignItems: "center",
            width: 250,
            borderRadius: 8,
            height: 50,
            marginBottom: 25,
            backgroundColor: '#fc704e'
        }
    })
    

    上例中我们使用了一个MyState类,在这个类中定义了一个被观测的num变量和一个action函数addNum来改变这个num值。
    之后我们实例化一个对象,叫做newState,之后在我的React组件中,我只需要用@observer修饰一下组件类,便可以愉悦地使用这个newState对象中的值和函数了。

    跨组件交互

    在不使用其它框架、类库的情况下,React要实现跨组件交互这一功能相对有些繁琐。通常我们需要在父组件上定义一个state和一个修改该state的函数。然后把state和这个函数分别传到两个子组件里,在逻辑简单,且子组件很少的时候可能还好,但当业务复杂起来后,这么写就非常繁琐,且难以维护。而用Mobx就可以很好地解决这个问题。来看看以下的例子:

    class MyState {
      @observable num1 = 0;
      @observable num2 = 100;
    
      @action addNum1 = () => {
        this.num1 ++;
      };
      @action addNum2 = () => {
        this.num2 ++;
      };
      @computed get total() {
        return this.num1 + this.num2;
      }
    }
    
    const newState = new MyState();
    
    const AllNum = observer((props) => <div>num1 + num2 = {props.store.total}</div>);
    
    const Main = observer((props) => (
      <div>
        <p>num1 = {props.store.num1}</p>
        <p>num2 = {props.store.num2}</p>
        <div>
          <button onClick={props.store.addNum1}>num1 + 1</button>
          <button onClick={props.store.addNum2}>num2 + 1</button>
        </div>
      </div>
    ));
    
    @observer
    export default class App extends React.Component {
    
      render() {
        return (
          <div>
            <Main store={newState} />
            <AllNum store={newState} />
          </div>
        );
      }
    }
    

    有两个子组件,Main和AllNum (均采用无状态函数的方式声明的组件)
    在MyState中存放了这些组件要用到的所有状态和函数。
    之后只要在父组件需要的地方实例化一个MyState对象,需要用到数据的子组件,只需要将这个实例化的对象通过props传下去就好了。

    那如果组件树比较深怎么办呢?
    查看最新Mobx5的@inject属性

    关于@observer的一些说明

    通常,在和Mobx数据有关联的时候,你需要给你的React组件加上@observer,你不必太担心性能上的问题,加上这个@observer不会对性能产生太大的影响,而且@observer还有一个类似于pure render的功能,甚至能起到性能上的一些优化。

    所谓pure render见下例:

    @observer
    export default class App extends React.Component {
      state = {
        a: 0,
      };
      add = () => {
        this.setState({
          a: this.state.a + 1
        });
      };
      render() {
        return (
          <div>
            {this.state.a}
            <button onClick={this.add}>+1</button>
            <PureItem />
          </div>
        );
      }
    }
    
    @observer
    class PureItem extends React.Component {
    
      render() {
        console.log('PureItem的render触发了');
        return (
          <div>你们的事情跟我没关系</div>
        );
      }
    }
    

    如果去掉子组件的@observer,按钮每次点击,控制台都会输出 PureItem的render触发了 这句话。

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

    @observer
    class MyComponent extends React.Component {
      
      state = { a: 0 };
    
      @observable b = 1;
    
      render() {
        return(
          <div>
           {this.state.a}
           {this.b}
          </div>
        )
      }
    }
    

    在添加@observer后,你的组件会多一个生命周期componentWillReact。当组件内被observable观测的数据改变后,就会触发这个生命周期。
    注意setState并不会触发这个生命周期!state中的数据和observable数据并不算是一类。

    另外被observable观测数据的修改是同步的,不像setState那样是异步,这点给我们带了很大便利。

    五、总结

    Mobx想要入门上手可以说非常简单,只需要记住少量概念并可以完成许多基础业务了。但深入学习下去,也还是要接触许多概念的。例如Modifier、Transation等等。
    最后与Redux做一个简单的对比

    1、Mobx写法上更偏向于OOP
    2、对一份数据直接进行修改操作,不需要始终返回一个新的数据
    3、对typescript的支持更好一些
    4、相关的中间件很少,逻辑层业务整合是一个问题

    相关文章

      网友评论

          本文标题:React Native 中使用Mobx实践

          本文链接:https://www.haomeiwen.com/subject/areoectx.html