记录一次使用redux的详细步骤

898 阅读8分钟

实现功能简述

从App首页点击进入详情页,轮播组件开始从store中心状态仓库加载初始图片并显示,然后下拉触发请求新图片,发送更新图片Action,然后通过dispatch(Action)的方式将图片状态更新至store中心状态仓库,最终触发轮播组件重渲染,刷新图片组件。

效果演示

redex知识点简述

首先我们先看看,当在外部发起一个 action,直到 state 完成更新并下发通知订阅者的这个过程中 redux 做了什么,可以用一张图来概括:

总得来说流程并不复杂:

我们使用 actionCreator 发起 action (当然 actionCreator 的使用只是一种规范约束,你可以直接 dispatch(action) 或者使用 bindActionCreators,这取决于你及你的团队风格) action 首先经过一系列中间件 middlewareXX 的处理(可以留意下这些中间件的图形结构,它比较类似 koa 的洋葱模型,它们本身是层层包裹的,后面会详细说明) 纯函数 combination 同时接受传入的 action 及当前的 state 树,即 currentState,并分发给对应的 reducer 分别计算不同的 subState 完成计算后,生成新的 state 树,即 newState,然后赋值给 currentState,并通知所有的 listeners 本次更新完成

需要注意的是,上文中的中间件不是必须的,它只是个可选项,如果没了它,整个流程会更简单:action 直接进入 combination。 大体流程就这样,我们再看看每个模块的具体实现

参考链接 技术胖的redux渐进式讲解

文件目录

reducers/index.js


import {combineReducers} from 'redux'

import appReducer from './AppReducer'
import {resetAppInfo} from '../actions/AppActions'
import LoginReducer from './accountReducers/LoginReducer'
import {reducer as formReducer} from 'redux-form'
import {CDManagerReducer} from '../component/cdManager'
import {
    RefreshReducer,
} from '../component/refreshAble'

import FindHouseReducer from "./homeReducer/FindHouseReducer";

import DetailsReducer from './detailsReducers/DetailsReducer'


function createNamedWrapperReducer(reducerFunction, reducerName) {
    return (state, action) => {
        const {name} = action;
        const isInitializationCall = state === undefined;
        if (name !== reducerName && !isInitializationCall) return state;

        return reducerFunction(state, action);
    }
}

const noResetReducersConfig = {
    loginReducer: LoginReducer,
    app: appReducer,
}

const canResetReducersConfig = {
    form: formReducer,
    cdManager: CDManagerReducer,
    refreshAble: RefreshReducer,

    houseReducer:FindHouseReducer,
    DetailsReducer:DetailsReducer,
    // testList: createNamedWrapperReducer(PullDownRefreshListReducer, 'testList'),
    
}

const reducers = combineReducers(canResetReducersConfig);

const otherReducers = combineReducers({...noResetReducersConfig, ...canResetReducersConfig})

export default (state: any, action: any) => {
    if (action.type === resetAppInfo.toString()) {
        let newState = reducers(undefined, action)
        newState = {...state, ...newState} 
        return otherReducers(newState, action)
    }
    return otherReducers(state, action)
}

/AppAction.js


import { createAction } from 'redux-actions'

export const setAppInfo = createAction('SetAppInfo')
export const resetAppInfo = createAction('ResetAppInfo')

/AppReducer.js



import { handleAction } from 'redux-actions'
import { setAppInfo } from '../actions/AppActions'

const initState = {    
    appVersionCode: null,
    appVersionName: null,
}

export default function (state = initState, action) {
    return reducer(state, action)
}

const reducer = handleAction(
    setAppInfo,
    (state, action) => (
        {...state, ...action.payload}
    ),
    {},
);

/configureStore.js

import {createStore,applyMiddleware} from 'redux';

import reducer from './reducers';
import thunk from 'redux-thunk';
import { persistStore, persistReducer } from 'redux-persist'
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2'
// import {createBlacklistFilter } from 'redux-persist-transform-filter';
import AsyncStorage from '@react-native-community/async-storage'



