【ReactNative】react-redux使用心得

657 阅读5分钟

Redux

Redux 单项数据流框架,其特点是严格的数据流控制,开发过程中应遵循3个原则:

  • 唯一数据源
  • 保持状态只读
  • 数据改变只能通过纯函数
1. 唯一数据源

Redux 应用中应保持数据源的唯一性,说白了整个应用中只保持一个 Store,所有组件的数据源就是这个 Store 上的状态,Store 是个树形结构,往往某一个组件或者模块的数据来源于 Store 上的某个节点。

2. 保持状态只读

Redux 强调要改变 Store 的状态只能通过 actionaction 返回对象,提供给 Redux 完成新的状态的组装。

3. 数据改变只能通过纯函数

这里的纯函数就是 Reducer ,其函数签名包含两个参数 stateaction,顾名思义就是通过 action 去改变 state,一般来说是返回一个新的 state,其函数签名大致如下export default (state = initialState, action) => {}

总结一下

Reudx 包含 StoreStateReudceraction,主要为这四部分组成:

  • Store 一个全局的对象, 其包含 Reudcer ,是一个树形结构,每个 Reducer 算一个节点。
  • Reducer 返回一个新的状态,简单来说,通过什么样的 action 产生一个改变了某一个节点的 State
  • State 状态树,同样树形结构,可以理解为 Reducer 下面的子节点,state 的设计应尽量扁平,一个模块控制一个状态节点、避免冗余数据。
  • action 返回一个对象, 供 Reducer 使用。 action 函数返回的对象大致如下{ type: actionType, data: ooxx };这里说一下 actionType:一个常亮,用来区分不同的 action

聪明组件&傻瓜组件(容器组件&展示组件)

所谓聪明、傻瓜只是相对来说,同样也叫容器组件和展示组件。 鉴于专业性,下文一律采用容器组件和展示组件的叫法。容器组件负责将 Store 中的状态,通过 props 传递给展示组件,展示组件只负责渲染页面,无需持有状态。将一个组件拆分为容器组件和展示组件是设计 React 组件的一种模式,和 Redux 无关。


前两者的结合 react-redux

上面讲到,Redux 负责管理状态,组件拆分为容器组件和展示组件。容器组件需要状态,状态来自哪呢?当然是 Redux 。 故,可以将 Redux 和组件做一个结合,达到更好的效果,那就是 react-redux,他帮助开发者抽取了可复用的容器组件,开发者只需关注展示组件即可。 相比于 Redux ,react-redux 多了 Providerconnect 两部分

Provider

理解 Provider 首先要知道 context ,也就是上下文。试想这样一种业务场景:一个多层嵌套的组件结构中,只有最里层的组件需要使用 store ,首先想到的是用 props 传递,为了把 store 从最外层传递到最里层,就要中间的所有组件都增加对 store的支持 ,这样无疑是灾难,很麻烦。React 提供了 context 的支持,就是说一个树型结构中的所有组件都能访问一个对象(上下文)。为了让树形结构支持 context ,需要父组件声明对 context 的支持, Provider 就提供了这样的功能(也是一个组件,增加了 context)。所以我们会看到 Provider 需要传递一个 store 属性,并且位于根组件的位置,这样应用中所有的组件都会通过 context 共享这个 store 。react-redux 提供了创建 Store 的方法。

connect

一个函数,负责展示组件和容器组件的连接。大致是这样export default connect(mapStateToProps, mapDispatchToProps)(SearchBar) 这里边其实是两次函数的执行,首先 connect 函数执行并返回了另一个函数然后执行,参数是展示组件。

  • mapStateToProps 一个返回对象的函数,可选的。通过函数签名可以知道是将 Store 中的某一个 State 转化为展示组件所需要的 props,决定暂时组件显示什么样的数据。
  • mapDispatchToProps 一个返回对象的函数,可选的。展示组件不能只负责展示,当然也有一定的交互,也就是触发 action 。在 react-redux 中触发一个 action 是通过 dispatch 执行的。所以这个函数是将 dispatch 转换为 props 供展示组件使用。

其实 connect 在执行的过程中实际上产生了一个无名的 React 组件,这个组件定制了 shouldComponentUpdate 函数的实现,实现逻辑是比对这次传递给内层展示组件的 props 和上一次的 props ,因为负责“组件看起来怎样”的展示组件是一个无状态的组件,他的渲染完全由传入的 props 决定,如果 props 没有变化,那就可以认为渲染结果肯定一样。就不需要经过 VirtualDOM 做渲染。

