強大的狀態管理工具-Mobx
前言
Mobx是一款精準的狀態管理工具庫,如果你在 React 和 React Native 應用中使用過 Redux ,那毫不猶豫地說,MobX 的簡單性將成為你狀態管理的不二之選,本文主要講的是關於Mobx在React-native下的使用和常見問題。
常見API
在使用Mobx之前,首先介紹幾個常見的API
1. observable
Mobx如此簡單的原因之一,就是使用了可觀察資料(observable Data),簡單來說,可觀察資料就是可以觀察到資料的讀取,寫入,並進行攔截
Mobx中提供了observable
介面來定義可觀察資料,可觀察資料的型別可以使基本資料型別,object,array或者ES6中的Map型別,
注意:
observable
包裝之後,就不是Array型別了,而是Mobx中的一個特殊型別,observable
型別。雖然資料型別不一樣,但是使用方式和原來使用方式一致(原始資料型別除外)
const Array = observable([1,2,3]); const object = observable({name: 'Jay'}); const Map = observable(new Map([['name','ding']])); console.log(Array[0]) // 1 console.log(object.name) // Jay console.log(Map.get('name')) // ding
@observable
裝飾器可以再ES7或者TypeScript類屬性中使用,將其轉換成可觀察的。 @observable
可以再欄位和屬性getter上使用,對於物件的哪部分需要成為可觀察物件,@observable 提供了細粒度的控制。
import { observable, computed } from "mobx"; class Order { @observable price = 0; @observable amount = 1; @computed get total() { return this.price * this.amount; } }
observer
observer接收一個React-native元件作為引數,並將其轉換成響應式元件
@observer export default class App extends Component {
render() {
return (
<View></View>
)
}
}
響應式元件,即當且僅當元件依賴的可觀察物件資料發生改變時,元件才會自動相應並且重新渲染,而在傳統的react-native應用中,當狀態屬性變化後會先呼叫shouldComponentUpdate,該方法會深層對比前後狀態和屬性是否發生改變,再確定是否更新元件。
shouldComponentUpdate是很消耗效能的,Mobx通過可觀察資料,精確地知道元件是否需要更新,減少了利用shouldComponentUpdate這個方法,這是Mobx效能好的原因之一
陷阱:Mobx可以做很多事,但是它還是無法將原始資料型別轉換成可觀察的,所以值是不可觀察的,但是物件的屬性可以被觀察,這意味著 @observer 實際上是對間接引用(dereference)值的反應。
computed
計算值(computed values)是可以根據現有狀態或其它計算值衍生出的值,計算的耗費是不可低估的,computed儘可能幫你減少其中的耗費,它們是高度優化的。
computed values是自動幫你從你的狀態(state)值和其他計算輔助值來計算的。MobX做了很多的優化。當參與計算的值沒有發生改變,Computed是不會重新執行。如果參與計算的值沒有被使用,Computed values是暫停的。如果Computed values不再是觀察者(observed),那麼在UI上也會把它除掉,MobX能自動做垃圾回收,用法如下:
class foo {
@observable length: 2,
@computed get squared() {
return this.length * this.length;
}
}
Autorun
Autorun是用在一些你想要產生一個不用觀察者參與的被動呼叫函式裡面。當autorun被使用的時候,一旦依賴項發生變化,autorun提供的函式就會被執行。與之相反的是,computed提供的函式只會在他有自己的觀察員(observers)的時候才會評估是否重新執行,否則它的值被認為是無用的
綜上所述:如果你需要一個自動執行但確不會產生任何新的值的結果的函式,就可以使用Autorun,其他情況可以使用computed,Autorun只是作用於如果達到某個效果或者功能,而不是計算某些值
就像 @ observer 裝飾器/函式,autorun 只會觀察在執行提供的函式時所使用的資料。
var numbers = observable([1,2,3]);
var sum = computed(() => numbers.reduce((a, b) => a + b, 0));
var disposer = autorun(() => console.log(sum.get()));
// 輸出 '6'
numbers.push(4);
// 輸出 '10'
disposer();
numbers.push(5);
// 不會再輸出任何值。`sum` 不會再重新計算
action
- 任何應用程式都有操作(action),action是任何改變狀態的事物,使用Mobx
,可以通過標記他們在你的程式碼中顯式的顯示你的操作(action),它會更好的組織你的程式碼,它們用於修改可觀察量或具有副作用的任何函式中。
需要注意的是:action是用在strict mode 中的用法:
- action(fn)
- action(name, fn)
- @action classMethod() {}
- @action(name) classMethod () {}
- @action boundClassMethod = (args) => { body }
- @action(name) boundClassMethod = (args) => { body }
- @action.bound classMethod() {}
// 新增圖片
@action addImg = () => {
ImagePicker.openPicker({
multiple: true,
waitAnimationEnd: false,
includeExif: true,
forceJpg: true,
maxFiles: 9 - this.imgs.length,
compressImageQuality: 0.5,
}).then((images) => {
console.log(images)
}).catch((err) => {
console.warn(err)
})
}
2.Action僅僅作用於當前執行的函式,而不是作用於當前函式呼叫的函式,這意味著在一些定時器或者網路請求,非同步處理的情況下,它們的回撥函式無法對狀態改變,這些回撥函式都應該包裹在action裡面,但是,如果你使用了async / await的話,最好的方式應該是使用 runInAction 來讓它變得更加簡單
@action /*optional*/ updateDocument = async () => {
const data = await fetchDataFromUrl();
/* required in strict mode to be allowed to update state: */
runInAction("update state after fetching data", () => {
this.data.replace(data);
this.isSaving = true;
})
}
常見可觀察型別
Observable 物件
observable.object方法將物件變為可觀察的,它實際上是把物件的所有屬性轉換為可觀察的,存放到一個代理物件上,以減少對原物件的汙染,預設情況下,observable是遞迴應用的,所以如果物件的某個值是一個物件或陣列,那麼該值也將通過 observable 傳遞
import {observable, autorun, action} from "mobx"
var person = observable({
name : 'jack',
age:24,
sex:'男'
})
Observable 陣列
與物件類似,可以使用Observable.array(array)或者將陣列傳給 observable,可以將陣列轉換成可觀察的,這也是遞迴的,所以陣列中的所有(未來的)值都會是可觀察的。
import {observable, autorun} from "mobx";
var todos = observable([
{ title: "Spoil tea", completed: true },
{ title: "Make coffee", completed: false }
]);
autorun(() => {
console.log("Remaining:", todos
.filter(todo => !todo.completed)
.map(todo => todo.title)
.join(", ")
);
});
// 輸出: 'Remaining: Make coffee'
todos[0].completed = false;
// 輸出: 'Remaining: Spoil tea, Make coffee'
todos[2] = { title: 'Take a nap', completed: false };
// 輸出: 'Remaining: Spoil tea, Make coffee, Take a nap'
todos.shift();
// 輸出: 'Remaining: Make coffee, Take a nap'
除了可以使用所有的內建函式,observable陣列還提供了好多方法供我們使用
- clear() -- 從陣列中刪除所有項
- replace(newItems) -- 用新元素替換陣列中所有已存在的元素
- remove(value) -- 通過值從陣列中移除一個單個的元素。
注意:
不同於sort和reverse函式的實現,observableArray.sort 和 observableArray.reverse 不會改變陣列本身,而只是返回一個排序過/反轉過的拷貝,在 MobX 5 及以上版本中會出現警告。推薦使用 array.slice().sort()
來替代。
Observable Map
與陣列的處理方式類似,Mobx也實現了一個ObservableMap類,不過只支援字串。數字或Bool值作為鍵,ObservableMap在可觀察物件的基礎上,還要使鍵的增刪可觀察。它可以看做兩個可觀察對映和一個可觀察陣列的組合:
import {observable, autorun} from "mobx"
const map = observable(new Map());
autorun(() => {
console.log(map.get('key'));
});
map.set('key', 'value'); // 新增 key-value 鍵值對,輸出 value
map.set('key', 'anotherValue'); // 修改為 key-anotherValue,輸出 anotherValue
map.set('prop', 'value'); // 不輸出
map.delete('prop'); // 不輸出
優化React 元件
避免在父元件中訪問子元件的屬性
在文件中也有提到過這個問題,Mobx對於一個observer元件,是通過訪問屬性來訪問以來的,所以哪怕父元件裡沒有用到這個屬性,只是為了作為props傳給子元件,Mobx還是會算它依賴了這個屬性,於是會產生不必要的更新,最好的方式是把資料統一放到Store中,子元件通過 inject store 方式獲取資料。
小元件
由於React的機制,Mobx只能在元件層發光發熱,對於元件內部就是無能為力了,所以大元件很容易卡死,小元件才能真正發揮Mobx的優勢。
在專用元件中渲染列表
React在渲染大型資料集合的時候處理的很不好,因為協調器必須評估每個集合變化的集合所產生的元件。因此,建議使用專門的元件來對映集合並渲染這個元件,且不再渲染其他元件:
官方提供的demo如下:
不妥的處理方式:
@observer class MyComponent extends Component {
render() {
const {todos, user} = this.props;
return (<div>
{user.name}
<ul>
{todos.map(todo => <TodoView todo={todo} key={todo.id} />)}
</ul>
</div>)
}
}
在示例中,當user.name發生變化時React 會不必要地協調所有的 TodoView 元件。儘管TodoView 元件不會重新渲染,但是協調的過程本身是非常昂貴的。
正確的處理方式:
@observer class MyComponent extends Component {
render() {
const {todos, user} = this.props;
return (<div>
{user.name}
<TodosView todos={todos} />
</div>)
}
}
@observer class TodosView extends Component {
render() {
const {todos} = this.props;
return <ul>
{todos.map(todo => <TodoView todo={todo} key={todo.id} />)}
</ul>)
}
}
在react-native使用Mobx常見的問題
observable 陣列的型別是物件
observable 陣列型別其實是個物件,所以它遵循propTypes.object ,如果使用propTypes.array 會報錯。mobx-react 為 observable 資料結構提供了明確的 PropTypes。
在 React Native 中渲染 ListView
React Native 的的DataSource只能接收真正的陣列,但是observable 陣列是個物件,所以在傳給ListView之前使用.slice方法,此外,ListView.DataSource 本身可以移到 store 之中並且使用 @computed 自動地更新,這步操作同樣可以在元件層完成
class ListStore {
@observable list = [
'Hello World!',
'Hello React Native!',
'Hello MobX!'
];
ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
@computed get dataSource() {
return this.ds.cloneWithRows(this.list.slice());
}
}
const listStore = new ListStore();
@observer class List extends Component {
render() {
return (
<ListView
dataSource={listStore.dataSource}
renderRow={row => <Text>{row}</Text>}
enableEmptySections={true}
/>
);
}
}
原文http://techblog.sishuxuefu.com/atricle.html?5bb9f17a0b6160006f5988bb