Mobx如此簡單

本文說明:mobx v5.0.0^、mobx一些基本概念、mobx與react 一塊兒使用的技巧、mobx在使用時注意點javascript

  • mobx 是什麼呢?爲何要開發出這個庫呢?
  • mobx 裏的一些關鍵概念聯繫:actions、state、computed values、reaction
  • mobx 經常使用的一些 API:@observable、@autorun、@when、@computed、@action
  • mobx 中調試追蹤的工具
  • mobx 中哪些地方值得注意

Mobx是什麼?

它就是簡單的、可擴展的狀態管理。 由於經過透明的函數響應式編程(transparently applying functional reactive programming - TFRP)使得狀態管理變得簡單和可擴展。java

主要概念有哪些?

  • actions:一些改變狀態值(state)的動做。
  • state:可觀察的狀態值
  • computed value:根據state,用pure function計算出來的值
  • reactions:因state或computed value變化而引發的反應,主要指視圖UI從新渲染

經常使用裝飾器

  • @observable 將一個變量變得可觀察
  • @observer 經常使用於React組件,可監視其render函數裏使用的可觀察變量,從而做出相應reactions
  • @autorun 經常使用於組件類或store類的constructor裏,用來建立實例時,可監視其函數參數裏使用的可觀察變量,從而做出相應reactions,通常是將函數再執行一遍。
  • @when 有條件的@autorun
  • @computed 經過可觀察變量通過純函數計算得來的值,使用時纔會計算,沒有使用時不會去計算
  • @action 能改變可觀察變量值的操做(通常是函數方法)

若是不習慣用裝飾器,用一些經常使用的函數方法也能夠,本文只是寫我的以爲的最佳實踐,但後面項目實現時會實踐。react

TodoList 項目實現對比:github.com/peidongGuo/…

第一組:有沒有使用Mobx

// 無 mobx
class TodoList extends Component {
 state = { //組件數據只能在state裏,更改只能用setState()
    inputItem: "",
    listFlag: "all",
    allList: []
  };

  handleInputItem = e => {
    this.setState({
      inputItem: e.target.value
    });
  };

  handleItemStatus = (e, index) => {
    e.persist();
    let items = this.state.allList;
    console.log(items, index);
    items[index].isCompleted = e.target.checked;
    this.setState({
      allList: items
    });
  };
  ...
  render(){
    ...
    <input
                className="new-todo"
                placeholder="What needs to be done?"
                value={this.state.inputItem}
                onChange={this.handleInputItem}
                onKeyDown={this.handleAddItem}
                autoFocus={true}
              />
    ...
     <li
                        className={item.isCompleted ? "completed" : ""}
                        className={item.isEditing ? "editing" : ""}
                        key={index}
                        onDoubleClick={() => {
                          this.handleItemEdit(index, true);
                        }}
                      >
                        <div className="view">
                          <input
                            className="toggle"
                            type="checkbox"
                            checked={item.isCompleted}
                            onChange={e => {
                              this.handleItemStatus(e, index);
                            }}
                          />
                          <label>{item.title}</label>
                          <button
                            className="destroy"
                            onClick={() => {
                              this.handleItemDelete(index);
                            }}
                          />
                        </div>
                        <input
                          className="edit"
                          value={item.title}
                          onChange={e => {
                            this.handleItemTitle(e, index);
                          }}
                          onBlur={e => {
                            this.handleItemEdit(index, false);
                          }}
                        />
                      </li>
    ...
  }
  ...
  }
複製代碼
// 有 mobx 並且使用裝飾器語法
@observer  // 將組件變得可觀察
class TodoListMobx extends Component {
  @observable inputItem = "";  // 將屬性變成可觀察屬性
  @observable listFlag = "all";
  @observable allList = [];

  @action // 此操做會引發可觀察屬性的變化
  handleInputItem = e => {
    console.log(e.target);
    this.inputItem = e.target.value; // 直接修改可觀察屬性
    console.log(this.inputItem);
  };

  @action
  handleItemToggle = (e, index) => {
    e.persist();
    this.allList[index].isCompleted = e.target.checked;
  };
  ...
  render(){  // 可觀察的組件當其包含的可觀察屬性變化後,render會再次執行。
      ...
      <li
                        className={LiClass}
                        key={index}
                        onDoubleClick={() => {
                          item.isEdit = true;
                        }}
                      >
                        <div className="view">
                          <input
                            className="toggle"
                            type="checkbox"
                            checked={item.isCompleted}
                            onChange={e => {
                              item.isCompleted = !item.isCompleted;
                            }}
                          />
                          <label>{item.title}</label>
                          <button
                            className="destroy"
                            onClick={e => {
                              this.handleItemDelete(index);
                            }}
                          />
                        </div>
                        <input
                          className="edit"
                          value={item.title}
                          onChange={e => {
                            item.title = e.target.value;
                          }}
                          onBlur={e => {
                            item.isEdit = false;
                          }}
                        />
                      </li>
    ...
  }
  ...
 }
複製代碼

以上兩組代碼對比能夠看到:mobx的加入將react組件裏的數據變化操做簡單化,機動化渲染UI。並且,總體代碼書寫上簡單很多,並且理解上更容易。git

第二組:有沒有使用裝飾器

使用裝飾器的代碼仍是參考第一組的第二塊代碼github

// 有 mobx 並且 不使用裝飾器語法