总结一下

不难理解 react-redux 告诉我们,展示组件仅仅负责展示,不需要持有任何状态,展示组件的所有 stateaction 全部来源于 props ,容器组件通过 props 传递给展示组件。


示例代码

Demo地址
  • actionType
export const HOMEPAGE_SHOWSEARCHBAR = 'HOMEPAGE/SHOWSEARCHBAR';

export const HOMEPAGE_MENU_PAGECHANGE = 'HOMEPAGE/MENU/PAGECHANGE';

  • action
export const searchBarFetch = (text) => ({
  type: HOMEPAGE_FETCH_SEARCHBAR,
  text: text
});

export const updateHomePageMenuPage = (page) => ({
  type: HOMEPAGE_MENU_PAGECHANGE,
  currentPage: page
});
  • State (我自认为不是很扁平,懒着改了)
const initialState = {
    homepage: {
        menuInfo: {
            items: common.menuInfos,
            currentPage: 0
        },
        gridInfos: [],
        sections: [{
            title: '',
            data: []
        }],
    },

    searchBar: {
        text: '搜一下'
    }
};
  • Reducer Reducer 可以是多个,一般一个模块一个 Reducer

export default (state = initialState, action) => {
    switch (action.type) {
        case HOMEPAGE_FETCH_SEARCHBAR:
            {
                return {
                    ...state,
                    searchBar: {
                        text: action.text
                    }
                };
            }

        case HOMEPAGE_MENU_PAGECHANGE:
            {
                return {
                    ...state,
                    homepage: {
                        menuInfo: {
                            items: state.homepage.menuInfo.items,
                            currentPage: action.currentPage
                        },
                        gridInfos: state.homepage.gridInfos,
                        sections: state.homepage.sections
                    }
                }
            }
    }
    return state;
};
  • Store react-redux 提供了 合并多个 Rducer 的方法,和创建 Store 的方法
const reducers = combineReducers({
    homepageReudcer
});

export default createStore(reducers);
  • 展示组件
class HomeMenu extends Component {

    render() {

        const {menuInfos, currentPage} = this.props;

        const items = menuInfos.map(({title, icon}) => (<HomeMenuItem title={title} icon={icon} key={title}/>));

        const pageCount = Math.ceil(items.length / 10);
        let menuViews = [];
        for (let i = 0; i < pageCount; i++) {
            const itemSlices = items.slice(i * 10, i * 10 + 10);
            const view = <View style={styles.itemsView} key={i}>
                {itemSlices}
            </View>
            menuViews.push(view);
        }

        return (
            <View style={styles.container}>
                <ScrollView
                    horizontal
                    pagingEnabled={true}
                    showsHorizontalScrollIndicator={false}
                    onScroll={this._onScroll}>
                    {menuViews}
                </ScrollView>
                <PageControl
                    style={styles.pageControl}
                    numberOfPages={pageCount}
                    currentPage={currentPage}
                    currentPageIndicatorTintColor={color.primary}
                    pageIndicatorTintColor={color.gray}/>
                <View style={styles.line}/>
                <HomeGridView/>
                <View style={styles.line}/>
            </View>

        );
    }

    _onScroll = (event) => {
        const x = event.nativeEvent.contentOffset.x;
        const page = Math.round(x / common.screen.width);

        const {currentPage, setCurrentPage} = this.props;
        if (currentPage !== page) {
            setCurrentPage(page);
        }
    }
}

const styles = StyleSheet.create({
    container: {
        backgroundColor: 'white',
    },
    itemsView: {
        flexDirection: 'row',
        flexWrap: 'wrap',
        width: common.screen.width
    },
    pageControl: {
        margin: 10
    },
    line: {
        backgroundColor: color.paper,
        width: common.screen.width,
        height: 10,
    }
});

const mapStateToProps = (state) => ({menuInfos: state.homepageReudcer.homepage.menuInfo.items, currentPage: state.homepageReudcer.homepage.menuInfo.currentPage});
const mapDispatchToProps = (dispatch) => ({
    setCurrentPage: (page) => {
        dispatch(updateHomePageMenuPage(page));
    }
})

export default connect(mapStateToProps, mapDispatchToProps)(HomeMenu)


完!!!