create-react-app+mobx入門初體驗

1.本文目標

閱讀本文章您可能收穫以下:css

  • 配置修飾器環境
  • 理解響應式編程的概念
  • 正確使用mobx關鍵api達到維護應用程序狀態的目標

2.什麼是修飾器?

Decorator是在 聲明階段 實現類與類成員註解的一種語法。html

說的直白點Decorator就是 添加 或者 修改 類的變量與方法。node

2.1 必定須要修飾器嗎?

在開始使用mobox,咱們還須要糾結一個東西,就是配置環境啓用ES7的修飾器語法,固然,若是你不須要修飾器,能夠跳過這一部分。react

若是是新手的話,建議配置,官方的MobX文檔也說明了,不必定要使用修飾器,若是有人說你必須在MobX中使用decorator,那就不是真的,你也可使用普通函數,以下:webpack

import { decorate, observable } from "mobx";

class Todo {
    id = Math.random();
    title = "";
    finished = false;
}
decorate(Todo, {
    title: observable,
    finished: observable
})
複製代碼

2.2 使用和不使用修飾符的對比

不使用修飾符以下:git

import React, {Componnet} from 'react';
import {observe} from 'mobx-react';

// 沒有使用 修飾器
class Test extends Componnet {
    ...
}

export default observe(Test);
複製代碼

使用修飾符以下:github

import React, {Componnet} from 'react';
import {observe} from 'mobx-react';

// 使用 修飾器
@observe class Test extends Componnet{
    ...
}

export default Test;
複製代碼

以上代碼中,經過observer(App)定義類和@observer class App 裝飾一個組件是同樣的。web

那麼多個修飾符組合到一個組件上是怎樣的呢?。npm

不使用修飾符以下:編程

import React, {Componnet} from 'react';
import {observe, inject} from 'mobx-react';
import {compose} from 'recompose';

// 沒有使用 裝飾器
class Test extends Componnet {
  render() {
    const {foo} = this.props;
  }
}

export default compose(
  observe,
  inject('foo')
)(Test)
複製代碼

使用修飾符以下:

import React, {Componnet} from 'react';
import {observe, inject} from 'mobx-react';

// 使用 修飾器
@inject('foo') @observe
class Test extends Componnet {
  render() {
    const {foo} = this.props;
  }
}

export default Test;
複製代碼

由上能夠看出,若是沒有修飾器的話,須要引入recompose,經過compose將多個修飾符組合到Test上,若是使用修飾符的話,則能夠直接在class Test前進行修飾,如上面代碼中@inject('foo') @observe,二者相比之下,能夠看出經過修飾器來修飾的方式會更加簡潔易懂些。更多詳情,閱讀mobx中文文檔

2.3 使用修飾符的優缺點

使用修飾符的優勢:

  • 樣板文件最小化,聲明式代碼。
  • 易於使用和閱讀。大多數 MobX 用戶都在使用。

使用修飾符的缺點:

  • ES.next 2階段特性。
  • 須要設置和編譯,目前只有 Babel/Typescript 編譯器支持。

3.create-react-app+mobx(裝飾器)

create-react-app 目前尚未內置的裝飾器支持,因此此小結要解決這個問題。

3.1.安裝react-app-rewire相關

安裝 react-app-rewired

npm install react-app-rewired --save-dev
複製代碼

修改 package.json 裏的啓動配置

/* package.json */
"scripts": {
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "test": "node scripts/test.js"
  },
複製代碼

項目根目錄建立一個 config-overrides.js 用於修改默認配置,文件位置以下:

+-- your-project
|   +-- config-overrides.js
|   +-- node_modules
|   +-- package.json
|   +-- public
|   +-- README.md
|   +-- src
複製代碼

3.2.安裝eject

在建立工程項目後,因爲沒有傳統的webpack.config文件首先安裝eject生成自定義配置文件(注意,用eject生成webpack.config後該操做不能回滾,注意備份)

npm i eject
複製代碼

3.3.安裝bable相關

具體配置參考連接

npm install --save-dev @babel/core
npm install --save-dev @babel/plugin-proposal-class-properties
npm install --save-dev @babel/plugin-proposal-decorators
複製代碼

逐條安裝完以上命令以後,package.json,若是在這裏面設置的話,就不要重複新建 .babelrc文件了,不然會報【重複錯誤】

//package.json
"babel": {
    "plugins": [
      [
        "@babel/plugin-proposal-decorators",
        {
          "legacy": true
        }
      ],
      [
        "@babel/plugin-proposal-class-properties",
        {
          "loose": true
        }
      ]
    ],
    "presets": [
      "react-app"
    ]
  }
複製代碼

4.小試牛刀看下是否成功?

4.1.實現過程

安裝上面的步驟,能夠從新啓動項目了

npm start
複製代碼

假設如今有一個父組件Father,一個子組件Child,在父組件中寫被觀察的數據、獲取數據、設置數據、重置數據的方法,父組件代碼以下:

