1.本文目標
閱讀本文章您可能收穫如下:
- 配置修飾器環境
- 理解響應式程式設計的概念
- 正確使用mobx關鍵api達到維護應用程式狀態的目標
2.什麼是修飾器?
Decorator是在 宣告階段 實現類與類成員註解的一種語法。
說的直白點Decorator就是 新增 或者 修改 類的變數與方法。
2.1 一定需要修飾器嗎?
在開始使用mobox,我們還需要糾結一個東西,就是配置環境啟用ES7的修飾器語法,當然,如果你不需要修飾器,可以跳過這一部分。
如果是新手的話,建議配置,官方的MobX文件也說明了,不一定要使用修飾器,如果有人說你必須在MobX中使用decorator,那就不是真的,你也可以使用普通函式,如下:
import {
decorate, observable
} from "mobx";
class Todo {
id = Math.random();
title = "";
finished = false;
}decorate(Todo, {
title: observable, finished: observable
})複製程式碼
2.2 使用和不使用修飾符的對比
不使用修飾符如下:
import React, {Componnet
} from 'react';
import {observe
} from 'mobx-react';
// 沒有使用 修飾器class Test extends Componnet {
...
}export default observe(Test);
複製程式碼
使用修飾符如下:
import React, {Componnet
} from 'react';
import {observe
} from 'mobx-react';
// 使用 修飾器@observe class Test extends Componnet{
...
}export default Test;
複製程式碼
以上程式碼中,通過observer(App)定義類和@observer class App 裝飾一個元件是一樣的。
那麼多個修飾符組合到一個元件上是怎樣的呢?。
不使用修飾符如下:
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') @observeclass 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相關
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/corenpm install --save-dev @babel/plugin-proposal-class-propertiesnpm 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';
// 引入 mobximport {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";
@observerclass 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.效果
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+falseword+25+falseword+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.jsimport {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";
@observerclass 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';
@observerclass 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';
// stylesimport './scss/style.scss';
// storeimport {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")@observerclass Upload extends Component {
render() {
return ( <
div>
<
/div>
)
}
}export default Upload;
複製程式碼