美文网首页GoCoding
React MobX 开始

React MobX 开始

作者: GoCodingInMyWay | 来源:发表于2021-12-28 19:12 被阅读0次

    MobX 用于状态管理,简单高效。本文将于 React 上介绍如何开始,包括了:

    • 了解 MobX 概念
    • 从零准备 React 应用
    • MobX React.FC 写法
    • MobX React.Component 写法

    可以在线体验: https://ikuokuo.github.io/start-react ,代码见: https://github.com/ikuokuo/start-react

    概念

    首先,ui 是由 state 通过 fn 生成:

    ui = fn(state)
    

    在 React 里, fn 即组件,依照自己的 state 渲染。

    如果 state 是共享的,一处状态更新,多处组件响应呢?这时就可以用 MobX 了。

    MobX 数据流向如下:

          ui
        ↙    ↖
    action → state
    

    ui 触发 action,更新 state,重绘 ui。注意是单向的。

    了解更多,请阅读 MobX 主旨 。这里讲下实现时的主要步骤:

    • 定义数据存储类 Data Store
      • 成员属性为 state,成员函数为 action
      • mobx 标记为 observable
    • 定义 Stores Provider
      • 方式一 React.ContextcreateContext 包装 Store 实例,ui useContext 使用
      • 方式二 mobx-react.Provider:直接包装 Store 实例,提供给 Providerui inject 使用
    • 实现 ui 组件
      • mobx 标记为 observer
      • 获取 stores,直接引用 state
      • 若要更新 state,间接调用 action

    项目结构上就是多个 stores 目录,定义各类 storestate action,异步操作也很简单。了解更多,请阅读:

    准备

    React App

    yarn create react-app start-react --template typescript
    cd start-react
    

    React Router

    路由库,以便导航样例。

    yarn add react-router-dom
    

    Antd

    组件库,以便布局 UI。

    yarn add antd @ant-design/icons
    

    高级配置

    yarn add @craco/craco -D
    yarn add craco-less
    

    craco.config.js 配置了深色主题:

    const path = require('path');
    const CracoLessPlugin = require('craco-less');
    const { getThemeVariables } = require('antd/dist/theme');
    
    module.exports = {
      plugins: [
        {
          plugin: CracoLessPlugin,
          options: {
            lessLoaderOptions: {
              lessOptions: {
                modifyVars: getThemeVariables({
                  dark: true,
                  // compact: true,
                }),
                javascriptEnabled: true,
              },
            },
          },
        },
      ],
      webpack: {
        alias: { '@': path.resolve(__dirname, './src') },
      },
    };
    

    ESLint

    VSCode 安装 ESLint Prettier 扩展。初始化 eslint

    $ npx eslint --init
    ✔ How would you like to use ESLint? · style
    ✔ What type of modules does your project use? · esm
    ✔ Which framework does your project use? · react
    ✔ Does your project use TypeScript? · No / Yes
    ✔ Where does your code run? · browser
    ✔ How would you like to define a style for your project? · guide
    ✔ Which style guide do you want to follow? · airbnb
    ✔ What format do you want your config file to be in? · JavaScript
    

    配置 .eslintrc.js .eslintignore .vscode/settings.json,详见代码。并于 package.json 添加:

    "scripts": {
      "lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern node_modules/"
    },
    

    执行 yarn lint 通过, yarn start 运行。

    到此, React Antd 应用就准备好了。初始模板如下,可见首个提交:

    image

    MobX

    yarn add mobx mobx-react
    

    mobx-react 包含了 mobx-react-lite,所以不必安装了。

    • 如果只用 React.FC (HOOK) 时,用 mobx-react-lite 即可。
    • 如果要用 React.Component (Class) 时,用 mobx-react 才行。

    mobx-react-lite 与 React.FC

    定义 Data Stores

    makeAutoObservable

    定义数据存储模型后,于构造函数里调用 makeAutoObservable(this) 即可。

    stores/Counter.ts:

    import { makeAutoObservable } from 'mobx';
    
    class Counter {
      count = 0;
    
      constructor() {
        makeAutoObservable(this);
      }
    
      increase() {
        this.count += 1;
      }
    
      decrease() {
        this.count -= 1;
      }
    }
    
    export default Counter;
    

    React.Context Stores

    React.Context 可以很简单的传递 Stores

    stores/index.ts:

    import React from 'react';
    
    import Counter from './Counter';
    import Themes from './Themes';
    
    const stores = React.createContext({
      counter: new Counter(),
      themes: new Themes(),
    });
    
    export default stores;
    

    创建一个 useStoresHook,简化调用。

    hooks/useStores.ts:

    import React from 'react';
    import stores from '../stores';
    
    const useStores = () => React.useContext(stores);
    
    export default useStores;
    

    Pane 组件,使用 Stores

    组件用 observer 包装,useStores 引用 stores

    Pane.tsx:

    import React from 'react';
    import { Row, Col, Button, Select } from 'antd';
    import { PlusOutlined, MinusOutlined } from '@ant-design/icons';
    import { observer } from 'mobx-react-lite';
    
    import useStores from './hooks/useStores';
    
    type PaneProps = React.HTMLProps<HTMLDivElement> & {
      name?: string;
    }
    
    const Pane: React.FC<PaneProps> = ({ name, ...props }) => {
      const stores = useStores();
    
      return (
        <div {...props}>
          {name && <h2>{name}</h2>}
          <Row align="middle">
            <Col span="4">Count</Col>
            <Col span="4">{stores.counter.count}</Col>
            <Col>
              <Button
                type="text"
                icon={<PlusOutlined />}
                onClick={() => stores.counter.increase()}
              />
              <Button
                type="text"
                icon={<MinusOutlined />}
                onClick={() => stores.counter.decrease()}
              />
            </Col>
          </Row>
          {/* ... */}
        </div>
      );
    };
    
    Pane.defaultProps = { name: undefined };
    
    export default observer(Pane);
    

    mobx-react 与 React.Component

    定义 Data Stores

    makeObservable + decorators

    装饰器在 MobX 6 中放弃了,但还可使用。

    首先,启用装饰器语法TypeScripttsconfig.json 里启用:

    "experimentalDecorators": true,
    "useDefineForClassFields": true,
    

    定义数据存储模型后,于构造函数里调用 makeObservable(this)。在 MobX 6 前不需要,但现在为了装饰器的兼容性必须调用。

    stores/Counter.ts:

    import { makeObservable, observable, action } from 'mobx';
    
    class Counter {
      @observable count = 0;
    
      constructor() {
        makeObservable(this);
      }
    
      @action
      increase() {
        this.count += 1;
      }
    
      @action
      decrease() {
        this.count -= 1;
      }
    }
    
    export default Counter;
    

    Root Stores

    组合多个 Stores

    stores/index.ts:

    import Counter from './Counter';
    import Themes from './Themes';
    
    export interface Stores {
      counter: Counter;
      themes: Themes;
    }
    
    const stores : Stores = {
      counter: new Counter(),
      themes: new Themes(),
    };
    
    export default stores;
    

    父组件,提供 Stores

    父组件添加 mobx-react.Provider,并且属性扩展 stores

    index.tsx:

    import React from 'react';
    import { Provider } from 'mobx-react';
    import stores from './stores';
    
    import Pane from './Pane';
    
    const MobXCLS: React.FC = () => (
      <div>
        <Provider {...stores}>
          <h1>MobX with React.Component</h1>
          <div style={{ display: 'flex' }}>
            <Pane name="Pane 1" style={{ flex: 'auto' }} />
            <Pane name="Pane 2" style={{ flex: 'auto' }} />
          </div>
        </Provider>
      </div>
    );
    
    export default MobXCLS;
    

    Pane 组件,注入 Stores

    组件用 observer 装饰,同时 inject 注入 stores

    Pane.tsx:

    import React from 'react';
    import { Row, Col, Button, Select } from 'antd';
    import { PlusOutlined, MinusOutlined } from '@ant-design/icons';
    import { observer, inject } from 'mobx-react';
    
    import { Stores } from './stores';
    
    type PaneProps = React.HTMLProps<HTMLDivElement> & {
      name?: string;
    };
    
    @inject('counter', 'themes')
    @observer
    class Pane extends React.Component<PaneProps, unknown> {
      get injected() {
        return this.props as (PaneProps & Stores);
      }
    
      render() {
        const { name, ...props } = this.props;
        const { counter, themes } = this.injected;
    
        return (
          <div {...props}>
            {name && <h2>{name}</h2>}
            <Row align="middle">
              <Col span="4">Count</Col>
              <Col span="4">{counter.count}</Col>
              <Col>
                <Button
                  type="text"
                  icon={<PlusOutlined />}
                  onClick={() => counter.increase()}
                />
                <Button
                  type="text"
                  icon={<MinusOutlined />}
                  onClick={() => counter.decrease()}
                />
              </Col>
            </Row>
            <Row align="middle">
              <Col span="4">Theme</Col>
              <Col span="4">{themes.currentTheme}</Col>
              <Col>
                <Select
                  style={{ width: '60px' }}
                  value={themes.currentTheme}
                  showArrow={false}
                  onSelect={(v) => themes.setTheme(v)}
                >
                  {themes.themes.map((t) => (
                    <Select.Option key={t} value={t}>
                      {t}
                    </Select.Option>
                  ))}
                </Select>
              </Col>
            </Row>
          </div>
        );
      }
    }
    
    export default Pane;
    

    最后

    MobX 文档可以浏览一遍,了解有哪些内容。未涉及的核心概念还有 Computeds, Reactions

    其中 MobX and React 一节,详解了于 React 中的用法及注意点,见:React 集成React 优化

    GoCoding 个人实践的经验分享,可关注公众号!

    相关文章

      网友评论

        本文标题:React MobX 开始

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