react+mobx+thrift學習demo

2018-08-14 learning about work

begin:2018-08-13css

step 1 熟悉react 寫法html

step 2 mobx 瞭解&使用vue

step 3 thrift接口調用過程node

React&JavaScript

propsType

propsType官方文檔react

react能夠在引入prop-types,配置propsTypes屬性以後進行類型檢查。webpack

能夠將屬性聲明爲JS原生類型、React元素、某個類的實例,指定類型的對象等,也能夠自定義,能夠加上isRequired後綴,若是沒有提供該信息,會打印警告。git

還能夠經過配置defaultProps,爲props定義默認值。es6

props.children

react childrengithub

class Grid extends React.Component {
  constructor(props) {
    super(props)
    this.state = {}
  }
  render() {
    return (
      // 能夠在這裏控制子元素是否顯示
       <div>{this.props.children}</div>
      // 只顯示hello文本,並不顯示子元素
      // <div>hello</div>
    )
  }
}
const Row = ({ name }) => {
  return (
    <div>{name}</div>
  )
}

ReactDom.render(
  <Grid>
    <Row name={1} />
    <Row name={2} />
    <Row name={3} />
  </Grid>,
  document.getElementById('root')
)

es6 static methods

React & MobX

介紹&功能

mobx是一個狀態管理器,下圖是官網的原理圖,看上去感受跟Vue的雙向數據綁定很類似。web

mobx

經過action來修改組件狀態,因爲數據與視圖雙向綁定,一旦數據改變,會觸發視圖的更新,從而引發組件或者頁面的從新渲染。

mobx的computed與vue的計算屬性也有相似,都設置了緩存,依賴項沒有發生變化的時候,該屬性不會從新運行計算,只有在真正須要更新的時候纔會更新。設置了computed的方法與普通方法的區別,也相似於vue的computed與method的區別。

感受簡單而言,從視圖更新的過程來看,能夠抽象成三個部分:action、state、views,mobx單項數據流,能夠有下圖的描述:

簡單描述

我以爲,State若是類比於MVVM的話,能夠理解爲ViewModel。

從開發者的角度來看:

開發者角度

本地搭建環境

本地須要搭建一個react-app環境並添加mobx等相關依賴。

step:

  1. create-react-app my-react-app 使用命令行工具建立新的react-app,並進入項目目錄

    (本地需先使用npm install -g create-react-app 命令安裝工具)

  2. 安裝babel等

    npm install --save-dev babel-core babel-cli babel-preset-env babel-preset-react

  3. 建立&編寫.babelrc文件

    (這裏的plugins若是不寫也能夠,關於支持ES7裝飾器的配置問題,後面會再講)

    {
        "presets": [
            "env",
            "react",
            "stage-1",
            "es2015"
        ],
        "plugins": [
            "transform-decorators-legacy",
            "transform-decorators"
        ]
    }
  4. 安裝其餘依賴,包括style-loaderbabel-loadercss-loader等等。

    這裏我開始手動安裝了webpack,而後安裝webpack的時候,沒有指定版本號,默認會安裝最新版本Webpack4,運行時會報下面錯誤:

    Cannot read property 'thisCompilation' of undefined during npm run build

    參考這裏的解決方式

    To solve this problem:

    • Delete node_modules
    • Delete package-lock.json if present
    • If you have react-scripts in package.json, make sure you don't have webpack in it
    • Run yarn (or npm install)
    • Also make sure you don't have package.json or node_modules in the parent folders of your project

    另外一種方式是webpack降級到3。能夠理解成webpack4與react-scripts不能同時在package.json中存在。

    查找資料的時候發現,若是使用Create React App的話,實際上是不須要手動再去安裝Webpack的。

    最後我刪除了node_modules,而後package.json中刪除了webpack,從新npm install或者yarn一下,問題解決了。

  5. 配置裝飾器語法支持。

    安裝babel-plugin-transform-decorators babel-plugin-transform-decorators-legacy等相關依賴。

    實際狀況是,依賴裝完,.babelrc文件中也配置了插件,webpack也配置完成以後,仍然沒法識別裝飾器語法,最後按照參考中的方法2解決了。可是這種方法須要在node_modules中修改,我的以爲不大好,暫時先這樣處理下,後續再查看下。

  6. 經過npm run start啓動項目,進行後續操做。

