begin:2018-08-13css
step 1 熟悉react 寫法html
step 2 mobx 瞭解&使用vue
step 3 thrift接口調用過程node
propsType官方文檔react
react能夠在引入prop-types
,配置propsTypes屬性以後進行類型檢查。webpack
能夠將屬性聲明爲JS原生類型、React元素、某個類的實例,指定類型的對象等,也能夠自定義,能夠加上isRequired後綴,若是沒有提供該信息,會打印警告。git
還能夠經過配置defaultProps
,爲props定義默認值。es6
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') )
mobx是一個狀態管理器,下圖是官網的原理圖,看上去感受跟Vue的雙向數據綁定很類似。web
經過action來修改組件狀態,因爲數據與視圖雙向綁定,一旦數據改變,會觸發視圖的更新,從而引發組件或者頁面的從新渲染。
mobx的computed與vue的計算屬性也有相似,都設置了緩存,依賴項沒有發生變化的時候,該屬性不會從新運行計算,只有在真正須要更新的時候纔會更新。設置了computed的方法與普通方法的區別,也相似於vue的computed與method的區別。
感受簡單而言,從視圖更新的過程來看,能夠抽象成三個部分:action、state、views,mobx單項數據流,能夠有下圖的描述:
我以爲,State若是類比於MVVM的話,能夠理解爲ViewModel。
從開發者的角度來看:
本地須要搭建一個react-app環境並添加mobx等相關依賴。
step:
create-react-app my-react-app
使用命令行工具建立新的react-app,並進入項目目錄(本地需先使用npm install -g create-react-app
命令安裝工具)
npm install --save-dev babel-core babel-cli babel-preset-env babel-preset-react
建立&編寫.babelrc
文件
(這裏的plugins
若是不寫也能夠,關於支持ES7裝飾器的配置問題,後面會再講)
{ "presets": [ "env", "react", "stage-1", "es2015" ], "plugins": [ "transform-decorators-legacy", "transform-decorators" ] }
安裝其餘依賴,包括style-loader
、babel-loader
、css-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
inpackage.json
, make sure you don't havewebpack
in it- Run
yarn
(ornpm install
)- Also make sure you don't have
package.json
ornode_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
一下,問題解決了。
安裝babel-plugin-transform-decorators
、 babel-plugin-transform-decorators-legacy
等相關依賴。
實際狀況是,依賴裝完,.babelrc
文件中也配置了插件,webpack也配置完成以後,仍然沒法識別裝飾器語法,最後按照參考中的方法2解決了。可是這種方法須要在node_modules
中修改,我的以爲不大好,暫時先這樣處理下,後續再查看下。
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);
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 修飾getter方法,計算值延遲更新,只有依賴改變,須要從新計算的時候纔會更新。
只要須要在狀態發生改變時須要更新視圖的view上使用@observer修飾,就能夠實現自動更新。
actions執行狀態的改變。
文檔中有這麼一段,我的以爲全部衍生同步更新,計算值延遲更新,這兩句彷佛有些矛盾,這裏的全部衍生是否指的是reactions或者action後出發的事件?意思是說不能用計算值來改變狀態,而是狀態改變以後計算值必定已經變化?有點拗口。。。這裏的同步更新和延遲更新到底指的是什麼,感受只能後面有時間看下源碼才能瞭解了
當 狀態改變時,全部 衍生( 任何 源自 狀態而且不會再有任何進一步的相互做用的東西就是衍生 )都會進行 原子級的自動更新。所以永遠不可能觀察到中間值。全部衍生默認都是同步更新。這意味着例如動做能夠在改變狀態以後直接能夠安全地檢查計算值。
計算值 是延遲更新的。任何不在使用狀態的計算值將不會更新,直到須要它進行反作用(I / O)操做時。 若是視圖再也不使用,那麼它會自動被垃圾回收。
全部的計算值都應該是純淨的。它們不該該用來改變狀態。
observable
傳遞對象時,後添加到對象的屬性沒法自動變成可觀察的狀態這點有點相似於Vue中的對象數據綁定,若是在最開始定義的時候沒有定義某個屬性,後面再添加時將沒法監控到這個屬性的變化,可使用vue.set
來使得操做生效。
當使對象轉變成 observable 時,須要記住一些事情:
- 當經過
observable
傳遞對象時,只有在把對象轉變 observable 時存在的屬性纔會是可觀察的。 稍後添加到對象的屬性不會變爲可觀察的,除非使用set
或extendObservable
。
Array.isArray(observable([]))
返回值爲false
observable.array
會建立一我的造數組(類數組對象)來代替真正的數組。 實際上,這些數組能像原生數組同樣很好的工做,而且支持全部的原生方法,包括從索引的分配到包含數組長度。請記住不管如何
Array.isArray(observable([]))
都將返回false
,因此不管什麼時候當你須要傳遞 observable 數組到外部庫時,經過使用array.slice()
在 observable 數組傳遞給外部庫或者內置方法前建立一份淺拷貝(不管如何這都是最佳實踐)總會是一個好主意。 換句話說,Array.isArray(observable([]).slice())
會返回true
。
不一樣於sort
和reverse
函數的內置實現,observableArray.sort 和 observableArray.reverse 不會改變數組自己,而只是返回一個排序過/反轉過的拷貝。
computed&autorun並不同。
兩者都是響應式調用的衍生,可是computed能夠理解爲一個純函數(即調用時刻的輸出只由該時刻的輸入決定,而不依賴於系統狀態),若是使用過程當中依賴沒有被修改,則不會從新計算。autorun的使用場景更像是產生效果,例如對數據進行過濾操做(而不是產生數據),或者數據監控到數據變化以後的通知等反作用操做(這點與vue中的method並不同,不要混淆)。
若是你想響應式的產生一個能夠被其它 observer 使用的 值,請使用@computed
,若是你不想產生一個新值,而想要達到一個 效果,請使用autorun
。 舉例來講,效果是像打印日誌、發起網絡請求等這樣命令式的反作用。
錯誤處理
若是計算值在其計算期間拋出異常,則此異常將捕獲並在讀取其值時從新拋出。 強烈建議始終拋出「錯誤」,以便保留原始堆棧跟蹤。拋出異常不會中斷跟蹤,全部計算值能夠從異常中恢復。
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
const myProp = props.myProp
)。否則,若是你在 reaction 中引用了 props.myProp
,那麼 props 的任何改變都會致使 reaction 的從新運行。陷阱
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)) // 讀取整個結構 })
runInAction
是個簡單的工具函數,它接收代碼塊並在(異步的)動做中執行。今天使用react+mobx 寫了個todolist的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'));