import React, {Component} from 'react';
// 引入 mobx
import {observable, computed, action} from "mobx";
// 引入子組件
import Child from "./Child.js";

class VM {
  @observable firstName = "";
  @observable lastName = "";

  @computed
  get fullName() {
    const {firstName, lastName} = this;
    if (!firstName && !lastName) {
      return "Please input your name!";
    } else {
      return firstName + " " + lastName;
    }
  }

  @action.bound
  setValue(key, event) {
    this[key] = event.target.value;
  }

  @action.bound
  doReset() {
    this.firstName = "";
    this.lastName = "";
  }
}

const vm = new VM();

export default class Father extends Component {

  render() {
    return (
     <Child vm={vm}/> ) } } 複製代碼

給子組件一個修飾符@observer 子組件代碼以下

import React, {Component} from 'react';
import {observer} from "mobx-react";


@observer
class Upload extends Component {
    render(){
        // 解構從父組件傳來的數據
        const {vm} = this.props;
        return(
            <div>
              <h1>This is mobx-react!</h1>
              <p>
                First name:{" "}
                <textarea
                  type="text"
                  value={vm.firstName}
                  onChange={e => vm.setValue("firstName", e)}
                />
              </p>
              <p>
                Last name:{" "}
                <textarea
                  type="text"
                  value={vm.lastName}
                  onChange={e => vm.setValue("lastName", e)}
                />
              </p>
              <p>Full name: {vm.fullName}</p>
              <p>
                <button onClick={vm.doReset}>Reset</button>
              </p>
            </div>
        )        
    }
}
複製代碼

4.2.效果

tab2.gif

5.mobx經常使用api

5.1.可觀察的數據

observable

observable:一種讓數據的變化能夠被觀察的方法

哪些數據能夠被觀察?
JS基本數據類型、引用類型、普通對象、類實例、數組和映射

以下代碼:

const arr = observable(['a', 'b', 'c']);
const map = observable(new Map());
const obj = observable({
  a: 1,
  b: 1
});
複製代碼

observable.box

observable.box:包裝數值、布爾值、字符串

以下代碼:

let num = observable.box(20);
let str = observable.box('hello');
let bool = observable.box(true);

// 使用set()設置數值
num.set(50);

// get()獲取原生數值
console.log(num.get(), str, bool);
// 50
// ObservableValue$$1 {name: "ObservableValue@5", isPendingUnobservation: false, isBeingObserved: false, observers: Set(0), diffValue: 0, …}
// ObservableValue$$1 {name: "ObservableValue@6", isPendingUnobservation: false, isBeingObserved: false, observers: Set(0), diffValue: 0, …}

複製代碼

使用修飾器

不使用修飾器是否是相對麻煩呢?,還要時刻記着變量的類型,而使用修飾器的話,內部會作一些變量判斷轉換,寫法也更加的簡潔。代碼以下:

class Store {
  @observable array = [];
  @observable obj = {};
  @observable map = new Map();

  @observable string = 'hello';
  @observable number=20;
  @observable bool=false;
}

複製代碼

5.2.對可觀察的數據作出反應

觀察數據變化的方式:computed、autorun、when、Reaction

computed:將多個可觀察數據組合成一個可觀察數據

autorun:重點了解一下,若是應用程序都是 可觀察數據 ,而應用程序渲染UI、寫入緩存等動做都設置爲autorun,咱們是否是就能夠安心寫代碼,只與數據狀態打交道,從而實現數據統一管理的目標。

when:提供了根據條件執行邏輯,是autorun的一種變種。

reaction:分離可觀察數據聲明,對autorun作出改進。

四個方法各有特色且互相補充

5.3.修改可觀察數據(action)

以前有提到autorun,還有一個問題須要解決,那就是性能問題,若是數據衆多,每一次小修改都會觸發autorun,以下:

import {observable, isArrayLike, computed, action, runInAction, autorun, when, reaction} from "mobx";

class Store {
  @observable array = [];
  @observable obj = {};
  @observable map = new Map();

  @observable string = 'hello';
  @observable number = 20;
  @observable bool = true;

  @computed get mixed() {
    return store.string + ':' + store.number;
  }
}

let store = new Store();

reaction(() => [store.string, store.number,store.bool], arr => console.log(arr.join('+')));

store.string = 'word';
store.number = 25;
store.bool = true;

// 打印結果
word+20+false
word+25+false
word+25+true
複製代碼

咱們從上面的結果中獲得,每次修改都會觸發reaction,那麼該怎麼解決呢?

可使用action來解決這個問題。修改代碼以下:

import {observable, isArrayLike, computed, action, runInAction, autorun, when, reaction} from "mobx";

class Store {
  @observable array = [];
  @observable obj = {};
  @observable map = new Map();

  @observable string = 'hello';
  @observable number = 20;
  @observable bool = false;

  @action bar() {
    store.string = 'word';
    store.number = 333;
    store.bool = true;
  }

}

let store = new Store();

reaction(() => [store.string, store.number, store.bool], arr => console.log(arr.join('+')));

