基本工作流程
- 用户通过action creator创建action并派发dispatch action。
- store收到之后自动调用相应的reducer,传入当前的state和收到的action,返回新的state。
- state一旦发生变化,会调用监听函数,通知订阅了store的组件(store.subscribe(listener))。
- Reacr component中可以通过store.getState()获取到store的状态。
中间件
上述流程中,我们的reducer只能处理一些同步的、无副作用的action,那一步操作怎么办?像数据获取这些有副作用的操作怎么办?这时我们可以使用redux中的新工具——中间件(middleware)。
所谓中间件,就是对原来的store.dispatch进行封装,在发出action和执行reducer之间添加一些操作。
中间件的用法
我们既可以使用现有的中间件(redux-thunk、redux-logger等),也可以自定义中间件:
const middleWare = (store) => (next) => (action) => {
//派发action之前进行一些操作
...
//dispatch这个action
next(action)
//action执行后
...
}
要使用中间件,只需在createStore的时候将applyMiddlewares(thunk, logger)参数传入即可。
异步操作的基本思路(使用redux-thunk)
假设说我们要向服务器请求数据,而且这个数据在多个模块中要用到,那么我们可以考虑在store中获取数据,并使用redux-thunk中间件,派发三种action,分别是
- fetchDataStart
- fetchDataSuccess
- fetchDataFail
维护state对象,包含loading、data、error三个属性,分别表示是否加载数据中,获取到的data,出错信息。
我们的目标是将获取数据的操作放到store中,也就是在component中dispatch一个类似giveMeData这样的action,然后store收到这个action之后自动执行API请求、请求成功或失败后的处理。
//在react component中
componentDidMount(){
dispatch(giveMeDataActionCreator())
}
一般的action creator返回的都是一个对象,这没有办法满足我们的需求,这时我们就要引入redux-thunk这个中间件,它封装了dispatch这个方法,让dispatch多支持一种参数类型——函数类型。
giveMeDataCreator返回一个函数,带有dispatch和getState两个redux方法。
export const giveMeDataCreator = () : ThunkAction => (dispatch, getState) => {
dispatch(fetchDataStartCreator());
try{
const { data } = await axios.get(...);
dispatch(fetchDataSuccessCreator(data));
} catch (e) {
dispatch(fetchDataFailCreator(e.message));
}
}
在这个action creator中,我们连续发送两个action,让reducer完成相关操作。而我们的中间件会持续执行,直到异步逻辑全部结束。
普通的dispatch只支持对象类型的参数,redux-thunk这个中间件,添加了函数类型参数的支持,类似的,redux-promise添加了对promise类型参数的支持。
UI组件和容器组件
React-Redux将所有组件分为UI组件和容器组件两类。
UI组件特征:
- 只负责UI的呈现,不带有任何业务逻辑。
- 没有状态(即不使用this.state这个变量)
- 所有参数由this.props提供
- 不使用任何Redux的API
容器组件特征:
- 负责管理数据和业务逻辑,不负责UI的呈现
- 带有内部状态
- 使用Redux的API
当一个组件既涉及UI呈现,又包含业务逻辑处理,我们可以把它拆分成这样的结构:外面一个容器组件,里面包含一个UI组件,前者负责从外部获取数据传给内部,后者负责根据传来的数据渲染出视图。
connect()
react-redux方法提供connect方法用于从UI组件中生成容器组件。使用方法如下:
export const Home =
connect(
mapStateToProps,
mapDispatchToProps)
(UIComponent)
这个方法后面跟两个括号,第一个括号接收两个参数mapStateToProps和mapDispatchToProps。第二个括号传入需要包裹的UI组件。
mapStateToProps
负责输入逻辑,将外部的state映射为内部的props。
const mapStateToProps = (state: RootState, ownProps) => {
return {
loading: state.recommendProducts.loading,
error: state.recommendProducts.error,
productList: state.recommendProducts.productList
}
};
是一个函数,接收外部的state为参数,返回一个对象,里面的每个键值对就是一个映射,比如说,通过this.props.loading,可以获取到store中state的loading属性。mapStateToProps会订阅store,当state更新时,会自动执行,重新计算UI组件的参数,触发重新渲染。
如果connect方法省略这个参数,UI组件就不会订阅store,也就是state的更新不会触发UI组件更新。
mapDispatchToProps
用来建立UI组件参数到store.dispatch方法的映射。它可以是一个函数,也可以是一个对象。
如果是函数,他会接收dispatch和ownProps作为参数,返回一个对象。
const mapDispatchToProps = (dispatch) => {
return {
giveMeData: () => {
dispatch(giveMeDataActionCreator())
}
}
}
如果mapDispatchToProps是对象,它的每一个键值是一个函数,被当作action creator。
const mapDispatchToProps = {
giveMeData: () => {
type:...,
payload: ...
}
}
Provider
connect方法能生成容器组件,但要让容器获取到store中的state,才能生成UI组件的参数。
一种方便的方法是,使用react-redux提供的Provider组件。
import { Provider } from 'react-redux'
<Provider store={store}>
<App />
</Provider>
这样的包裹,使得App内所有子组件都可以通过context获取到state。
参考资料: