重用 Redux 中的 reducer

一、使用 Multireducer 重用相同的 reducer

在使用 Redux 開發應用的過程中,你可能會發現自己經常需要複製黏貼一些重複的reducer。舉個例子,你可能需要實現一個東西的不同列表, 比如已發布的文章列表和未發布的文章列表,你會怎麼實現?相比於實現兩個叫 publishedPosts 和 draftPosts 的 reducer 跟對應的叫 fetchPublishedPosts() 和 fetchDraftPosts() 的 action creator,如果只用一個 posts reducer, 程序會更簡單,維護也會更容易。

但是,你沒辦法在 Redux 中這樣做:

import posts from ./reducers/posts;nnconst reducer = combineReducers({n published: posts, // 錯n draft: posts, // 錯n});n

這樣的話每個 reduer 都會處理同樣的 action。

而 Multireducer 可以讓把同一個 reducer 掛載到不同狀態樹節點,而且自動處理對應的 action。下面我們來看怎麼用 multireducer 來處理上面文章的例子。

安裝

$ npm i multireducer --saven

配置reducer

import multireducer from multireducer;nimport posts from ./reducers/posts;nnconst reducer = combineReducers({n posts: multireducer({n published: posts,n draft : posts,n })n});n

調整相關的組件

import React, { Component, PropTypes } from react;nimport { connect } from react-redux;n// 注意這裡用的是 multireducer 的 bindActionCreatorsnimport { bindActionCreators } from multireducer;nimport { fetch } from ./actions/posts;nnclass PostList extends Component {n static propTypes = {n posts: PropTypes.array.isRequired,n fetch: PropTypes.func.isRequired,n }nn componentWillMount() {n this.props.fetch()n }nn render() {n const { posts } = this.props;n return (n <div>n <ul>n {posts.map(post =>n <li key={post.id}>n {post.title}n </li>n )}n </ul>n </div>n );n }n}nnPostList = connect(n (state, { as }) => ({ posts: state.posts[as] }),n (dispatch, { as }) => bindActionCreators({ fetch }, dispatch, as)n)(PostList);nnexport default PostList;n

然後我們可以這樣用這個 PostList:

<div>n <PostList as="published"/>n <PostList as="draft"/>n</div>n

二、使用高階函數抽象 reducer

上面介紹的 multireducer 適用於同一個東西的不同邏輯,還有一種情況就是不同的東西會有相同的邏輯,比如我們有一個文章列表和一個用戶列表,它們的 reducer 是這樣的:

// reducers/products.jsnconst reducer = (state, { type, payload }) => {n switch (type) {n case products/FETCH_SUCCESS:n return {n ...state,n loading: false,n list: payloadn }n default:n return staten }n}n

// reducers/users.jsnconst reducer = (state, { type, payload }) => {n switch (type) {n case users/FETCH_SUCCESS:n return {n ...state,n loading: false,n list: payloadn }n default:n return staten }n}n

這裡兩個 reducer 幾乎是一樣的,我們可以寫一個叫 list 的 reducer 來把兩個 reducer 相同的部分提取出來:

const list = (actionType) => {n return (state, { type, payload }) => {n switch (type) {n case actionType:n return {n ...state,n loading: false,n list: payloadn }n break;n default:n return staten }n }n}n

然後我們可以這樣用這個 reducer:

// reducers/products.jsnconst reducer = (state, action) => {n switch (action.type) {n // 其他邏輯n default:n return staten }n}nnconst finnalReducer = (state, action) => {n state = list(products/FETCH_SUCCESS)(state, action)n return reducer(state, action)n}n

// reducers/users.jsnconst reducer = (state, action) => {n switch (action.type) {n // 其他邏輯n default:n return staten }n}nnconst finnalReducer = (state, action) => {n state = list(users/FETCH_SUCCESS)(state, action)n return reducer(state, action)n}n

這樣 list 的邏輯就被抽象出來了,但是隨著抽象的東西越來越多,你會發現 reducer 會變成這樣:

// reducers/products.jsnconst reducer = (state, action) => {n switch (type) {n // 其他邏輯n default:n return staten }n}nnconst finnalReducer = (state, action) => {n state = list(products/FETCH_SUCCESS)(state, action)n state = query(products/FETCH_SUCCESS)(state, action)n state = pagination(products/FETCH_SUCCESS)(state, action)n return reducer(state, action)n}n

這些抽象 reducer 的調用過程都很類似,有沒有更優雅的辦法來組合這些抽象的 reducer 呢?

我們來實現一個 composeReducers 組合這些 reducer:

function composeReducers(...reducers) {n return (state, action) => {n if (reducers.length === 0) {n return staten }nn const last = reducers[reducers.length - 1]n const rest = reducers.slice(0, -1)nn return rest.reduceRight((enhanced, reducer) => reducer(enhanced, action), last(state, action))n }n}n

利用這個 composeReducers 我們就可以這樣組合 reducer 了:

// reducers/products.jsnconst reducer = (state, action) => {n switch (type) {n // 其他邏輯n default:n return staten }n}nnconst finnalReducer = composeReducers(n reducer(state, action),n list(products/FETCH_SUCCESS),n query(products/FETCH_SUCCESS),n pagination(products/FETCH_SUCCESS)n)n

至此,我們通過 multireducer 和 composeReducers 實現了 reducer 中相同邏輯的抽象和重用。
推薦閱讀:

React 從青銅到王者系列教程之倔強青銅篇
React 許可證雖嚴苛,但不必過度 react
基於Decorator的組件擴展實踐
[上海] 招前端,移動端,小程序開發(多圖)
React 16 中的異常處理

TAG:React | Redux | 前端框架 |