store.bar();

// 打印結果
word+333+true
複製代碼

使用action後發現,雖然修改了3個數據,可是隻調用了一次reaction方法,作到了性能上的優化。因此當數據較多的時,建議使用action來更新數據。

6.mobx實現TodoList

功能以下:

  • Todo條目的列表展現
  • 增長Todo條目
  • 修改完成狀態
  • 刪除Todo條目

首先,建立一個TodoList文件夾,在目錄下新建一個store.js文件,這個文件用來作數據的處理。

// store.js
import {observable, computed, action} from "mobx";

class Todo {
  id = Math.random();
  @observable title = '';
  @observable finished = false;

  constructor(title) {
    this.title = title;
  }

  @action.bound toggle() {
    this.finished = !this.finished;
  }

}

class Store {
  @observable todos = [];

  @action.bound createTodo(title) {
    this.todos.unshift(new Todo(title))
  }

  @action.bound removeTode(todo) {
    // remove不是原生的方法,是mobx提供的
    this.todos.remove(todo);
  }

  @computed get left() {
    return this.todos.filter(item => !item.finished).length;
  }
}

var store = new Store();


export default store;
複製代碼

新建一個TodoList.js文件

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {observer, PropTypes as ObservablePropTypes} from 'mobx-react';
import TodoItem from "./TodoItem";


@observer
class TodoList extends Component {
  // 屬性類型要在全局這裏定義
  static propTypes = {
    store: PropTypes.shape({
      createTodo: PropTypes.func,
      todos: ObservablePropTypes.observableArrayOf(ObservablePropTypes.observableObject).isRequired
    }).isRequired
  };

  state = {
    inputTile: ""
  };

  handleSubmit = (e) => {
    // 表單提交,阻止整個頁面被提交
    e.preventDefault();
    let {store} = this.props;
    let {inputTile} = this.state;

    store.createTodo(inputTile);

    // 建立完新的條目以後,要清空輸入框
    this.setState({
      inputTile: ""
    })
  };

  handleChange = (e) => {
    var inputTile = e.target.value;
    this.setState({
      inputTile
    })
  };

  render() {
    let {inputTile} = this.state;
    let {store} = this.props;
    return <div className="todoList">
      <header>
        <form onSubmit={this.handleSubmit}>
          <input type="text"
                 onChange={this.handleChange}
                 value={inputTile}
                 placeholder="你想要到哪裏去?"
                 className="input"
          />
        </form>
      </header>
      <ul>
        {
          store.todos.map((item) => {
            return (
              <li key={item.id}>
                <TodoItem todo={item}/>
                <span onClick={()=>{store.removeTode(item)}}>刪除</span>
              </li>
            )
          })
        }
      </ul>
      <footer>
        {store.left} 項 未完成
      </footer>
    </div>
  }
}

export default TodoList
複製代碼

新建一個TodoItem.js文件

import React, {Component} from 'react';
import {observer} from 'mobx-react';
import PropTypes from 'prop-types';

@observer
class TodoItem extends Component {
  // 從父組件傳來的屬性類型要在全局這裏定義,作一些限制
  static propTypes = {
    todo: PropTypes.shape({
      id: PropTypes.number.isRequired,
      title: PropTypes.string.isRequired,
      finished: PropTypes.bool.isRequired
    }).isRequired
  };

  handleClick = (e) => {
    let {todo} = this.props;
    todo.toggle();
  }

  render() {
    // 這裏的Item是一個對象
    let {todo} = this.props;
    return (
      <div> <input type="checkbox" checked={todo.finished} onChange={this.handleClick} /> <span className="title">{todo.title}</span> </div> ) } } export default TodoItem; 複製代碼

在你組件中引入TodoList組件、以及store.js

import React, {Component} from 'react';

import TodoList from "../../component/TodoList/TodoList";
import store from "../../store/store";

class PictureResources extends Component {
  render() {
    return (
      <TodoList store={store}/> ) } } export default PictureResources 複製代碼

7.使用@inject注入

爲了更方便數據管理,咱們使用@inject將數據注入,首先找尋根組件,例如APP.js,咱們引入mox-react中的Provider做爲根容器,再將咱們的全部數據引入到APP組件中,營造一種把數據放到全局統一調配的感受。若是子組件須要的到store數據的話,再按需注入(@inject)以下代碼:

import React, {Component} from 'react';
// styles
import './scss/style.scss';
// store
import {Provider} from 'mobx-react';
import store from './store/store.js';

class App extends Component {
  render() {
    return (
      <Provider store={store}> <div className="panel"></div> </Provider>
    );
  }
}

export default App;
複製代碼

在須要的子組件中注入數據,注意"store"使用引號注入,代碼以下:

import React, {Component} from 'react';
import {inject, observer} from 'mobx-react';

@inject("store")
@observer
class Upload extends Component {
  render() {
    return (
      <div></div>
    )
  }
}

export default Upload;
複製代碼
相關文章
相關標籤/搜索