单页应用通常情况下都需要在进入某一个页面的时候去获取该页面的数据,当然 React 可以很轻松地完成,最简单的可以在 componentWillMount 里写获取数据的代码:
class MyComponent extends React.Component {
...
componentWillMount() {
fetchData()
}
...
}
然而由于获取数据的方法通常是异步的,这么处理会有一些问题不能很好地解决:
- 不能自由地控制取数据的时机:比如很多时候我们希望数据获取完成才跳转到这个页面
- 如果需要服务端同构,服务端渲染的页面将没有任何数据 —— 这样的服务端同构也就没有意义了
- 获取页面数据的代码和其他代码混在一起,代码不好管理
- 使用了 componentWillMount 方法的话组件将不能写成无状态组件
3、4 还比较好处理,比如我们可以用装饰器来把 componentWillMount 里获取数据的方法抽取出来:
// fetchData
function fetchDataDecorator(func) {
return async (Component) => {
Component.componentWillMount = () => func()
}
}
// MyComponent
@fetchDataDecorator(fetchData)
class MyComponent extends React.Component {
...
}
上述 MyComponent 也可以写成无状态组件了。不过 1、2 的问题就不太好处理了,可以采用的思路是依赖 react-router 的 onEnter 方法。
redux-async-connect 就是用来异步获取的一个库,这里简要介绍一下该库的用法。该库比较核心的是一个装饰器 asyncConnect(需要注意的是该库 github 上的文档是旧版的,如果使用的是 1.x 版本以后的用法参照如下,其接受的将是一个数据而非对象)
import { ReduxAsyncConnect, asyncConnect } from 'redux-async-connect'
@asyncConnect([{
promise: ({ store, helpers }) => fetchData(),
key: 'fetch-data-for-my'
}])
class MyComponent extends React.Component {
...
}
asyncConnect 接受的是一个数组对象,其中每个对象都含有一个 promise 属性,如果 promise 是一个 Promise 方法,那么它就会被异步执行,另一个可选属性是 key,如果带有 key 则将该异步请求的请求状态存储到 store 中,可以直接在该组件中通过 this.props[key] 来获取,[key].loading 表示请求中,[key].loaded 表示请求完成。同时该库也支持服务器端同构,服务端可以用该库提供的 loadOnServer 来请求异步数据。
不过可惜的是该库只支持先获取异步数据,等数据请求完成后才发生页面跳转,而某些场合我们也希望能先发生页面跳转再请求数据,因此我们需要对该库做一个改造:
import { asyncConnect } from 'redux-async-connect'
export default function deferredAsyncConnect(items) {
const asyncItems = __CLIENT__ ? items.map(each => {
const { promise, deferred } = each
const newPromise = !deferred ?
async (options) => await promise(options) :
(options) => { promise(options) }
return {
...each, promise: newPromise }
}) : items
return asyncConnect(asyncItems)
}
@deferredAsyncConnect([{
promise: ({ store, helpers }) => fetchData(),
// if want to fetch data deferred, set deferred property to true
deferred: true,
}])
class MyComponent extends React.Component {
...
}
杏树林研发 邹世程