让前端飞Web前端之路技术干货

以Favorite组件为例分析 RN+Redux 状态管理与数据

2017-08-02  本文已影响459人  changchao

无论使用React还是ReactNative,Redux总是绕不过的结(劫?解?)。近日在实现一个本地收藏组件的时候,浅显但还算完整的使用了Redux来管理收藏的状态与同步,因而有了本文(文末有demo视频)。

0 准备

先上个参考文献甩锅。我讲不清的,请查看参考文献,还有个小Sample搭配

1 需求

Favorite组件,本文主角,其实就是一个收藏按钮(图1)。用户点击按钮,按钮变实心,收藏此篇文章,将这篇文章加入收藏列表(图3),同时在所有显示这篇文章的地方,自动同步收藏状态。为简单描述,省略server交互过程,我们假设收藏文章的信息都存在本地。

图2-文章详情页 图3-收藏列表

2 实现

因为需求中明显涉及到跨组件状态的同步,所以用redux也就是很自然的了,react配合redux通常需要实现“四大金刚”:Action,Reducer,Container,Component,下面一一道来。

import * as types from '../constants/ActionTypes';
export function addFavorite(article) {
    return {
        type: types.ADD_FAVORITE,//常量定义文件中定义好的常量字符串
        article//收藏的文章object,{id:123,title:'hello',....}
    };
}
export function removeFavorite(article) {
    return {
        type: types.REMOVE_FAVORITE,
        article
    };
}
import * as types from '../constants/ActionTypes';
import * as _ from 'lodash'

const initialState = {
    favoriteItems:[]//存储用户收藏的article列表,这一行只是设初值
};
//Reducer主体:很纯粹的一个函数,接受老的state和action,返回新的state
export default function favorite(state = initialState, action) {
    switch (action.type) {
        case types.ADD_FAVORITE://收藏时对应的操作,将action带过来的article加到列表中,仔细看此处的操作,返回的是《新的》state
            return Object.assign({}, state, {
                favoriteItems: insertItem(state.favoriteItems, action.article)
            });
        case types.REMOVE_FAVORITE://相对应的,删除操作
            return Object.assign({}, state, {
                favoriteItems: removeItem(state.favoriteItems, action.article)
            });
        default:
            return state;
    }
}
//这两个工具函数就是为了让我们在每次数据更新时,返回的都是全新的article列表
function insertItem(array, item) {
    let newArray = array.slice();
    newArray.splice(0, 0, item);
    return newArray;
}

function removeItem(array, item) {
    let newArray = array.slice();
    _.remove(newArray,{id: item.id});
    return newArray;
}
两种组件对比
import React, {PropTypes} from 'react';
import Icon from 'react-native-vector-icons/Ionicons';
import * as _ from 'lodash';
import ToastUtil from "../utils/ToastUtil";
import * as COLOR from "../constants/Colors";
import * as creaters from '../actions/favorite';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
//容器组件接受的props
const propTypes = {
    clickedName: PropTypes.string,
    unClickedName: PropTypes.string,
    favoriteItems: PropTypes.array,//这是个特殊的props,来源于redux store,下面会看到,这个是自动注入的
    article: PropTypes.object
};
//展示组件定义
class FavoriteIcon extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            iconName: ''
        };
    }
    
    /*请注意,这儿针对组件渲染做了一点儿性能优化,因为本例中在任何收藏按钮上点击,都将修改
     FavoriteItems这个list,而只要这个list修改,就会触发所有收藏按钮的重新渲染判断,这是不必要的,所以
     此处针对自己是否在新旧FavoriteItems做了一个异或,只有异或结果为TRUE,才表示需要update
    */
    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.article !== nextProps.article){
            return true;
        }
        return (_.some(nextProps.favoriteItems, {id: this.props.article.id}) ^
        _.some(this.props.favoriteItems, {id: this.props.article.id}))
    }

    render() {
        let {clickedName, unClickedName, favoriteItems, article, favoriteActions} = this.props;
        return (
            <Icon.Button
                name={_.some(favoriteItems, {id: article.id}) ? clickedName : unClickedName}//显示实心的已收藏还是空心的未收藏
                backgroundColor="transparent"
                underlayColor="transparent"
                color={COLOR.HeaderText}
                activeOpacity={0.8}
                onPress={() => {
                    if (_.some(favoriteItems, {id: article.id})) {
                        favoriteActions.removeFavorite(article);//关键一步:我们在此处调用了注入进来的action,dispatch了一个remove favorite action
                        ToastUtil.showShort('Article removed from favorite');
                    } else {
                        favoriteActions.addFavorite(article);// 上同,dispatch add action
                        ToastUtil.showShort('Article marked as favorite');
                    }
                }}
            />
        )
    }
}
// 容器组件定义,可以看到,这个组件什么都没做,只是引用了展示组件,并且把props穿进去,很好理解吧?
class Container extends React.Component {
    render() {
        return <FavoriteIcon {...this.props}/>
    }
}
//关键性操作,将redux store中的favoriteItems 注入到容器组件的props中
const mapStateToProps = (state) => {
    const {favoriteItems} = state.favorite;
    return {
        favoriteItems
    };
};
//关键性操作,将redux store中操作favoriteitems的action注入到容器组件的props中
const mapDispatchToProps = (dispatch) => {
    const favoriteActions = bindActionCreators(creaters, dispatch);
    return {
        favoriteActions
    };
};

