众所周知,React中数据通信是单向的,即父组件可以通过props向子组件传递数据,而子组件却不能向父组件传递数据。要实现子组件向父组件传递数据的需求,需要父组件提供一个修改数据的方法,当页面越来越多的时候,数据的管理就会变得异常复杂。
并且,每次数据的更新都需要调用setState,特别是涉及到跨组件通信的问题就会很麻烦。在React开发中,为了解决跨组件通信的问题,业界开发了一大批状态管理框架,目前比较常用的React状态管理框架有Flux、Redux和Mobx等几个。
其中,Flux是Facebook用于建立客户端Web应用的前端架构,它利用一个单向数据流的方式补充了React的组合视图组件,解决了MVC技术架构中数据流管理混乱的问题。Redux则是由Dan Abramov开源的一款前端状态管理框架,Redux框架由Action、Store和Reducers三部分组成,所有组件的数据都存储到Store对象中,每个组件只需要改变Store中的数据,当Store数据发生变化时就会其他订阅的组件执行数据更新。Mobx是一个面向对象的状态管理框架,它与Redux的最大区别是可以直接修改数据,精准的通知UI进行刷新,而不是Redux的广播。
可以发现,Redux特别适合用在需要集中式管理数据场景中。多个组件使用同一个数据源,维护同一个数据样本,进而保持各个组件之间数据的一致性。react-redux是Redux状态框架在React中的技术实现,对于熟悉Redux状态管理框架开发者来说,学习react-redux将会显得非常容易。
在Redux状态框架中,Redux将状态管理分为Action、Store和Reducers三部分。其中,Redux将应用程序的状态存储到Store中,组件通过dispatch()方法触发Action,Store接收Action并将Action转发给Reducer,Reducer根据Action类型对状态数据进行处理并将处理结果返回给Store,其他组件通过订阅Store状态的来刷新自身的状态,整个框架的工作流程如图3-9所示。
下面以计数器为例来说明Redux的基本使用。首先,创建一个action.js文件,用来存放Action行为事件,如下所示。
export const ADD = 'ADD' export const MINUS = 'MINUS'
然后,创建一个reducer.js文件,用来处理业务逻辑的更新,并将处理的结果返回给Store,如下所示。
import {ADD, MINUS} from './action'; function reducer (state = {count: 0}, action) { switch(action.type) { case ADD: return {count: state.count + 1} case MINUS: return {count: state.count - 1} default: return state } } export default reducer;
Reducer是一个纯函数,接收State和Action两个参数。其中,State是旧的状态,不可以直接修改,Reducer会根据Action的类型来生成不同的新State,并将结果返回给Store。
接下来,创建一个全局的Store对象,用来存放应用的状态数据,创建时需要使用Store提供的createStore()方法,如下所示。
import { createStore } from 'redux' import reducer from './reducer'; const store = createStore(reducer) export default store
除了createStore()方法外,创建的Store还有以下几个方法可以调用。
为了实现计数器加减的功能,还需要在组件的生命周期函数中添加订阅事件,并在组件销毁时解决订阅,如下所示。
class CounterPage extends React.Component { constructor(props){ super(props) this.state = { number: store.getState().count } } componentDidMount () { this.unSubscribe = store.subscribe(() => { this.setState({ number: store.getState().count }) }) } componentWillUnmount () { this.unSubscribe && this.unSubscribe() } render() { return ( <View style={styles.ct}> <Text>{this.state.number}</Text> <Button title="加1" onPress={() => store.dispatch({type: 'ADD'})}/> <Button title="减1" onPress={() => store.dispatch({type: 'MINUS'})}/> </View> ); } } const styles = StyleSheet.create({ ct: { flex: 1, justifyContent: 'center', alignItems: 'center', }, }); export default CounterPage;
在上面的代码中,我们通过store.getState()方法来获取最新的State,而执行加减操作时通过store.dispatch()方法派发Action给Store。可以发现,在类组件中使用Redux还是挺繁琐的,需要开发者自己管理组件的状态数据,而如果改用React Hook就要简单许多。
在React Hook中使用Redux需要使用react-redux库提供的useSelector()与useDispatch()两个函数。其中,useSelector()函数可以用来获取状态值,而useDispatch()则可以用来修改状态数据,如下所示。
import { useSelector, useDispatch } from 'react-redux' const CounterPage = () => { const count = useSelector(state => state.count) const dispatch = useDispatch() return ( <View style={styles.ct}> <Text>{count}</Text> <Button title='加1' onPress={() => dispatch({type: 'ADD'})}/> <Button title='减1' onPress={() => dispatch({type: 'MINUS'})}/> </View> ); } const styles = StyleSheet.create({ …. //省略代码 }); export default CounterPage
可以发现,相比于类组件来说,使用React Hook实现就要简洁许多。首先,我们使用useSelector()函数获取Store中的状态,然后再使用useDispatch()函数派发事件。
最后,使用Redux在让不同组件之间共享状态数据时,还需要使用react-redux库提供的Provider包裹应用组件,如下所示。
const App = () => { return ( <Provider store={store}> <CounterPage /> </Provider> ); };
重新运行代码,就实现了计数器的功能,如下图所示。
最后,需要说明的是,使用Redux进行状态管理时,应注意以下几点:
• 应用中有且仅有一个Store,该Store存储了整个应用的状态。
• State是只读的,修改State只能通过派发Action事件,为了描述Action改变State的过程,需要使用Reducer纯函数。
• 单一数据源让多个React组件之间的通信更加方便,也有利于状态的统一管理。