这一节主要看mobx怎么实现asynchronous actions
1 要实现的demo功能
输入地名,查询天气,利用openweathermap api
2 思想
observable观察数据:location地点、temperature温度
observer响应式组件:
actions: location输入更新、点击add异步查询天气
3 代码块
App:Container组件
// 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组件
@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组件
@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组件
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)
}
}
4 回顾
action:动作是任何用来修改状态的东西;只会对当前运行的函数作出反应,而不会对当前运行函数所调用的函数(不包含在当前函数之内)作出反应,这意味着如果 action 中存在 setTimeout
、promise 的 then
或 async
语句,并且在回调函数中某些状态改变了,那么这些回调函数也应该包装在 action
中。
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