核心概念 & 使用

參考文檔

參考學習了 egghead.io課程

入門demo:

import { observable } from 'mobx';
import { observer } from 'mobx-react';
import { Component } from 'react';
import React from "react";
import ReactDOM from "react-dom";

const appState = observable({
  count: 0
})
// 這裏不能寫成剪頭函數 不然數據綁定會失效
appState.increment = function () {
  this.count++;
}
appState.decrement = function () {
  this.count--;
}

@observer class Counter extends Component {
  render() {
    return (
      <div>
        Counter {this.props.store.count} <br />
        <button onClick={this.handleInc}> + </button>
        <button onClick={this.handleDec}> - </button>
      </div>
    )
  }

  handleInc = () => {
    this.props.store.increment()
  }

  handleDec = () => {
    this.props.store.decrement()
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Counter store={appState} />, rootElement);
Observable state(可觀察的狀態)

mobx使用ES7裝飾器語法(可選使用)經過給現有屬性增長@observable 註解,就能夠將屬性設定爲可觀察的屬性。使用裝飾器屬性可使得書寫代碼更加簡潔.

也能夠寫成這樣

class appState {
   @observable count = 0;
   increment = function () {
     this.count++;
   }
   decrement = function () {
     this.count--;
   }
 }
 ......
 ReactDOM.render(<Counter store={new appState()} />, rootElement);

不過有個疑惑,下圖方法1使用const定義是出於什麼目的,官方文檔的demo中也有不少是使用const定義的。

若是像下圖方法二這樣書寫,是否有意義?count值也會改變,appState定義爲const,其中內容是能夠被改變的,如何控制不被改變?實際中是否會有這種狀況?

//1. 這裏寫成const是什麼意義?
const appState = observable({
  count: 0
}) 

//2. 這樣寫是否有意義?const?
 const appState =  {
   @observable count: 0
 }
Computed values

使用@computed 修飾getter方法,計算值延遲更新,只有依賴改變,須要從新計算的時候纔會更新。

Reactions(反應)
  1. 能夠經過@observer將無狀態組件變成響應式組件, MobX 會確保組件老是在須要的時從新渲染。

    只要須要在狀態發生改變時須要更新視圖的view上使用@observer修飾,就能夠實現自動更新。

  2. 自定義 reactions
Actions

actions執行狀態的改變。

文檔中有這麼一段,我的以爲全部衍生同步更新,計算值延遲更新,這兩句彷佛有些矛盾,這裏的全部衍生是否指的是reactions或者action後出發的事件?意思是說不能用計算值來改變狀態,而是狀態改變以後計算值必定已經變化?有點拗口。。。這裏的同步更新和延遲更新到底指的是什麼,感受只能後面有時間看下源碼才能瞭解了

狀態改變時,全部 衍生任何 源自 狀態而且不會再有任何進一步的相互做用的東西就是衍生 )都會進行 原子級的自動更新。所以永遠不可能觀察到中間值。

全部衍生默認都是同步更新。這意味着例如動做能夠在改變狀態以後直接能夠安全地檢查計算值。

計算值延遲更新的。任何不在使用狀態的計算值將不會更新,直到須要它進行反作用(I / O)操做時。 若是視圖再也不使用,那麼它會自動被垃圾回收。

全部的計算值都應該是純淨的。它們不該該用來改變狀態

其餘注意

observable 相關
  1. 經過 observable 傳遞對象時,後添加到對象的屬性沒法自動變成可觀察的狀態

    這點有點相似於Vue中的對象數據綁定,若是在最開始定義的時候沒有定義某個屬性,後面再添加時將沒法監控到這個屬性的變化,可使用vue.set來使得操做生效。

