mobx,做用相似於redux,相比於reduxmobx的學習成本更低,開發難度低,開發代碼少,渲染性能好(狀態和組件是一對一的,好比你又三個轉檯對應三個組件,若是組件狀態發生改變以後,只會處理受影響的組件,不受影響的不作處理)javascript
狀態變化引發的反作用應該被自動觸發java
概念的東西可能有點抽象,咱們先試試node
mkdir mobx-text
react
npm init -y
webpack
npm i webpack webpack-cli babel-core babel-loader babel-preset-env babel-preset-react babel-preset-stage-0 babel-plugin-transform-decorators-legacy mobx mobx-react react -D
web
安裝完成後,打開項目npm
建立mobx-test/webpack.config.js,json
mobx-test/webpack.config.jsredux
const path = require('path');
module.exports = {
entry: './src/index.js' ,//文件入口
output:{
path: path.resolve('dist'),
filename: 'bundle.js'
},
//模塊編譯
module:{
rules:[
{
test:/\.jsx?/,
use:{
loader:'babel-loder',
options: {
presets: ["env", "react", "stage-0"],
plugins: ["transform-decorators-legacy"]
}
},
exclude: /node_modules/
}
]
}
}
複製代碼
建立tsconfig.json數組
{
"compilerOptions": {
"experimentalDecorators": true,
"allowJs": true
}
}
複製代碼
修改package.json
"scripts": {
"start": "webpack --mode development -w"
},
複製代碼
而後嘗試npm run start
會出來mobx/dist/bundle.js
, 咱們在`mobx/dist
用來修改類的行爲,修飾器本質就是編譯時執行的函數,若是想添加實例屬性,能夠經過目標類的prototype對象操做
@testable
class Person {
}
function testable(target) {
target.testable = true;
}
// testable(Person) 蕾絲遇將類自己傳到函數裏面修改
console.log(Person.testable);
複製代碼
//target類 key類的屬性 discriptor描述器
function readonly(target, key, discriptor){
discriptor.writable = false;
}
class Circle {
@readonly PI = 3.14; //這是實例的屬性
}
let c1 = new Circle();
console.log('Circle.prototype.PI=',Circle.prototype.PI);
c1.PI = 3.15
console.log(c1.PI);
let obj = {};
obj.name = 'zfpx';
Object.defineProperty(obj, 'age', {
value: 9,//實際的值
enumerable: false,//是否可枚舉
writable: false,//是否可修改
configurable: false//是否可配置 delete obj.age;
});
console.log(obj.age);
obj.age = 10;
console.log(obj.age);
複製代碼
function logger(target, key, descriptor) {
let oldVal = descriptor.value;//獲取老函數
descriptor.value = function () {
console.log(`${key}(${Array.from(arguments).join(',')})`);
return oldVal.apply(this, arguments);
}
}
class Calculator {
@logger
add(a, b) {
return a + b;
}
}
logger(Calculator.prototype, 'add', Object.getOwnPropertyDescriptor(Calculator.prototype, 'add'));
let c1 = new Calculator();
console.log(Calculator.prototype.add);
let ret = c1.add(1, 2);// add(1,2)
console.log(ret);
複製代碼
代替objectdefinePropty
let p1 = new Proxy({ name: 'zfpx', age: 9},{
get: function(target, key){
console.log(`get ${key}`);
return Reflect.get(target,key);
},
set: function(target, key,value){
console.log(`set ${key} ${value}`);
target[key] = value;
}
});
console.log(p1.name,p1.age);
get name
index.js:8 get age
index.js:16 zfpx 9
複製代碼
MobX爲現有的數據結構(如對象,數組和類實例)添加了可觀察的功能。
observable就是一種讓數據的變化能夠被觀察的方法
先把數據轉化成能夠被觀察的對象,那麼對這些數據的修改就能夠備監視
let arr1 = observable([1, 2, 3]);
console.log(arr1);
//數據處理
arr1.pop();
arr1.push(4);
arr1.unshift(0);
console.log(arr1); //0124
複製代碼
o1 輸出一個proxy對象,可觀察對象,原理就是proxy,對於基本數據類型須要裝箱,在類裏面的基本數據類型不須要裝箱
//添加觀察
let { observable, observe } = require('mobx');
let o1 = observable({ name: 'zdl' });
console.log(o1);
observe(o1, change => console.log(change));
o1.name = 'zdl2';
let num = observable.box(1);
observe(num, c => console.log(c));
console.log(num.get());
num.set(2);
let bool = observable.box(true);
console.log(bool.get());
let str = observable.box('hello');
console.log(str.get());
複製代碼
let { observable, observe, computed, autorun, when, reaction, action,runInAction } = require('mobx');
class Person {
@observable name = 'zfpx';
@observable age = 10;
@observable province = '廣東';
@observable city = '深圳';
@observable area = '010';
@observable number = '1899999999';
@computed get home() {
return this.province + '-' + this.city;
}
}
let p1 = new Person();
console.log(p1.home);
//要監聽變量變化能夠用observe,可是計算得放到外面
let phone = computed(() => {
return "number:" + p1.area + '-' + p1.number;
});
phone.observe(c => console.log(c));
p1.area = '202'
複製代碼
若是使用修飾器模式,則不能再用observe方法了 當你想建立一個響應式函數,而該函數自己永遠不會有觀察者時,可使用 mobx.autorun 當使用 autorun 時,所提供的函數老是當即被觸發一次,而後每次它的依賴關係改變時會再次被觸發 數據渲染後自動渲染
上述若是將@computed修飾phone,p1.phone.observe(change=>console.log(change))
會出錯,由於p1.phone
獲得的是字符串,因此不能監聽,此時咱們就須要用到autorun,當即被觸發一次,而後每次它的依賴關係改變時會再次被觸發。
let { observable, observe, computed, autorun, when, reaction, action,runInAction } = require('mobx');
class Person {
@observable name = 'zfpx';
@observable age = 10;
@observable province = '廣東';
@observable city = '深圳';
@observable area = '010';
@observable number = '1899999999';
@computed get phone() {
return this.area + '-' + this.number;
}
}
let p1 = new Person();
// p1.phone.observe(change=>console.log(change));
console.log(p1.phone);
//自動運行,當系統啓動以後自動運行此函數
autorun(() => {
console.log(p1.phone);
});
p1.area='020';
複製代碼
when 觀察並運行給定的 predicate,直到返回true。 一旦返回 true,給定的 effect 就會被執行,而後 autorunner(自動運行程序) 會被清理。 該函數返回一個清理器以提早取消自動運行程序。
let { observable, observe, computed, autorun, when, reaction, action,runInAction } = require('mobx');
class Person {
@observable name = 'zfpx';
@observable age = 10;
@observable province = '廣東';
@observable city = '深圳';
@observable area = '010';
@observable number = '1899999999';
@computed get home() {
return this.province + '-' + this.city;
}
@computed get phone() {
return this.area + '-' + this.number;
}
}
let p1 = new Person();
// when會等待條件知足,一旦知足就會執行回調並銷燬監聽
when(() => p1.age >= 11, () => {
console.log(p1.age);
});
setInterval(() => {
p1.age++;
}, 1000);
// 會返回一個取消監聽的函數,若是 調用它就直接取消監聽
let disposer = when(() => p1.age >= 18, () => {
console.log(p1.age);
});
disposer();
//這樣就直接取消監聽了,不會在console.log
setInterval(() => {
p1.age++;
}, 1000);
複製代碼
// 監聽數組中變量的變化 ,變化以後才執行回調函數
reaction(() => [p1.age, p1.name], arr => {
console.log(arr);
});
p1.age = 11;
p1.name = 'zfpx8';
複製代碼
即批量處理,所有改完在觸發
...
autorun(() => {
console.log(p1.phone);
});
p1.area='020';
p1.number='020';
//會輸出兩次,咱們想輸出一個,須要用action函數
let { observable, observe, computed, autorun, when, reaction, action,runInAction } = require('mobx');
class Person {
...
@action switchPhone(area, number) {
this.area = area;
this.number = number;
}
}
let p1 = new Person();
autorun(() => {
console.log(p1.phone);
});
//p1.area='020';
//p1.number='020';
p1.switchPhone('200', '300');
複製代碼
除了action 還有action.bound
let { observable, observe, computed, autorun, when, reaction, action,runInAction } = require('mobx');
class Person {
...
@action.bound switchPhone(area, number) {
this.area = area;
this.number = number;
}
}
let p1 = new Person();
autorun(() => {
console.log(p1.phone);
});
//p1.area='020';
//p1.number='020';
//p1.switchPhone('200', '300');
let s = p1.switchPhone;
s('200', '300');
//s() 的this指針不同,此時咱們就須要bounld操做
複製代碼
注意點:
let num = 1;
let numObj = observable.box(num);
numObj.observe(x => console.log(x));
numObj.set(100);
num = 2;//因爲num是普通類型,因此改變num對numObj沒有影響
複製代碼
可是若是咱們要隨意組合,那咱們不可能寫無數個方法,解決方法以下
將其做爲臨時代碼塊執行,runInAction不執行完,任何都沒法發現,做爲action運行
runInAction(() => {
store.province='山東';
store.city='濟南';
});
複製代碼
安裝mobx-react && react-dom src/index.js
import React, { Component } from 'react';
import { observable, action } from 'mobx';
import { observer } from 'mobx-react';
import ReactDOM from 'react-dom';
class Store {
@observable number = 0;
@action.bound add(num) {
this.number = this.number + num;
}
}
@observer
class Counter extends Component {
render() {
let store = this.props.store;
return (
<div> <p>{store.number}</p> <button onClick={() => store.add(1)}>+</button> </div>
)
}
}
let store = new Store();
//store.add = store.add.bind(store);
ReactDOM.render(<Counter store={store} />, document.querySelector('#root')); 複製代碼
import React, { Component } from 'react';
import { observable, action ,observe} from 'mobx';
import { observer } from 'mobx-react';
import ReactDOM from 'react-dom';
class Store {
@observable todos = [];
constructor() {
observe(this.todos, event => {
console.log(event);
});
}
}
let store = new Store();
store.todos.push({id: 1, name : 'zfpx'});
store.todos.push({id: 2, name : 'zfpx'});
console.log(store.todos.get(0).name);
store.todos.get(0).name = 'zfpx3'
console.log(store.todos.get(0).name);
複製代碼
disposers
import React, { Component } from 'react';
import { observable, action ,observe} from 'mobx';
import { observer } from 'mobx-react';
import ReactDOM from 'react-dom';
class Store {
@observable todos = []
disposers = [];//裏面存放着全部的取消監聽的函數
//但願監聽store的變化,變化發生以後自動執行回調函數
constructor() {
observe(this.todos, event => {
console.log(event);
//讓之前的全部取消監聽函數執行
this.disposers.forEach(disposer => disposer());
this.disposers = [];
for (let todo of event.object) {
let disposer = observe(todo, e => {
console.log(e);
});
this.disposers.push(disposer);
}
});
}
}
let store = new Store();
store.todos.push({id: 1, name : 'zfpx'});
store.todos.push({id: 2, name : 'zfpx'});
store.todos.get(0).name = 'zfpx3'
複製代碼
spy
監聽每個變化,可是性能不好,通常挑食的時候才用
import React, { Component } from 'react';
import { observable, action ,observe} from 'mobx';
import { observer } from 'mobx-react';
import ReactDOM from 'react-dom';
spy(event => console.log(event));
class Store {
@observable todos = []
disposers = [];//裏面存放着全部的取消監聽的函數
}
let store = new Store();
store.todos.push({id: 1, name : 'zfpx'});
store.todos.push({id: 2, name : 'zfpx'});
store.todos.get(0).name = 'zfpx3'
複製代碼
src/index.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { observable, action, computed, trace } from 'mobx';
import { observer } from 'mobx-react';
//放數據
class Todo {
id = Math.random();
@observable text = '';
@observable completed = false;
constructor(text) {
this.text = text;
}
//切換完成狀態
@action.bound toggle() {
this.completed = !this.completed;
}
}
class Store {
@observable todos = [];
@observable filter = 'all';
@action.bound changeFilter(filter) {
this.filter = filter;
}
@action.bound addTodo(text) {
this.todos.push(new Todo(text));
}
@action.bound removeTodo(todo) {
this.todos.remove(todo);
}
@computed get filteredTodos() {
return this.todos.filter(todo => {
switch (this.filter) {
case 'completed':
return todo.completed;
case 'uncompleted':
return !todo.completed;
default:
return true;
}
});
}
@computed get reminder() {
return this.todos.reduce((count, todo) => {
count = count + (todo.completed ? 0 : 1);
return count;
}, 0);
}
}
let store = new Store();
@observer
class TodoItem extends Component {
trace();
render() {
return (
<React.Fragment>
<input type="checkbox"
onChange={this.props.todo.toggle}
checked={this.props.todo.completed} />
<span style={{ textDecoration: this.props.todo.completed ? 'line-through' : '' }}>{this.props.todo.text}</span>
</React.Fragment>
)
}
}
@observer
class Todos extends Component {
state = {
text: ""
}
handleSubmit = (event) => {
event.preventDefault();
let text = this.state.text;
this.props.store.addTodo(text);
this.setState({ text: '' });
}
handleChange = (event) => {
this.setState({
text: event.target.value
});
}
render() {
trace()
let store = this.props.store;
return (
<div>
<form onSubmit={this.handleSubmit}>
<input type="text"
onChange={this.handleChange}
value={this.state.text} />
</form>
<ul>
{
store.filteredTodos.map(todo => (
<li key={todo.id}>
<TodoItem todo={todo} />
<span onClick={() => store.removeTodo(todo)}>X</span>
</li>
))
}
</ul>
<div>
<p>你還有{store.reminder}件待辦事項</p>
<p>
<button
onClick={() => store.changeFilter('all')}
style={{ color: store.filter == 'all' ? 'red' : 'black' }}
>所有</button>
<button
onClick={() => store.changeFilter('uncompleted')}
style={{ color: store.filter == 'uncompleted' ? 'red' : 'black' }}
>未完成</button>
<button
onClick={() => store.changeFilter('completed')}
style={{ color: store.filter == 'completed' ? 'red' : 'black' }}
>已完成</button>
</p>
</div>
</div>
)
}
}
ReactDOM.render(<Todos store={store} />, document.querySelector('#root'));
複製代碼
咱們能夠看到使用mobx優雅,簡潔,但這還不夠,咱們用其中的trace測試性能,結果並不太好,所以咱們對述代碼作出優化,
優化原則
src/index
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { observable, action, computed, trace , observe, toJS} from 'mobx';
import { observer } from 'mobx-react';
//放數據
class Todo {
id = Math.random();
@observable text = '';
@observable completed = false;
constructor(text, completed = false) {
this.text = text;
}
//切換完成狀態
@action.bound toggle() {
this.completed = !this.completed;
}
}
class Store {
@observable todos = [];
@observable filter = 'all';
@action.bound changeFilter(filter) {
this.filter = filter;
}
@action.bound addTodo(text) {
this.todos.push(new Todo(text));
}
@action.bound removeTodo(todo) {
this.todos.remove(todo);
}
@computed get filteredTodos() {
return this.todos.filter(todo => {
switch (this.filter) {
case 'completed':
return todo.completed;
case 'uncompleted':
return !todo.completed;
default:
return true;
}
});
}
@computed get reminder() {
return this.todos.reduce((count, todo) => {
count = count + (todo.completed ? 0 : 1);
return count;
}, 0);
}
}
let store = new Store();
@observer
class TodoItem extends Component {
render() {
return (
<React.Fragment>
<input type="checkbox"
onChange={this.props.todo.toggle}
checked={this.props.todo.completed} />
<span style={{ textDecoration: this.props.todo.completed ? 'line-through' : '' }}>{this.props.todo.text}</span>
</React.Fragment>
)
}
}
@observer
class TodoHeader extends Component {
state = {
text: ""
}
handleChange = (event) => {
this.setState({
text: event.target.value
});
}
handleSubmit = (event) => {
event.preventDefault();
let text = this.state.text;
this.props.store.addTodo(text);
this.setState({ text: '' });
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text"
onChange={this.handleChange}
value={this.state.text} />
</form>
)
}
}
@observer
class TodoItems extends Component {
render() {
return (
<ul>
{
this.props.store.filteredTodos.map(todo => (
<li key={todo.id}>
<TodoItem todo={todo} />
<span onClick={() => store.removeTodo(todo)}>X</span>
</li>
))
}
</ul>
)
}
}
@observer
class TodoFooter extends Component {
render() {
let store = this.props.store;
return (
<div>
<p>你還有{store.reminder}件待辦事項</p>
<p>
<button
onClick={() => store.changeFilter('all')}
style={{ color: store.filter == 'all' ? 'red' : 'black' }}
>所有</button>
<button
onClick={() => store.changeFilter('uncompleted')}
style={{ color: store.filter == 'uncompleted' ? 'red' : 'black' }}
>未完成</button>
<button
onClick={() => store.changeFilter('completed')}
style={{ color: store.filter == 'completed' ? 'red' : 'black' }}
>已完成</button>
</p>
</div>
)
}
}
@observer
class Todos extends Component {
render() {
let store = this.props.store;
return (
<div>
<TodoHeader store={store} />
<TodoItems store={store} />
<TodoFooter store={store} />
</div>
)
}
}
ReactDOM.render(<Todos store={store} />, document.querySelector('#root'));
複製代碼
每次刷新保持原來狀態
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { observable, action, computed, trace , observe, toJS} from 'mobx';
import { observer } from 'mobx-react';
//放數據
class Todo {
id = Math.random();
@observable text = '';
@observable completed = false;
constructor(text, completed = false) {
this.text = text;
}
//切換完成狀態
@action.bound toggle() {
this.completed = !this.completed;
}
}
class Store {...
@action.bound load(todos) {
todos.forEach(todo => {
this.todos.push(new Todo(todo.text, todo.completed));
});
}
cancelObserves = [];
constructor() {
observe(this.todos, (event) => {
console.log('event', event);
this.save();
this.cancelObserves.forEach(d => d());
this.cancelObserves = [];
for (let todo of event.object) {
this.cancelObserves.push(observe(todo, this.save));
}
});
}
@action.bound save() {
let todos = toJS(this.todos);
localStorage.setItem('todos', JSON.stringify(todos));
}
@action.bound addTodo(text) {
window.setTimeout(() => {
this.todos.push(new Todo(text));
}, 1000)
}
...
}
...
@observer
class Todos extends Component {
componentDidMount() {
let todosStr = localStorage.getItem('todos');
let todos = todosStr ? JSON.parse(todosStr) : [];
this.props.store.load(todos);
}
render() {...}
}
ReactDOM.render(<Todos store={store} />, document.querySelector('#root')); 複製代碼