這一節主要看mobx怎麼實現asynchronous actionsjavascript
輸入地名,查詢天氣,利用openweathermap apihtml
observable觀察數據:location地點、temperature溫度java
observer響應式組件:git
actions: location輸入更新、點擊add異步查詢天氣github
App:Container組件json
// App const App = observer(({ temperatures }) => ( <ul> <TemperatureInput temperatures={temperatures} /> {temperatures.map(t => <TView key={t.id} temperature={t} /> )} <DevTools/> </ul> )) ReactDOM.render( <App temperatures={temps}/>, document.getElementById('root') )
TemperatureInput:地址輸入Container組件api
@observer class TemperatureInput extends React.Component { @observable input = '' render () { return ( <li> Destination <input value={this.input} onChange={this.onChange}/> <button onClick={this.onSubmit}>Add</button> </li> ) } @action onChange = e => { this.input = e.target.value } @action onSubmit = () => { this.props.temperatures.push(new Temperature(this.input)) this.input = '' } }
TView:地點天氣列表Container組件promise
@observer class TView extends React.Component { render () { const t = this.props.temperature return ( <li key={t.id} onClick={() => this.onTemperatureClick()}>{t.location}: {t.loading ? 'loading...' : t.temperature}</li> ) } @action onTemperatureClick = () => { this.props.temperature.inc() } }
Temperature: Data Model組件app
class Temperature { id = Math.random() @observable unit = 'C' @observable temperatureCelsius = 25 @observable location = 'Amsterdam, NL' @observable loading = true constructor (location) { this.location = location this.fetch() } @computed get temperatureKelvin () { console.log('calculating Kelvin') return this.temperatureCelsius * (9 / 5) + 32 } @computed get temperatureFahrenheit () { console.log('calculating Fahrenheit') return this.temperatureCelsius + 273.15 } @computed get temperature () { console.log('calculating temperature') switch (this.unit) { case 'K': return this.temperatureKelvin + '°K' case 'F': return this.temperatureFahrenheit + '°F' case 'C': return this.temperatureCelsius + '°C' default: return this.temperatureCelsius + '°C' } } @action fetch () { window.fetch(`http://api.openweathermap.org/data/2.5/weather?appid=${APPID}&q=${this.location}`) .then(res => res.json()) .then(action(json => { this.temperatureCelsius = json.main.temp - 273.15 this.loading = false })) } @action setUnit (newUnit) { this.unit = newUnit } @action setCelsius (degrees) { this.temperatureCelsius = degrees } @action('update temperature and unit') setTemperatureAndUnit (degrees, unit) { this.setUnit(unit) this.setCelsius(degrees) } @action inc() { this.setCelsius(this.temperatureCelsius + 1) } }
action:動做是任何用來修改狀態的東西;只會對當前運行的函數做出反應,而不會對當前運行函數所調用的函數(不包含在當前函數以內)做出反應,這意味着若是 action 中存在 setTimeout
、promise 的 then
或 async
語句,而且在回調函數中某些狀態改變了,那麼這些回調函數也應該包裝在 action
中。dom
wrong!
mobx.configure({ enforceActions: true }) // 不容許在動做以外進行狀態修改 class Store { @observable githubProjects = [] @observable state = "pending" // "pending" / "done" / "error" @action fetchProjects() { this.githubProjects = [] this.state = "pending" fetchGithubProjectsSomehow().then( projects => { const filteredProjects = somePreprocessing(projects) this.githubProjects = filteredProjects this.state = "done" }, error => { this.state = "error" } ) } }
上面的例子會報錯,由於fetchGithubProjectsSomehow().then(cb)中的cb不是fetchProjects動做的一部分,由於動做只會做用於當前棧,要修復它,簡單思想是將promise的then變成當前動做的一部分
方案1:使用action.bound
right!
mobx.configure({ enforceActions: true }) class Store { @observable githubProjects = [] @observable state = "pending" // "pending" / "done" / "error" @action fetchProjects() { this.githubProjects = [] this.state = "pending" fetchGithubProjectsSomehow().then(this.fetchProjectsSuccess, this.fetchProjectsError) } @action.bound fetchProjectsSuccess(projects) { const filteredProjects = somePreprocessing(projects) this.githubProjects = filteredProjects this.state = "done" } @action.bound fetchProjectsError(error) { this.state = "error" } }
方案2:使用action關鍵字包裝promise回調函數,須要給它們命名
right!
mobx.configure({ enforceActions: true }) class Store { @observable githubProjects = [] @observable state = "pending" // "pending" / "done" / "error" @action fetchProjects() { this.githubProjects = [] this.state = "pending" fetchGithubProjectsSomehow().then( // 內聯建立的動做 action("fetchSuccess", projects => { const filteredProjects = somePreprocessing(projects) this.githubProjects = filteredProjects this.state = "done" }), // 內聯建立的動做 action("fetchError", error => { this.state = "error" }) ) } }
咱們的開頭的示例就是採用這種方式
若是我只想在動做中運行回調函數的狀態修改部分,而不是爲整個回調建立一個動做,這就造成了在整個過程結束時儘量多的對全部狀態進行修改
方案3:runInAction
mobx.configure({ enforceActions: true }) class Store { @observable githubProjects = [] @observable state = "pending" // "pending" / "done" / "error" @action fetchProjects() { this.githubProjects = [] this.state = "pending" fetchGithubProjectsSomehow().then( projects => { const filteredProjects = somePreprocessing(projects) // 將‘「最終的」修改放入一個異步動做中 runInAction(() => { this.githubProjects = filteredProjects this.state = "done" }) }, error => { // 過程的另外一個結局:... runInAction(() => { this.state = "error" }) } ) } }
還有async/await的處理,flows的高級用法,詳見:https://cn.mobx.js.org/best/actions.html