// const loginReducerFilter = createBlacklistFilter(
//     'loginReducer',
//     ['requesting', 'autologin']
// );
const persistConfig = {
    key: 'root',
    storage:AsyncStorage,
    stateReconciler: autoMergeLevel2,
    whitelist: [
        'loginReducer',
        'app',
       
    ],
    // transforms: [
    //     loginReducerFilter,
    // ],
}
const persistedReducer = persistReducer(persistConfig, reducer)


let store = createStore(persistedReducer, applyMiddleware(thunk))

export const persistor = persistStore(store)
export default store

/App.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import 'react-native-gesture-handler';//这一句不可以删,删掉可能会引起真机闪退
import React, {Component} from 'react'

import AppContainer from './AppContainer'

import store ,{persistor}from './configureStore'
import { PersistGate } from 'redux-persist/integration/react'

import { Provider } from 'react-redux';



class Root extends Component {
    render(){
        return(
            
            <Provider store = {store}>
                  <PersistGate loading={null} persistor={persistor}> 
                    <AppContainer />
                   </PersistGate>
            </Provider>
        )
    }
}

export default Root 

⚠️:react-redux是为了方便开发,提供了一个Provider组件,以及connect方法。Provider组件作为做上层组件,需要将store作为参数注入组件中,此后在子组件中都可以访问到store这个对象;connect方法接受两个参数:mapStateToProps,actionCreators,并返回处理后的组件,其中mapStateToProps可以将对应的state作为prop注入对应的子组件,actionCreator可以将对应的actioncreator作为prop注入对应的子组件。

/index.js

/**
 * @format
 */

import {AppRegistry,LogBox} from 'react-native';
import App from './js/App';
import {name as appName} from './app.json';


//关闭所有黄色警告
LogBox.ignoreAllLogs(true);


if (!__DEV__) {
    global.console = {
        log: () => { },
        error: () => { },
        info: () => { },
        warn: () => { },
    }
}
//注册全局常量
require('./js/common/GlobalValue')



AppRegistry.registerComponent(appName, () => App);

使用例子

reducers/detailsReducers/DetailsAction.js

/**
 
 * @flow
 */

import {GetHouseDetail,GetHouseTypelist,GetHouseTypeDetail, GetHousePhotos,GetHouseDynamics} from './DetailsActionTypes'

import * as api from '../../networking/Api'

//redux-thunk
// export function login(values) {
//     return (dispatch) => {
//         const {userName, password} = values
//         dispatch(loginRequest(userName, password))
//         asyncLoginRequst(values)
//             .then(({state, data}) => {
//                 if(state === 'gotoHome') {
//                     dispatch(loginSuccess(data))
//                 } else {
//                     dispatch(loginFailed())
//                 }
//             })
//             .catch((error) => {
//                 if(error instanceof SubmissionError) {
//                     EDMoney.Toast.show(error.errors._error)
//                     dispatch(loginFailed())
//                 } else {
//                     EDMoney.Toast.show('网络错误')
//                     dispatch(networdError())
//                 }
//             })
//     }
// }

//获取楼盘相册
export function getHousePhotosAction(response,refreshing) {
    console.log('refreshingStore',refreshing);
    return {
        type: GetHousePhotos,
        banner:response.banner,
        refreshing:refreshing//控制加载中小控件
    }
}

export async function asyncGetHousePhotos (values) {
    console.log('asyncGetHousePhotos---values',values)
    // 获取楼盘相册(轮播)
    // const values = "1790";
    const response = await api.GetHousePhotos(values)
    // console.log('获取楼盘相册',response);
    let photosData = response.items[0]
    console.log('response',response,'photosData:',photosData);
    if(photosData){
        return Promise.resolve({type: GetHousePhotos, banner: photosData,})
    }
}