當使對象轉變成 observable 時,須要記住一些事情:

  • 當經過 observable 傳遞對象時,只有在把對象轉變 observable 時存在的屬性纔會是可觀察的。 稍後添加到對象的屬性不會變爲可觀察的,除非使用 setextendObservable
  1. Array.isArray(observable([]))返回值爲false
observable.array 會建立一我的造數組(類數組對象)來代替真正的數組。 實際上,這些數組能像原生數組同樣很好的工做,而且支持全部的原生方法,包括從索引的分配到包含數組長度。

請記住不管如何 Array.isArray(observable([])) 都將返回 false ,因此不管什麼時候當你須要傳遞 observable 數組到外部庫時,經過使用 array.slice() 在 observable 數組傳遞給外部庫或者內置方法前建立一份淺拷貝(不管如何這都是最佳實踐)總會是一個好主意。 換句話說,Array.isArray(observable([]).slice()) 會返回 true

  1. Array的sort&reverse方法會修改原數組,observableArray則不會
不一樣於  sort 和  reverse 函數的內置實現,observableArray.sort 和 observableArray.reverse 不會改變數組自己,而只是返回一個排序過/反轉過的拷貝。
computed
  1. computed&autorun並不同。

    兩者都是響應式調用的衍生,可是computed能夠理解爲一個純函數(即調用時刻的輸出只由該時刻的輸入決定,而不依賴於系統狀態),若是使用過程當中依賴沒有被修改,則不會從新計算。autorun的使用場景更像是產生效果,例如對數據進行過濾操做(而不是產生數據),或者數據監控到數據變化以後的通知等反作用操做(這點與vue中的method並不同,不要混淆)。

    若是你想響應式的產生一個能夠被其它 observer 使用的 ,請使用  @computed,若是你不想產生一個新值,而想要達到一個 效果,請使用  autorun。 舉例來講,效果是像打印日誌、發起網絡請求等這樣命令式的反作用。
  2. 能夠經過將computed做爲一個函數,來獲取在box中的計算值(即基本數據類型值)
  3. 錯誤處理

    若是計算值在其計算期間拋出異常,則此異常將捕獲並在讀取其值時從新拋出。 強烈建議始終拋出「錯誤」,以便保留原始堆棧跟蹤。拋出異常不會中斷跟蹤,全部計算值能夠從異常中恢復。
    const x = observable(3)
    const y = observable(1)
    const divided = computed(() => {
        if (y.get() === 0)
            throw new Error("Division by zero")
        return x.get() / y.get()
    })
    
    divided.get() // 返回 3
    
    y.set(0) // OK
    divided.get() // 報錯: Division by zero
    divided.get() // 報錯: Division by zero
    
    y.set(2)
    divided.get() // 已恢復; 返回 1.5
autorun
  1. autorun函數具備響應式功能,可是該函數不具備觀察者。
  2. autorun函數會當即觸發,而後每次依賴關係發生改變時會再次觸發。computed建立的函數,只有當它有本身的觀察者時纔會從新計算。
observer
  1. 簡單來講: 全部渲染 observable 數據的組件都須要使用@observer
  2. 在 reaction 中使用的特定 props 必定要間接引用(例如 const myProp = props.myProp)。否則,若是你在 reaction 中引用了 props.myProp,那麼 props 的任何改變都會致使 reaction 的從新運行。
  3. MobX 追蹤屬性訪問,而不是值,能夠理解爲追蹤的是引用,當引用的內存空間發生變化時觸發響應行爲,若是隻是內存空間中的值發生變化,是不會被追蹤的。
  4. 陷阱

    const message = observable({ title: "hello" })
    autorun(() => {
        // 錯誤
        console.log(message)
        // 正確
        console.log(message.title)
    })
    
    // 不會觸發從新運行
    message.title = "Hello world"

    其餘解決方案:

    autorun(() => {
        console.log(message.title) // 很顯然, 使用了 `.title` observable
    })
    autorun(() => {
        console.log(mobx.toJS(message)) // toJS 建立了深克隆,從而讀取消息
    })
    autorun(() => {
        console.log({...message}) // 建立了淺克隆,在此過程當中也使用了 `.title`
    })
    autorun(() => {
        console.log(JSON.stringify(message)) // 讀取整個結構
    })
