react-redux 使用心得

在 iOS 開發(fā)領(lǐng)域工作3年半了,都說金三銀四,今天四月最后一天,工作還沒有落實(shí)。想當(dāng)初還是個(gè)小白的時(shí)候,一天能安排4、5個(gè)面試,如今找工作如此之難,真是慌得一比。當(dāng)然,不能怨天尤人,總歸是自己實(shí)力不夠硬朗。
最近閑來無(wú)事,撿起了快被自己忘的一干二凈的 ReactNative ,簡(jiǎn)單了寫了個(gè)小項(xiàng)目,算是溫故知新了。


Redux

Redux 單項(xiàng)數(shù)據(jù)流框架,其特點(diǎn)是嚴(yán)格的數(shù)據(jù)流控制,開發(fā)過程中應(yīng)遵循3個(gè)原則:

  • 唯一數(shù)據(jù)源
  • 保持狀態(tài)只讀
  • 數(shù)據(jù)改變只能通過純函數(shù)
1. 唯一數(shù)據(jù)源

Redux 應(yīng)用中應(yīng)保持?jǐn)?shù)據(jù)源的唯一性,說白了整個(gè)應(yīng)用中只保持一個(gè) Store,所有組件的數(shù)據(jù)源就是這個(gè) Store 上的狀態(tài),Store 是個(gè)樹形結(jié)構(gòu),往往某一個(gè)組件或者模塊的數(shù)據(jù)來源于 Store 上的某個(gè)節(jié)點(diǎn)。

2. 保持狀態(tài)只讀

Redux 強(qiáng)調(diào)要改變 Store 的狀態(tài)只能通過 action ,action 返回對(duì)象,提供給 Redux 完成新的狀態(tài)的組裝。

3. 數(shù)據(jù)改變只能通過純函數(shù)

這里的純函數(shù)就是 Reducer ,其函數(shù)簽名包含兩個(gè)參數(shù) state、action,顧名思義就是通過 action 去改變 state,一般來說是返回一個(gè)新的 state,其函數(shù)簽名大致如下export default (state = initialState, action) => {}

總結(jié)一下

Reudx 包含 Store、StateReudcer、action,主要為這四部分組成:

  • Store
    一個(gè)全局的對(duì)象, 其包含 Reudcer ,是一個(gè)樹形結(jié)構(gòu),每個(gè) Reducer 算一個(gè)節(jié)點(diǎn)。
  • Reducer
    返回一個(gè)新的狀態(tài),簡(jiǎn)單來說,通過什么樣的 action 產(chǎn)生一個(gè)改變了某一個(gè)節(jié)點(diǎn)的 State
  • State
    狀態(tài)樹,同樣樹形結(jié)構(gòu),可以理解為 Reducer 下面的子節(jié)點(diǎn),state 的設(shè)計(jì)應(yīng)盡量扁平,一個(gè)模塊控制一個(gè)狀態(tài)節(jié)點(diǎn)、避免冗余數(shù)據(jù)。
  • action
    返回一個(gè)對(duì)象, 供 Reducer 使用。 action 函數(shù)返回的對(duì)象大致如下{ type: actionType, data: ooxx };這里說一下 actionType:一個(gè)常亮,用來區(qū)分不同的 action

聰明組件&傻瓜組件(容器組件&展示組件)

所謂聰明、傻瓜只是相對(duì)來說,同樣也叫容器組件和展示組件。 鑒于專業(yè)性,下文一律采用容器組件和展示組件的叫法。容器組件負(fù)責(zé)將 Store 中的狀態(tài),通過 props 傳遞給展示組件,展示組件只負(fù)責(zé)渲染頁(yè)面,無(wú)需持有狀態(tài)。將一個(gè)組件拆分為容器組件和展示組件是設(shè)計(jì) React 組件的一種模式,和 Redux 無(wú)關(guān)。


前兩者的結(jié)合 react-redux

上面講到,Redux 負(fù)責(zé)管理狀態(tài),組件拆分為容器組件和展示組件。容器組件需要狀態(tài),狀態(tài)來自哪呢?當(dāng)然是 Redux 。 故,可以將 Redux 和組件做一個(gè)結(jié)合,達(dá)到更好的效果,那就是 react-redux,他幫助開發(fā)者抽取了可復(fù)用的容器組件,開發(fā)者只需關(guān)注展示組件即可。
相比于 Redux ,react-redux 多了 Providerconnect 兩部分

Provider

提供了 Store 的容器組件,Provider 應(yīng)位于根組件最頂層,管理所有子組件。其中會(huì)檢查這一次渲染時(shí) Store 代表的 props 和上次是否一致,這樣做避免了多次渲染用了不同的 Store ,所以項(xiàng)目中應(yīng)只有一個(gè) Store。react-redux 提供了創(chuàng)建 Store 的方法。

connect

一個(gè)函數(shù),負(fù)責(zé)展示組件和容器組件的連接。大致是這樣export default connect(mapStateToProps, mapDispatchToProps)(SearchBar)
這里邊其實(shí)是兩次函數(shù)的執(zhí)行,首先 connect 函數(shù)執(zhí)行并返回了另一個(gè)函數(shù)然后執(zhí)行,參數(shù)是展示組件。

  • mapStateToProps
    一個(gè)返回對(duì)象的函數(shù),可選的。通過函數(shù)簽名可以知道是將 Store 中的某一個(gè) State 轉(zhuǎn)化為展示組件所需要的 props,決定暫時(shí)組件顯示什么樣的數(shù)據(jù)。
  • mapDispatchToProps
    一個(gè)返回對(duì)象的函數(shù),可選的。展示組件不能只負(fù)責(zé)展示,當(dāng)然也有一定的交互,也就是觸發(fā) action 。在 react-redux 中觸發(fā)一個(gè) action 是通過 dispatch 執(zhí)行的。所以這個(gè)函數(shù)是將 dispatch 轉(zhuǎn)換為 props 供展示組件使用。
總結(jié)一下

不難理解 react-redux 告訴我們,展示組件僅僅負(fù)責(zé)展示,不需要持有任何狀態(tài),展示組件的所有 state、action 全部來源于 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 (我自認(rèn)為不是很扁平,懶著改了)
const initialState = {
    homepage: {
        menuInfo: {
            items: common.menuInfos,
            currentPage: 0
        },
        gridInfos: [],
        sections: [{
            title: '',
            data: []
        }],
    },

    searchBar: {
        text: '搜一下'
    }
};
  • Reducer
    Reducer 可以是多個(gè),一般一個(gè)模塊一個(gè) 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 提供了 合并多個(gè) Rducer 的方法,和創(chuàng)建 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)


完?。。?/h5>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容