export function toGetHousePhotos(values) {
    // 使用了reduex-thunk中间件时, return出去的函数会被bindActionCreators(DetailsActions,dispatch)绑定并dispatch(DetailsActions.toGetHousePhotos),
    // 这时reduex-thunk会判断dispatch的内容,当dispatch(函数)时,则直接执行return出去的函数,处理完后最后还是变成dispatch(对象);当dispatch(对象)时,则直接到达reducer,改变状态
    return (dispatch,getState) => {
       var refreshingStore = EDMoney.refreshingStore//获取全局注册的状态值
       console.log('refreshingStore~~~~~',EDMoney.refreshingStore);
    // console.log('getState().DetailsReducer.banner',getState().DetailsReducer); 
        dispatch(getHousePhotosAction(getState().DetailsReducer,refreshingStore.doing))
        asyncGetHousePhotos(values)
            .then((response) => {
                console.log('response',response);
                console.log('refreshingStore~!@#¥ ~',EDMoney.refreshingStore);
                console.log('getHousePhotosAction(response)1',getHousePhotosAction(response,refreshingStore.done));
                console.log('getHousePhotosAction(response)2',{type:response.type,banner:response.banner,refreshing:refreshingStore.done});
                // dispatch({banner:response.banner,})//和下面写法作用一样
                dispatch(getHousePhotosAction(response,refreshingStore.done))
                console.log('getState',getState());//获取更新后的最新状态
            })
            .catch((error) => {
                EDMoney.Toast.show(error)
            })
    }
}



//测试bindActionCreators
export function test(testData) {
    // redux-thunk主要的功能就是可以让我们dispatch一个函数,而不只是普通的 Object。后面我们会看到,这一点改变可以给我们巨大的灵活性。
    return (dispatch) => {
        console.log('获取redux-thunk返回的dispatch',dispatch);
        dispatch({
            type: 'ceshi',
            testData
        })
        
    }
}

//测试bindActionCreators
export function test2(testData) {
    return {
        type: 'ceshi2',
        testData,
    }
}




reducers/detailsReducers/DetailsReducer.js

/**

 * @flow
 */

import {GetHouseDetail,GetHouseTypelist,GetHouseTypeDetail, GetHousePhotos,GetHouseDynamics} from './DetailsActionTypes'

const initState = {
    banner:{
        housePhotoTypes:[
            {typeName:'航拍'},
            {typeName:'VR'},
            {typeName:'效果图'},
            {typeName:'户型图'},
            {typeName:'配套'},
            {typeName:'实景'},
            {typeName:'其他'},
        ],
        housePhotos:[
            {type: "C", picUrl: "https://aikf.hopechina.com/image/bigimg.jpg"},
            {type: "C", picUrl: "https://aikf.hopechina.com/image/bigimg.jpg"},
            {type: "C", picUrl: "https://aikf.hopechina.com/image/bigimg.jpg"},
            {type: "C", picUrl: "https://aikf.hopechina.com/image/bigimg.jpg"},
            {type: "C", picUrl: "https://aikf.hopechina.com/image/bigimg.jpg"},
        ],//楼盘相册
    },
    testData:{
        a:'1',
    },
    testData2:{
        a:'2',
    },
    refreshing:false
      
}

export default function(state = initState, action) {
    console.log('state!!!!!!!',state,'action!!!!!',action);
    if(action.type === GetHousePhotos) {
        //获取楼盘相册
        return {...state, banner: action.banner,refreshing: action.refreshing}
    }else if(action.type === 'ceshi'){
        //测试bindActionCreators
        return {...state, testData: action.testData}
    }else if(action.type === 'ceshi2'){
        //测试bindActionCreators
        return {...state, testData2: action.testData}
    }
    return state
}

reducers/detailsReducers/DetailScreen.js

//楼盘详情 created by Li
import React,{Component} from 'react';

import { View,
  Text ,
  Button,
  StyleSheet,
  ScrollView,
  ImageBackground,
  Image,
  TouchableOpacity,
  Dimensions,
  RefreshControl,
} from 'react-native';
import {connect} from 'react-redux'
import { bindActionCreators } from 'redux'
import * as DetailsActions from '../../reducers/detailsReducers/DetailsAction'
import store from './../../configureStore'

