Redux "使用"教程

作者: 挂着铃铛的兔 | 来源:发表于2017-05-28 16:12 被阅读3552次

    如果在使用过程中遇到什么问题,欢迎加QQ群397885169讨论。

    如果觉得识兔项目可以,欢迎给个star,在稍后的日子里,识兔将会不断完善,如果有什么有意思的需求可以提出来,我可能会加在识兔里面哦!

    本文的Demo

    最终的运行效果

    运行效果.gif

    前言

    本文仅写给在项目中没用使用过redux,又急于利用redux开发的同学们。

    之前开源rn版百思不得姐的时候,写过几篇文章,有人问什么时候会出rn+redux的文章,我也犹豫了很久,因为在开源百思的时候,没有用到跨页面传递状态。

    上个月我开源了识兔(可以识别手机中或者拍摄的图片),本来打算使用mobx来做状态管理的,但在真正使用过程中,可能是我的方法不对,我总感觉mobx总有那么一点欠缺,深思熟虑之后,还是决定使用redux来管理项目中的状态。

    在之前学习redux的过程中发现,网上和Github上确实有很多开源redux的例子,和介绍redux的文章,但很少有从新手角度考虑的文章,绝大部分新手不一定需要知道redux是干什么的,他们只是想尽快的在项目中使用它,而我也认为不用你咋知道原理是啥,所以我写了这篇文章,用识兔项目中的几个小案例来让新同学入了redux的门。

    ps:哪怕真的不愿意知道redux的原理,也需要知道redux中基本概念和 API,比如说在项目中会经常用到的StoreActionReducer等等。而为了知道怎么用,最简单的方式就是去看其他开源的项目,但看的越多只会让你的思路越凌乱,所以在文章中用我的写法,每个人理解的redux都不一样,但万变不离其宗,学会一种之后可以再根据自己的需求更改。

    需求

    1.网络请求:redux最简单的需求当然就是网络请求了,识兔首页,进入App的时候是需要请求一次网络的,用来验证UserToken,如果不存在就从网络获取并保存,如果存在就直接取出来。
    2.跨页面修改状态:识兔的首页背景图是个大美女,我要在福利(瀑布流)页面的图片详情中,可以修改首页的背景图,之前的写法是通过通知的方式,但redux本身就是可以跨页面传值的。

    实现

    逆向工程

    逆向工程.png

    为什么要提到逆向工程呢?
    因为本文的写法可能会与正常的数据redux逻辑不太一样,它会反着写,会让你很快看到效果,而不是要全部把ActionReducer全都写完了,才能看到完整的效果。

    用到的redux库

    redux,
    react-redux,
    redux-logger,
    redux-thunk
    

    1. 包装入口文件

    入口文件:因为在rn中,iOS安卓平台入口是两个文件index.ios.jsindex.android.js,为了跨平台,一般都会创建统一的入口文件。识兔项目的入口文件是app目录下的index.js文件。

    index.js

    import { AppRegistry,View,Text } from 'react-native';
    import React, { Component } from 'react';
    
    // 引入react-redux
    import {Provider}from 'react-redux';
    // 引入store文件,下步会创建
    import configureStore from './store/ConfigureStore';
    
    // 调用 store 文件中的rootReducer常量中保存的方法
    const store = configureStore();
    
    // 项目中使用了react-navigation,推荐的做法是将初始文件写在一个文件中,
    // 所以App.js也可以看做是页面的初始化入口
    import App from './APP';
    
    export default class Root extends Component {
        render() {
            return (
                // 包装App
                <Provider store={store}>
                    <App />
                </Provider>
            );
        }
    }
    

    2. 创建ConfigureStore文件

    store:在上一步的时候,会发现有一个ConfigureStore,这个就是我们俗称的store

    ConfigureStore.js

    // redux库里面提供的方法,创建store和middleware中间件
    import {createStore,applyMiddleware} from 'redux';
    
    // redux-thunk是用来发送异步请求的中间件,用了thunk之后,
    // 一般的操作是将网络请求的方法放在action中,后面会有说明
    import thunk from 'redux-thunk';
    
    // redux-logger打印logger的中间件,具体效果可以看下图
    import logger from 'redux-logger';
    
    // rootReducer下一步会创建
    import RootReducer from '../reducers/RootReducer';
    
    let middlewares = [];
    middlewares.push(logger);
    middlewares.push(thunk);
    
    // 通过applyMiddleware将中间件添加
    const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore);
    
    // 导出configureStore,里面携带着reducer,中间件,初始值
    export default function configureStore(initialState){
        return createStoreWithMiddleware(RootReducer,initialState);
    }
    
    
    redux-logger.png

    3. 创建RootReducer文件

    reducerRootReducer中放的是各个页面的Reducer,推荐的做法是一个页面公用一个Reducer,便于之后管理。

    RootReducer.js

    // Reducer是纯函数,里面不应该有过多的逻辑。
    import { combineReducers } from 'redux';
    // 这个是ShiTu页面中用到的Reducer
    import ShiTuReducer from './ShiTuReducer';
    // 下面这个还未实现
    // import GankReducer from './GankReducer';
    // 取决于这里你加入了多少 reducer
    const RootReducer = combineReducers({
        ShiTuReducer,
    });
    export default RootReducer;
    

    ShiTuReducer.js

    // ActionTypes里面存放着App中可能发生的情况
    import * as types from '../constant/ActionTypes';
    
    // 初始化值
    const initialState = {
        imageURL: 'timg',
        userToken: '',
        webViewUrl: '',
        qiNiuData: null,
    };
    
    // 导出ShiTuReducer。
    export default function ShiTuReducer(state = initialState, action){
        // console.log(action);
        
        // 通过switch来判断types的值,在action中实现功能
        switch (action.type) {
            // 当type=USER_TOKEN_SUCCESS时,会将action中的值,
            // 赋给userToken,在ShiTu.js中就能拿到userToken的值。
            case types.USER_TOKEN_SUCCESS:
                // console.log(action);
                return Object.assign({}, state, {
                    ...state,
                    userToken: action.userToken,
                });
            case types.QINIU_UPLOAD_TOKEN:
                // console.log(action);
                return Object.assign({}, state, {
                    qiNiuData:action.qiNiuData,
                });
            case types.WEBVIEW_URL:
                return Object.assign({}, state ,{
                    ...state,
                    webViewUrl:action.webViewUrl,
                });
            case types.BACKIMAGE_URL:
                return Object.assign({}, state ,{
                    imageURL:action.imageURL,
                });
            default:
                return state;
        }
    }
    

    4. 创建ActionTypes文件

    actionTypes:在使用redux过程中,需要给每个动作添加一个actionTypes类型,比如说在上面,我要获取userToken,就需要再actionTypes中加上USER_TOKEN,而有些时候,调用网络会有成功/失败两种状态,所以可以将这个状态更加细分USER_TOKEN_SUCCESS/USER_TOKEN_ERRO

    ActionTypes.js

    // 用户Token
    export const USER_TOKEN_SUCCESS = 'USER_TOKEN_SUCCESS';
    // 获取失败
    export const USER_TOKEN_ERROR = 'USER_TOKEN_ERROR';
    // 首页背景图片
    export const BACKIMAGE_URL = 'BACKIMAGE_URL';
    

    5. 创建xxxAction文件

    xxxAction:可能在一个页面需要用到多个action,比如说我识兔页面中,进入app的时候需要调用获取userToken的action,点击查找按钮的时候需要调用获取图片信息的action。每个页面是可以有多个action的,只需要在页面中引入就好

    ShiTuUserToken.js

    // 获取actionType中的全部状态,需要哪个就用哪个
    import * as types from '../constant/ActionTypes';
    
    // 网络请求的网址和网络请求的方法
    import Config from '../common/Config';
    import Request from '../common/Request';
    
    import { AsyncStorage } from 'react-native';
    // 存储信息的KEY
    let KEY = 'USERTOKEN';
    
    // 这个方法是请求网络,获取Token的方法
    // 识兔中调用这个方法之后,需要判断是否存在userToken,不存在请求网络并保存,存在直接调用返回
    export function userToken() {
        return dispatch => {
            return Request.get(Config.api.getUserToken,(data)=>{
                AsyncStorage.getItem(KEY,(Error,result)=>{
                    if (result === null){
                        Request.get(Config.api.getUserToken,(data)=>{
                            // console.log(data);
                            if (data && data.success){
                                let token = data.token;
                                AsyncStorage.setItem(KEY,token,(error)=>{
                                    if (error){
                                        console.log('存储失败' + error);
                                    }else {
                                        console.log('存储成功');
                                        // dispatch发送数据到Reducer里面
                                        dispatch(getUserToken(token));
                                    }
                                })
                            }
                        },(error)=>{
                            console.log(error);
                        })
                    }else {
                        console.log('获取成功' + result);
                        dispatch(getUserToken(result));
                    }
                });
    
            },(error)=>{
                console.log(error);
                dispatch({type: types.USER_TOKEN_ERROR, error: error});
            });
        }
    };
    
    // 如果()括号里面的参数`userToken`和Reducer里面的初始参数一样,就可以不用键:值的写法,直接返回就好。
    export function getUserToken(userToken) {
        return {
            // type是必要参数,通过type值判断
            type: types.USER_TOKEN_SUCCESS,
            userToken
        }
    }
    

    6. connect连接页面和Reducer,完成需求1

    connect:如果完成了上面的步骤,恭喜你,还差最后一步就可以看到运行的效果了,那就是在需要的页面connect。因为ShiTu.js中代码过多,所以只在文章中放重要的部分,如果想要查看全部,可以运行项目或者直接查看ShiTu.js

    ShiTu.js

    // 引入需要用到的方法
    ···
    // 引入connect和bindActionCreators
    import { connect } from 'react-redux';
    import { bindActionCreators } from 'redux';
    // 引入获得userToken的action方法
    import { userToken } from '../../actions/ShiTuUserToken';
    ···
    
    // 导出ShiTu页面并连接Reducer
    ···
    // connect 连接 Recucer ,我ShiTu.js的Reducer叫ShiTuReducer,
    // userToken等方法是我在action里面创建的,所以调用的也就是action方法
    export default connect((state) => {
        const { ShiTuReducer } = state;
        return {
            ShiTuReducer
        };
    },{ userToken })(ShiTu)
    ···
    
    // 使用
    componentDidMount(){
        console.log('componentDidMount');
        // 使用userToken方法。
        this.props.userToken();  
    }
    
     render() {
        console.log('render');
        // 掉用过上面的方法后就可以通过打印`ShiTuReducer`获得需要的数据
        console.log(this.props.ShiTuReducer);
    }
    
    
    ShiTuReducer.png

    完成上面的6步,就可以在现有的项目中使用redux开发了。在第6步中我们只是完成了需求1,接下来就要利用redux实现跨页面更改状态了。

    1. 寻找需求

    并不是每个App都需要用到跨页面更改状态的,但在识兔中,我心中的想法是,识兔项目中多数的页面布局都可以由用户自定义,所以我选择了redux,如果不需要用到跨页面更改状态,使用state管理也是没有问题的。
    需求2的操作流程是,在福利的图片详情页WelfarePicture.js中长按图片弹出actionSheet,点击设为主屏幕就可以修改首页的背景图片了。

    2. 实现

    实现逻辑和获取�userToken基本一样,唯一的区别就是,点击按钮的时候,需要通过dispatch传递图片的url地址到Reducer中。

    WelfarePicture

    // 因为
    ···
    
    import { connect } from 'react-redux';
    import { bindActionCreators } from 'redux';
    
    import { backImage,getBackImage } from '../../actions/ShiTuBackImage';
    ···
    
    ···
    export default connect((state) => {
        const { ShiTuReducer } = state;
        return {
            ShiTuReducer
        };
    },{  backImage,getBackImage})(WelfarePicture)
    ···
    
    // 下面的方法就是点击actionSheet的方法
    ···
    handlePress(url,i) {
            let SHITUIMAGEKEY = 'SHITUIMAGEKEY';
            if(i===2){
                AsyncStorage.setItem(SHITUIMAGEKEY,url,(error)=>{
                    if (error){
                        console.log('存储失败' + error);
                    }else {
                        console.log('存储成功');
                        // 通过getBackImage方法将图片的url发给Reducer,Reducer中就会相应的更新页面
                        this.props.getBackImage(url);
                    }
                })
            }
        }
    ···
    
    

    总结

    在文章中真的没有多说redux原理,如果有想了解的同学可以参考阮一峰老师的几篇文章,讲的清晰透彻。

    本文只是我对于实现redux的一点看法,可能并不适用于你的项目,如果在使用过程中遇到什么问题,欢迎加QQ群397885169讨论。

    如果认为我的文章不错,欢迎打赏,关注,喜欢。

    如果感觉我的文章有问题,欢迎在评论区提出,我会修改。

    相关文章

      网友评论

      • _摩西_:花了1元钱,奔着这个需求去的A-B-C-D-E,从E直接返回A,看这个demo,并没有看到从某个页面返回任意页面的效果,只是更多的打开了第三方的页面,貌似
      • molikar:用不起
        _摩西_:看这个demo,并没有看到从某个页面返回任意页面的效果,只是更多的打开了第三方的页面,貌似
      • 5e64f6d1fc21:兔兔,我爱你
        5e64f6d1fc21:@挂着铃铛的兔 坏蛋:kissing_closed_eyes:
        挂着铃铛的兔:@5e64f6d1fc21 :scream::scream:
      • c54333c341b0:兔神写的很很详细

      本文标题: Redux "使用"教程

      本文链接:https://www.haomeiwen.com/subject/bydhfxtx.html