action
  1. 對於修改狀態的函數使用@action
  2. runInAction 是個簡單的工具函數,它接收代碼塊並在(異步的)動做中執行。
  3. 仔細瞭解了異步Action這一部分,注意書寫方式。

練習Demo

今天使用react+mobx 寫了個todolist的demo,目前實現了添加和刪除的功能。熟悉一下開發方式和書寫方式。

地址

demo

主要代碼:

import React, { Component } from 'react'
import { observable, computed, observe, action } from 'mobx';
import ReactDOM from 'react-dom';
import { inject, Provider, observer } from 'mobx-react'
import './index.css';
import { debug } from 'util';


class Todo {
  constructor(content) {
    this.content = content
  }
  id = Math.random()
  @observable content
  @observable completed = false
}

class TodoListStore {
  @observable todos = []

  @computed get todosLength() {
    return this.todos.length
  }

  @computed get completedLength() {
    return this.todos.filter(item => item.completed).length
  }

  @computed get uncompletedLength() {
    return this.todosLength - this.completedLength
  }

  @action
  addTodo(todo) {
    this.todos.push(todo)
  }

  @action
  deleteTodo(index) {
    this.todos.splice(index, 1)
    // console.log(e)
  }
}

// const TodoItem = observer(({ todo }) => (
//   <li>
//     <input
//       type="checkbox"
//       checked={todo.completed}
//       onClick={() => (todo.completed = !todo.completed)}
//     />
//     {todo.content}
//     <button onClick={}>X</button>
//   </li>
// ));

@observer
class TodoItem extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    const {todo, index} = this.props
    return (
      <li>
        <input
          type="checkbox"
          checked={todo.completed}
          onClick={() => (todo.completed = !todo.completed)}
        />
        {todo.content}
        <button onClick={this.props.del.bind(this)}>X</button>
      </li>
    )
  }
}


@observer
class TodoInput extends Component {
  constructor(props) {
    super(props)
    this.state = new Todo()
  }

  addTodo() {
    let content = this.refs.content.value
    let item = new Todo(content)
    this.props.store.addTodo(item)
    console.log(this.props.store.todos)
    this.refs.content.value = ''
  }

  render() {
    return (
      <div>
        新增todo:
        <input className="todo-input-box" type="text" ref="content" placeholder="add something"></input>
        <button onClick={this.addTodo.bind(this)}>Add</button>
      </div>
    )
  }
}

@observer
class TodoList extends Component {
  constructor(props) {
    super(props)
    this.state = {
      todos: this.props.store.todos,
      index: -1
    }
  }

  delete(index) {
    this.props.store.deleteTodo(index)
  }
  
  render() {
    // let todos = [...this.props.store.todos]
    return (
      <div>
        事項清單:
        <ul>
          {this.state.todos.map((todo, index) => (
            <TodoItem todo={todo} key={todo.id} index={index} del={()=>this.delete(index)}/>
          )
          )}
        </ul>
      </div>
    )
  }
}

@observer
class TodoArchive extends Component {
  render() {
    let store = this.props.store
    return (
      <div className="archieve">
        <span className="archieve-item">總計:{store.todosLength}項</span>
        <span className="archieve-item">已完成:{store.completedLength}項</span>
        <span className="archieve-item">未完成:{store.uncompletedLength}項</span>
      </div>
    )
  }
}

class TodoListView extends Component {
  render() {
    let store = this.props.store
    return (
      <div className="list-view">
        <TodoInput store={store} />
        <TodoList store={store} />
        <TodoArchive store={store} />
      </div>
    )
  }
}

let todolist = new TodoListStore()

ReactDOM.render(<TodoListView  store={todolist} />, document.getElementById('root'));
相關文章
相關標籤/搜索