Container.propTypes = propTypes;
Container.defaultProps = {
    clickedName: "ios-star",
    unClickedName: "ios-star-outline"
};
//此处用react-redux的connect生成容器组件,并且把相关的注入处理好,大功告成。
// 此时你就可以直接用这个容器组件了,就像用普通展示组件一样,但是区别是,props里面会自动注入redux store中的相关data和action。
//只要redux store中data一变,props中相关数据就会变,从而自动触发试图更新。组件中的componentWillReceiveProps 也会触发。
export default connect(mapStateToProps, mapDispatchToProps)(Container);
<View>
  ...
  <FavoriteIcon article={article}/>// 记得传入article对象哦
  ...
</View>

3 写在最后

如果你有全部看完代码实现逻辑,细心的你应该会发现,我有在展示组件里面做渲染性能优化,其实这是不得已而为之,因为整套组件的设计架构导致了每次的收藏都会导致store中favoriteitem列表的变化,而这个变化会导致所有icon的props变化,进而重渲染。此处用shouldComponentUpdate做过滤虽然避免了vitual dom比较的开销,但是这个函数本身也有计算开销,而且,virtual dom diff过程和此方法的执行开销孰大孰小可能也要打个问号。在此我能想到的一个优化方式是将user对于一个article的收藏状态临时存于article,借助article的更新来refresh任意位置的收藏状态。当然这需要做更多的操作,比如每次网络获取articlelist之后,都需要与本地favoriteList做merge,给已经收藏的文章打一个标记。所以,这是一个折中的过程,如果同时渲染的favorite icon数量不多,其实本文实现方式足够了,也欢迎大家在评论区就优化方法留言讨论 :)

另外,细心地你应该还会发现一个问题,favoriteItems没有持久化?用户关闭软件再进来岂不是就没了?没错,这个地方是需要持久化的,best practice自然是持久化到server,但是此处我们只持久化到了phone本地存储,借助的是redux-persist,傻瓜式替我们做这一步,大概代码如下:

const middlewares = [];
middlewares.push(...);//你的其他中间件
export default function configureStore() {
    const store = createStore(
        rootReducer,
        undefined,
        compose(
            applyMiddleware(...middlewares),
            autoRehydrate()//magic 一般的帮我们统统的持久化了
        )
    );
    store.close = () => store.dispatch(END);
    persistStore(store, {storage: AsyncStorage});//用rn提供的AsyncStore做save 引擎
    return store;
}

The End ,欢迎留言讨论

f95f5d7455643e7543ae218bfae8b0bc.gif

原文链接:http://www.jianshu.com/p/c925e84ec06a
作者: changchao 转载请注明出处

上一篇 下一篇

猜你喜欢

热点阅读