react-redux 使用心得

在 iOS 开发领域工作3年半了,都说金三银四,今天四月最后一天,工作还没有落实。想当初还是个小白的时候,一天能安排4、5个面试,如今找工作如此之难,真是慌得一比。当然,不能怨天尤人,总归是自己实力不够硬朗。
最近闲来无事,捡起了快被自己忘的一干二净的 ReactNative ,简单了写了个小项目,算是温故知新了。


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

提供了 Store 的容器组件,Provider 应位于根组件最顶层,管理所有子组件。其中会检查这一次渲染时 Store 代表的 props 和上次是否一致,这样做避免了多次渲染用了不同的 Store ,所以项目中应只有一个 Store。react-redux 提供了创建 Store 的方法。

connect

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

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

不难理解 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)


完!!!
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 228,316评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,481评论 3 415
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 176,241评论 0 374
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,939评论 1 309
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,697评论 6 409
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,182评论 1 324
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,247评论 3 441
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,406评论 0 288
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,933评论 1 334
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,772评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,973评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,516评论 5 359
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,209评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,638评论 0 26
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,866评论 1 285
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,644评论 3 391
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,953评论 2 373

推荐阅读更多精彩内容