import * as api from '../../networking/Api'
import ConcactInformation from '../../component/detail/ConcactInformation'//中介联系方式浮窗
import StatisticsAudience from '../../component/detail/StatisticsAudience'//围观人数
import HouseType from '../../component/detail/HouseType'//户型
import ItemTitle from '../../component/detail/ItemTitle'//自定义标题
import AgentHouse from '../../component/detail/AgentHouse'//经纪人楼盘
import MapComponent from '../../component/detail/MapComponent'//地图
import ShowPictures from '../../component/detail/ShowPictures'//轮播
import TimeAxis from '../../component/detail/TimeAxis'//楼盘动态时间轴

const image = { uri: "https://reactjs.org/logo-og.png" };
// const image = require('./../../resources/cHomeTab/photo/banner.png')
const opening_time = require('./../../resources/cHomeTab/icon/opening_time.png');
const decoration = require('./../../resources/cHomeTab/icon/decoration.png');
const developers = require('./../../resources/cHomeTab/icon/developers.png');
const property_address = require('./../../resources/cHomeTab/icon/property_address.png');
const sharePhoto = require('./../../resources/cHomeTab/icon/sharePhoto.png');
// import Banner from './home/component/Swiper_'

const window = Dimensions.get("window");
const screen = Dimensions.get("screen");

class DetailScreen extends Component{
  constructor(props, context) {
    //Provider是通过context传递给子组件的,子组件通过connect获得数据,实现过程如下,可以看到在没有定义props的情况下,通过context直接取得store中的数据。
    // this.store = props.store || context.store 
    super(...arguments)
    this.state = {
      refreshing:false
    }
  }
  componentDidMount(){
    console.log('this.props.banner------',this.props.banner);
    const houseId = "1790";
    this.props.toGetHousePhotos(houseId)
    setTimeout(()=>{console.log('this.props.banner+++++',this.props.banner)},500)
  }