class TodoListMobxNoDeco extends Component {
  inputItem = "";  
  listFlag = "all";
  allList = [];

 
  handleInputItem = e => {
    console.log(e.target);
    this.inputItem = e.target.value; 
    console.log(this.inputItem);
  };

  
  handleItemToggle = (e, index) => {
    e.persist();
    this.allList[index].isCompleted = e.target.checked;
  };
  ...
  render(){  // 可觀察的組件當其包含的可觀察屬性變化後,render會再次執行。
      ...
      <li
                        className={LiClass}
                        key={index}
                        onDoubleClick={() => {
                          item.isEdit = true;
                        }}
                      >
                        <div className="view">
                          <input
                            className="toggle"
                            type="checkbox"
                            checked={item.isCompleted}
                            onChange={e => {
                              item.isCompleted = !item.isCompleted;
                            }}
                          />
                          <label>{item.title}</label>
                          <button
                            className="destroy"
                            onClick={e => {
                              this.handleItemDelete(index);
                            }}
                          />
                        </div>
                        <input
                          className="edit"
                          value={item.title}
                          onChange={e => {
                            item.title = e.target.value;
                          }}
                          onBlur={e => {
                            item.isEdit = false;
                          }}
                        />
                      </li>
    ...
  }
  ...
 }
 decorate(TodoListMobxNoDeco, {
  inputItem: observable,
  allList: observable,
  listFlag: observable,
  handleAddItem: action,
  handleClearCompleted: action,
  handleInputItem: action,
  handleItemDelete: action,
  handleItemToggle: action,
  handleListChg: action
});

TodoListMobxNoDeco = observer(TodoListMobxNoDeco);

export default TodoListMobxNoDeco;
複製代碼

能過這一組例子對比會發現,裝飾器只是一個語法糖,能夠完成不使用,但使用後,可以讓代碼整潔很多。編程

一些經常使用調試追蹤工具函數,會在下面的實踐中使用

  1. trace
  2. mobx-react-devtools
  3. spy

記住如下幾點

  • mobx 只對observables 屬性的引用監視,不會對其值進行監視
import DevTools from "mobx-react-devtools";
...
render(){return (
    <div> <DevTools /> </div>
)}
...

//Counter組件,簡單展現
@observer
class Counter extends Component {
  @observable count = 0; // 將count變成可觀察屬性

  countCopy = this.count;  // 被賦值給一個本地變量,不會隨着this.count的變化而變化

  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div> <span>這是 @observable 變量 count:{this.count}</span> <br /> <span>這是 @observable 變量 count的本地拷貝:{this.countCopy}</span> <br /> <span onClick={this.handleAdd}>Count加一</span> <br /> <span onClick={this.handleDec}>Count減一</span> </div>
    );
  }

  @action
  handleAdd = () => {
    this.count++;
  };

  @action
  handleDec = () => {
    this.count--;
  };
}

複製代碼

能夠看到拷貝出來的變量不會再變化app

  • 儘可能晚些引用observables屬性的子屬性
// 這個例子中,父組件在使用子組件時直接賦值了整個observable,導到若是此Observable地址不變,子組件極有可能不會從新渲染

class SubCounter extends Component {
  render() {
   const count=this.props.count;
    return (
      <div> <span> 這是子組件顯示父組件 @observable 變量 countObj.count1: {count.count1} </span> <br /> <span> 這是子組件顯示父組件 @observable 變量 countObj.count1: {count.count2} </span> </div>
    );
  }
}

@observer
class Counter extends Component {
  @observable count = 0;

  countCopy = this.count;

  @observable countObj = {
    count1: 1,
    count2: 2
  };

  constructor(props) {
    super(props);
  }
  render() {
    const countCopy2 = this.count;
    return (
      <div> // 這裏直接引用了整個observable,導到若是此Observable地址不變,子組件極有可能不會從新渲染 <SubCounter count={this.countObj} /> <span> 這是 @observable 變量 countObj.count2:{this.countObj.count2} </span> <br /> <span>這是 @observable 變量 count的本地拷貝:{this.countCopy}</span> <br /> <span onClick={this.handleAdd}>CountObj.count1加一</span> <br /> <span onClick={this.handleDec}>Count減一</span> </div> ); } @action handleAdd = () => { this.countObj.count1++; }; @action handleDec = () => { this.count--; }; } export default Counter; 複製代碼
  • 嵌套組件最好每層都是observer
@observer //若是注掉,那麼不會監視傳過來的count, 天然不會從新渲染
class SubCounter extends Component {
  render() {
    return (
      <div> <span> 這是子組件顯示父組件 @observable 變量 countObj.count1: {this.props.count.count1} </span> <br /> <span> 這是子組件顯示父組件 @observable 變量 countObj.count1: {this.props.count.count2} </span> </div>
    );
  }
}

@observer
class Counter extends Component {
  @observable count = 0;

  countCopy = this.count;

  @observable countObj = {
    count1: 1,
    count2: 2
  };

  constructor(props) {
    super(props);
  }
  render() {
    const countCopy2 = this.count;
    return (
      <div> <SubCounter count={this.countObj} /> <span> 這是 @observable 變量 countObj.count2:{this.countObj.count2} </span> <br /> <span>這是 @observable 變量 count的本地拷貝:{this.countCopy}</span> <br /> <span onClick={this.handleAdd}>CountObj.count1加一</span> <br /> <span onClick={this.handleDec}>Count減一</span> </div> ); } @action handleAdd = () => { this.countObj.count1++; }; @action handleDec = () => { this.count--; }; } export default Counter; 複製代碼
相關文章
相關標籤/搜索