Redux "使用"教程

如果在使用過程中遇到什么問題,歡迎加QQ群397885169討論。

如果覺得識兔項目可以,歡迎給個star,在稍后的日子里,識兔將會不斷完善,如果有什么有意思的需求可以提出來,我可能會加在識兔里面哦!

本文的Demo

最終的運行效果

運行效果.gif

前言

本文僅寫給在項目中沒用使用過redux,又急于利用redux開發的同學們。

之前開源rn版百思不得姐的時候,寫過幾篇文章,有人問什么時候會出rn+redux的文章,我也猶豫了很久,因為在開源百思的時候,沒有用到跨頁面傳遞狀態。

上個月我開源了識兔(可以識別手機中或者拍攝的圖片),本來打算使用mobx來做狀態管理的,但在真正使用過程中,可能是我的方法不對,我總感覺mobx總有那么一點欠缺,深思熟慮之后,還是決定使用redux來管理項目中的狀態。

在之前學習redux的過程中發現,網上和Github上確實有很多開源redux的例子,和介紹redux的文章,但很少有從新手角度考慮的文章,絕大部分新手不一定需要知道redux是干什么的,他們只是想盡快的在項目中使用它,而我也認為不用你咋知道原理是啥,所以我寫了這篇文章,用識兔項目中的幾個小案例來讓新同學入了redux的門。

ps:哪怕真的不愿意知道redux的原理,也需要知道redux中基本概念和 API,比如說在項目中會經常用到的Store、Action、Reducer等等。而為了知道怎么用,最簡單的方式就是去看其他開源的項目,但看的越多只會讓你的思路越凌亂,所以在文章中用我的寫法,每個人理解的redux都不一樣,但萬變不離其宗,學會一種之后可以再根據自己的需求更改。

需求

1.網絡請求:redux最簡單的需求當然就是網絡請求了,識兔首頁,進入App的時候是需要請求一次網絡的,用來驗證UserToken,如果不存在就從網絡獲取并保存,如果存在就直接取出來。
2.跨頁面修改狀態:識兔的首頁背景圖是個大美女,我要在福利(瀑布流)頁面的圖片詳情中,可以修改首頁的背景圖,之前的寫法是通過通知的方式,但redux本身就是可以跨頁面傳值的。

實現

逆向工程

逆向工程.png

為什么要提到逆向工程呢?
因為本文的寫法可能會與正常的數據redux邏輯不太一樣,它會反著寫,會讓你很快看到效果,而不是要全部把Action、Reducer全都寫完了,才能看到完整的效果。

用到的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討論。

如果認為我的文章不錯,歡迎打賞,關注,喜歡。

如果感覺我的文章有問題,歡迎在評論區提出,我會修改。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容