   _onRefresh = () => {
    console.log('this.props.banner',this.props.banner);
    console.log('this.props.refreshing',this.props.refreshing);
    const houseId = "1790";

    // 普通写法(refreshing可以由Store中的中央状态控制,也可以直接setState控制)
    // this.setState({refreshing:true})
    // DetailsActions.asyncGetHousePhotos(houseId)
    //       .then((response) => {
    //         this.setState({refreshing:false})
    //           if (response.type === 'GetHousePhotos') {
    //               console.log('response.banner',response.banner);
    //               this.props.added({type:response.type,banner:response.banner})
    //               console.log('this.props.banner',this.props.banner);
    //               this.props.test({a:'01'});
    //               this.props.test2({a:'02'});
    //               console.log('this.props.testData',this.props.testData);
    //               console.log('this.props.testData2',this.props.testData2);
    //           }
    //       })
    //       .catch((error) => {
    //           return error
    //       })
    
    // redux-thunk写法(refreshing需要由Store中的中央状态控制)
    this.props.toGetHousePhotos(houseId)
    setTimeout(()=>{console.log('更新后this.props.banner',this.props.banner);console.log('更新后this.props.refreshing',this.props.refreshing);},500)
  }
  render() {
    return (
        <View style={{flex:1}}>
          
          <ScrollView 
          style={styles.Scroll}
          refreshControl={
            <RefreshControl refreshing={this.props.refreshing} onRefresh={()=>{this._onRefresh()}} />
          }>
            <View style={{backgroundColor:'#f5f5f5',}}>
              {/* 图片展示 */}
              
                <ShowPictures navigation={this.props.navigation} />
                <View style={styles.houseMsg}>
                  <View style={styles.houseName}>
                    <Text style={styles.houseTitle}>保利鱼珠港</Text>
                    <Text style={styles.status}>即将发售</Text>
                  </View>
                  <Text style={styles.description}>珠江中轴之上,位于广州第二CBD最核心区域</Text>
                  <View style={styles.label}>
                    <Text style={styles.SpecificLabel}>专车带看</Text>
                    <Text style={styles.SpecificLabel}>电梯房</Text>
                    <Text style={styles.SpecificLabel}>双拼</Text>
                    <Text style={styles.SpecificLabel}>国际化社区</Text>
                  </View>
                  <Text style={styles.price}>36500元/m²</Text>
                </View>
                
                <View>
                  <View>
                    <View style={styles.projectInformation}>
                      <View style={styles.informationList}>
                        <Image source={opening_time} style={styles.icon}/>
                        <View style={styles.listContent}>
                          <Text style={styles.listText}>开盘时间:</Text>
                          <Text style={styles.listText}>2018年10月</Text>
                        </View>
                      </View>
                      <View style={styles.informationList}>
                        <Image source={decoration} style={styles.icon}/>
                        <View style={styles.listContent}>
                          <Text style={styles.listText}>装修情况:</Text>
                          <Text style={styles.listText}>带装修</Text>
                        </View>
                      </View>
                      <View style={styles.informationList}>
                        <Image source={developers} style={styles.icon}/>
                        <View style={styles.listContent}>
                          <Text style={styles.listText}>开发商:</Text>
                          <Text style={styles.listText}>保利发展</Text>
                        </View>
                        </View>
                      <View style={styles.informationList}>
                        <Image source={property_address} style={styles.icon}/>
                        <View style={styles.listContent}>
                          <Text style={styles.listText}>楼盘地址:</Text>
                          <Text style={styles.listText}>黄埔黄埔大道动856号</Text>
                        </View>
                      </View> 
                    </View>
                  </View>
                  <View style={{backgroundColor:'#fff'}}>
                    <TouchableOpacity
                      onPress={() => {
                        this.props.navigation.navigate('ProjectInformation')
                      }}
                      activeOpacity={0.5}//触摸时完全不透明,值越小越透明
                      style={styles.morecontainer}
                    >
                      <Text style={styles.more}>更多项目信息</Text>
                    </TouchableOpacity>
                  </View>
                </View>
                {/* 围观人数 */}
                <StatisticsAudience />
                <View style={styles.projectItem}>
                      <ItemTitle title='项目介绍'/>
                      <Text style={styles.content}>保利鱼珠港位于黄埔大道东,雄踞广州自西向东发展的珠江东进中轴,接力珠江新城,成为下一个十年重点发展区。2017年政府铭文指出将重点发展第二商务区,保利鱼珠正处于第二CBD的核心区,占领城市规划发汗利好。</Text>
                </View>
                <View style={styles.projectItem}>
                      <ItemTitle title='热推户型' seeMore='查看更多' navigation={this.props.navigation} />
                      {/* 热推户型 */}
                      <HouseType />
                </View>
                
                <View style={styles.projectItem}>
                    <ItemTitle title='楼盘动态' count={24}  seeMore='查看更多' navigation={this.props.navigation} />
                    <TimeAxis />
                </View>
                <View style={styles.projectItem}>
                    <ItemTitle title='位置周边' seeMore='查看更多' navigation={this.props.navigation} />
                    <MapComponent />
                </View>
                <View style={styles.projectItem}>
                    <ItemTitle title='经纪人其他楼盘'/>
                    <AgentHouse />
                </View>
            </View>
        </ScrollView>
        {/* 中介联系方式 */}
        <ConcactInformation />
      </View>
    );
  } 
}

export default connect(
  (state,ownProps) => {
    console.log('detailState',state)
    console.log('ownProps',ownProps)
      return { 
          userId: state.loginReducer.userId,
          banner:state.DetailsReducer.banner,
          refreshing:state.DetailsReducer.refreshing,
          testData:state.DetailsReducer.testData,
          testData2:state.DetailsReducer.testData2,

      }
  },
  // 写法一:
  // (dispatch) => { 
  //   console.log('dispatch!!!!!!!',dispatch)
  //   // console.log('123456',bindActionCreators(DetailsActions,dispatch))//bindActionCreators(DetailsActions,dispatch)写法返回一个key为DetailsActions中各Action函数名
  //   // console.log('78910',{...bindActionCreators(DetailsActions,dispatch)})//{...bindActionCreators(DetailsActions,dispatch)}写法返回一个key为DetailsActions中各Action函数名
  //   return {
  //     ...bindActionCreators(DetailsActions,dispatch)
  //     /**或者:
  //      * add: (data) =>{console.log('data',data); dispatch(data);} , 
  //      * added: (data) =>{console.log('data',data); dispatch(data);},
  //      * 
  //      * */ 
      
  //   }
  // }

  // 写法二:
  // (dispatch) => (
  //   {...bindActionCreators(DetailsActions,dispatch)}
  // )

  // 写法三:
  // (dispatch) => ({
  //   bindActionCreators({
  //           actionCreator1: actionCreator1,
  //           actionCreator2: actionCreator2
  //   })},dispatch)
    //调用方式://this.props.actionCreator1()

  // 写法四:
  (dispatch) => ({
    ...bindActionCreators(DetailsActions,dispatch),
    // 异步操作要写成下面这样才方便
    add: (data) =>{console.log('data',data); dispatch(data);},
    added: (data) =>{console.log('data',data); dispatch(data);},
  })
  
)(DetailScreen)

var styles = StyleSheet.create({
  Scroll:{
    backgroundColor:'#ffffff'
  },
  flexLayout:{
    flexDirection:'row',
  },
  shareIcon:{
    position:"absolute",
    top:10,
    right:30,
    width:40,
    height:40, 
  },
  sharePhotoIcon:{
    width:30,
    height:30, 
    borderRadius:14
  },
  projectInformation:{
    paddingLeft:15,
    backgroundColor:'#ffffff',
    paddingBottom:10
  },
  informationList: {
    flexDirection:'row',
    alignItems:'center',
  },
  listContent:{
    flexDirection:'row'
  },
  icon:{
    marginRight:5
  },
  listText:{
    color:'#222222',
    fontSize:16,
    lineHeight:30
  },
  morecontainer:{
    backgroundColor:'#f5f5f5',
    alignItems:'center',
    borderRadius:3,
    overflow:'hidden',
    marginLeft:15,
    marginRight:15
  },
  more:{
    lineHeight:45,
    fontSize:17,
    color:'#495b84',
  },
  houseMsg:{
    paddingLeft:15,
    paddingRight:15,
    backgroundColor:'#ffffff',
    paddingTop:20
  },
  houseName:{
    flexDirection:'row',
    alignItems:'center',
   
  },
  houseTitle:{
    color:'#222222',
    fontSize:20,
    lineHeight:25,
    marginRight:10
  },
  status:{
    height:25,
    lineHeight:25,
    backgroundColor:'#fae5e4',
    color:'#ec736f',
    borderRadius:5,
    overflow:'hidden',
    paddingLeft:5,
    paddingRight:5,
    fontSize:13
  },
  description:{
    height:25,
    lineHeight:25,
    color:'#545454',
  },
  label:{
    flexDirection:'row',
  
  },
  SpecificLabel:{
    height:20,
    lineHeight:20,
    fontSize:13,
    color:'#495b84',
    backgroundColor:'#f5f5f5',
    paddingLeft:5,
    paddingRight:5,
    borderRadius:3,
    overflow:'hidden',
    marginRight:10
  },
  price:{
    fontSize:25,
    color:'#eb5e36',
    lineHeight:50
  },
  projectItem:{
    paddingLeft:15,
    paddingRight:15,
    backgroundColor:'#ffffff',
    marginTop:10,
    paddingTop:20,
    paddingBottom:20
  },
  content:{
    color:'#545454',
    fontSize:16,
    lineHeight:22
  },
  
  

})