react+redux大体思路(一)

思路图基本是components->action->middlewave->server(response)->reducer(重组数据更新components)


具体实例:

app/host/components/mine/edit_details.js中:

import React, {Component} from 'react';
import {View, Text, Image, StyleSheet, ScrollView, Animated, Alert, TouchableOpacity, Dimensions} from 'react-native';

import _ from 'underscore'
import {I18n, S, COLOR} from '../../utils/tools'
import SegmentsGroup from '../common/segments_group'
import Segment from '../common/segment'
import LeftBarButton from '../common/navigatorBar/leftBarButton'
import NavigationBar from 'react-native-navbar'
import buildApiUrl from '../../../utils/build_api_url'
import SessionStore from '../../../stores/session_store'
import SessionActions from '../../../actions/session_actions'
import Picker from 'react-native-picker'
var ImagePickerManager = require('NativeModules').ImagePickerManager;

export default class EditDetail extends Component {

  static contextTypes = {
    page: React.PropTypes.object,
    app: React.PropTypes.object,
  };

  constructor(props) {
    super(props)

    const {users} = this.props.users
    let user = users[`${CURRENT_USER.id}`]

    this.state = {
        isUploading: false,
        profile_photo: user.profile_photo?user.profile_photo.medium_url:null,
        birth_date: user.birth_date
    }
  }

  componentDidMount() {

  }

  _onPressHandle(){
		this.picker.toggle();
	}

  render() {
    const {users} = this.props.users
    let user = users[`${CURRENT_USER.id}`]
    var array=user.languages||[]
    var languages = _.map(array,(element,index)=>{
      return(element)
    })
    let leftButtonConfig = <LeftBarButton onPress={() => {this.context.page.navigator.pop()}}/>
    return (
      <View style={[S.container, {backgroundColor: "#EFEFEF"}]}>
        <NavigationBar title={{title:I18n.t('my.edit_profile_tip')}} leftButton={leftButtonConfig}/>
        <ScrollView>
        <View style={styles.detailView}>
        <SegmentsGroup>
        {
          user.profile_photo&&this.state.profile_photo?(<Segment onPress={() => this._editHeadImage()}
            redTitle={true}
            value={I18n.t('my.edit_avatar')}
            iconImage={<Image source={{uri:this.state.profile_photo}} style={{width: 40, height: 40, borderRadius: 20}} />}
          />):(<Segment onPress={() => this._editHeadImage()}
              redTitle={true}
              value={I18n.t('my.edit_avatar')}
              iconImage={<Image source={require('../../../components/image/img/default_profile.png')} style={{width: 40, height: 40, borderRadius: 20}} />}
            />)
        }
          <Segment onPress={() => this._editName()}
            title={I18n.t('my.name')}
            value={user.full_name}
          />
          <Segment onPress={() => this._editSex()}
            title={I18n.t('my.gender')}
            value={user.gender}
          />
          <Segment onPress={()=>this._onPressHandle()}
            title={I18n.t('user.birth_date')}
            value={this.state.birth_date}
            rightIcon="none"
          />
          <Segment onPress={() => this._editCity()}
            title={I18n.t('my.city')}
            value={user.location}
          />
        </SegmentsGroup>
        <SegmentsGroup>
          <Segment onPress={() => this._editCollege()}
            title={I18n.t('my.learning')}
          />
          <Segment onPress={() => this._editJob()}
            title={I18n.t('my.occupation')}
          />
          <Segment onPress={() => this._editLanguage()}
            title={I18n.t('user.languages')}
            value={languages}
          />
        </SegmentsGroup>
        <SegmentsGroup>
          <Segment onPress={() => this._editYourself()}
            title={I18n.t('my.introduce_myself')}
        />
        </SegmentsGroup>
        </View>
        </ScrollView>
        {this._renderDatePicker()}
      </View>
    )
  }
  _renderDatePicker(){
    return (
			<Picker
				ref={picker => this.picker = picker}
				style={{height: 250}}
				showDuration={300}
				pickerData={createDateData()}
        pickerBtnText={I18n.t('house.completed')}
        pickerCancelBtnText={I18n.t('common.cancel')}
				selectedValue={['2015', '1', '1']}
				onPickerDone={(pickedValue) => {
        var birthDate =pickedValue.join('-')
          const {actions} = this.props
            actions.updateUser({
              data: {
                birth_date: birthDate
              },
              success: () => {
                this.setState({
                  birth_date: birthDate
                })
              }
            })
				}}
			/>
		)
  }

  _editHeadImage() {
    if (!this.state.isUploading) {
      var options = {
        title: I18n.t('photo.select'),
        cancelButtonTitle: I18n.t('common.cancel'),
        takePhotoButtonTitle: I18n.t('photo.take'),
        chooseFromLibraryButtonTitle: I18n.t('photo.choose'),
        cameraType: 'front', // 'front' or 'back'
        mediaType: 'photo', // 'photo' or 'video'
        videoQuality: 'high', // 'low', 'medium', or 'high'
        maxWidth: 1000, // photos only
        maxHeight: 1000, // photos only
        aspectX: 1, // android only - aspectX:aspectY, the cropping image's ratio of width to height
        aspectY: 1, // android only - aspectX:aspectY, the cropping image's ratio of width to height
        quality: 0.5, // 0 to 1, photos only
        angle: 0, // android only, photos only
        allowsEditing: true, // Built in functionality to resize/reposition the image after selection
        noData: false, // photos only - disables the base64 `data` field from being generated (greatly improves performance on large photos)
        storageOptions: { // if this key is provided, the image will get saved in the documents directory on ios, and the pictures directory on android (rather than a temporary directory)
          skipBackup: true, // ios only - image will NOT be backed up to icloud
          path: 'images' // ios only - will save image at /Documents/images rather than the root
        }
      }

      ImagePickerManager.showImagePicker(options, (response) => {
        if (response.didCancel) {
          console.log('User cancelled image picker');
        } else if (response.error) {
          console.log('ImagePickerManager Error: ', response.error);
        } else {
          var source;
          source = {uri: 'data:image/jpeg;base64,' + response.data, isStatic: true};

          // uri (on iOS)
          if (IOS) {
            source = {uri: response.uri.replace('file://', ''), isStatic: true};
          }

          // uri (on android)
          if (ANDROID) {
            source = {uri: response.uri, isStatic: true};
          }

          __LOG.info(` source .... `)
          __LOG.info(` ${JSON.stringify(source)} `)

          this.uploadProfilePhoto(source);
        }
      })
    }
  }

  uploadProfilePhoto(asset) {
    var xhr = new XMLHttpRequest();
    var url = buildApiUrl({path: '/v1/profile/0.json'});
    xhr.open('PUT', url);

    var formdata = new FormData();
    formdata.append('profile_photo', {type: "image/jpeg", name: 'image.jpg', uri: asset.uri});

    if (xhr.upload) {
      xhr.upload.onprogress = (event) => {
        if (event.lengthComputable) {
          this.setState({uploadProgress: event.loaded / event.total});
        }
      };
    }
    xhr.send(formdata);
    this.setState({
      isUploading: true,
      profile_photo: asset.uri
    })

    xhr.onload = () => {
      this.setState({isUploading: false});
      if (xhr.status !== 200) {
        Alert.alert(
          'Upload failed',
          'Expected HTTP 200 OK response, got ' + xhr.status
        );
        return;
      }
      var json = JSON.parse(xhr.responseText);
      var {data, meta} = json;
      const {actions} = this.props
      actions.updateUser({
        data: {
          user: data.user
        },
        success: () => {
        }
      })
    }
  }

  _editName(){
    this.context.page.navigator.push({
      id: "editName",
    })
  }

  _editSex(){
    this.context.page.navigator.push({
      id: "editSex",
    })
  }

  _editBirthday() {
    this.context.page.navigator.push({
      id: "editBirthDate",
    })
  }

  _editCity(){
    this.context.page.navigator.push({
      id: "editCity",
    })
  }

  _editCollege(){
    this.context.page.navigator.push({
      id: "editCollege",
    })
  }

  _editJob(){
    this.context.page.navigator.push({
      id: "editWork",
    })
  }

  _editLanguage(){
    this.context.page.navigator.push({
      id: "editLanguage",
    })
  }

  _editYourself(){
    this.context.page.navigator.push({
      id: "editYourself",
    })
  }
}

function createDateData(){
	let date = {};
	for(let i=1900;i<2021;i++){
		let month = {};
		for(let j = 1;j<13;j++){
			let day = [];
			if(j === 2){
				for(let k=1;k<29;k++){
					day.push(k);
				}
			}	else if (j in {1:1, 3:1, 5:1, 7:1, 8:1, 10:1, 12:1}){
				for(let k=1;k<32;k++){
					day.push(k);
				}
			} else {
				for(let k=1;k<31;k++){
					day.push(k);
				}
			}
			month[j] = day;
		}
		date[i] = month;
	}
	return date;
}

const styles = StyleSheet.create({
   container: {
    flex: 1,
    marginLeft: 10,
    marginRight: 10,
    alignItems: 'center',
    justifyContent: 'center',
   },
   detailView: {
     marginTop: 10,
     marginLeft: 10,
     marginRight: 10,
   }
})
#####################上面代码解释######################

EditDetail为一整个组件(这个组建就相当于整个一个页面),

  static contextTypes = {
    page: React.PropTypes.object,
    app: React.PropTypes.object,
  };
这段是从最上层父组件带过来的变量也就是从 / app /modal_container .js这个文件中的 ModalContainer这个父组件中

  childContextTypes: {
    app: React.PropTypes.object,
    page: React.PropTypes.object,
  },
这句继承而来的就是跳过很多组件直接从最父的组件集成
  constructor(props) {
    super(props)

    const {users} = this.props.users
    let user = users[`${CURRENT_USER.id}`]

    this.state = {
        isUploading: false,
        profile_photo: user.profile_photo?user.profile_photo.medium_url:null,
        birth_date: user.birth_date
    }
  }
这段是继承来父级的props和对本级的state进行初始化赋值
  componentDidMount() {

  }
这个函数是render执行完毕后进行的回调函数
render中就是对页面进行的渲染
          <Segment onPress={() => this._editName()}
            title={I18n.t('my.name')}
            value={user.full_name}
          />
这句中this._edit_Name()函数, 这个this指的是当前组建, 也就是当前组建中的_edit_Name()函数
  _editName(){
    this.context.page.navigator.push({
      id: "editName",
    })
  }
这个函数这句this.context.page.navigator.push({id:"editName"})中前面到push意思是onPress这个动作触发这个_editName
()这个函数跳到id为“editName”这个组建中这个routes对应找editName这个组件的表在 / app / host / components / navigation / routes.js中
    switch (route.id) {
      case 'host':
        return <ContextWrapper {...contextProps}><TabBarApp {...viewProps} /></ContextWrapper>
      case 'chat_list':
        return <ContextWrapper {...contextProps}><ChatList {...viewProps} /></ContextWrapper>
      case 'chat_message':
        return <ContextWrapper {...contextProps}><ChatMessage {...viewProps} /></ContextWrapper>
      case 'order_list':
        return <ContextWrapper {...contextProps}><OrderList {...viewProps} /></ContextWrapper>
      case 'house_list':
        return <ContextWrapper {...contextProps}><HouseList {...viewProps} /></ContextWrapper>
      case 'calendar_manage':
        return <ContextWrapper {...contextProps}><CalendarManage {...viewProps} /></ContextWrapper>
      case 'mine_home':
        return <ContextWrapper {...contextProps}><MineHome {...viewProps} /></ContextWrapper>
      case 'editPersonalDetails':
可以找到对应的组建(新建组建id和名字也要在这个routes中注册)
然后找到id是editName对应 host / components / mine / edit_name.js文件中的
export default class EditName extends Component
这个组建, 则点push,也就是跳转到前面的页面是EditName这个组建渲染的页面

import React, {Component} from 'react';
import {View, Text, Image, StyleSheet, ScrollView, TextInput} from 'react-native';

import {I18n, S, COLOR} from '../../utils/tools'
import SegmentsGroup from '../common/segments_group'
import Segment from '../common/segment'
import LeftBarButton from '../common/navigatorBar/leftBarButton'
import NavigationBar from 'react-native-navbar'
import Input from '../common/input'

export default class EditName extends Component {

  static contextTypes = {
    page: React.PropTypes.object
  };

  constructor(props) {
    super(props)

    const {users} = this.props.users
    let user = users[`${CURRENT_USER.id}`]

    this.state = {
      first_name: user.first_name,
      last_name: user.last_name
    }
  }

  componentDidMount() {

  }

  render() {

    const {actions} = this.props

    var rightButtonConfig = {
      title: I18n.t('common.save'),
      tintColor: '#ff4068',
      handler: () => {
        actions.updateUser({
          data: {
            first_name: this.state.first_name,
            last_name: this.state.last_name,
          },
          success: () => {
            this.context.page.navigator.pop()
          }
        })
      }
    }
    let leftButtonConfig = <LeftBarButton onPress={() => {this.context.page.navigator.pop()}}/>
    return (
      <View style={[S.container, {backgroundColor: "#EFEFEF"}]}>
        <NavigationBar title={{title:I18n.t('my.name')}} leftButton={leftButtonConfig} rightButton={rightButtonConfig}/>
        <ScrollView>
          <View style={styles.container}>
            <SegmentsGroup>
              <Input
                style={styles.input}
                onChangeText={(value) => this._onChange('first_name', value)}
                placeholder={I18n.t('login.last_name')}
                value={this.state.first_name} />
              <Input
                style={styles.input}
                onChangeText={(value) => this._onChange('last_name', value)}
                placeholder={I18n.t('login.first_name')}
                value={this.state.last_name} />
            </SegmentsGroup>
          </View>
        </ScrollView>
    </View>
    )
  }

  _onChange(key, value){
    this.setState({[key]: value})
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    margin: 10,
  },
  input: {
    backgroundColor: '#ffffff',
    height: 44,
    fontSize: 14,
    paddingHorizontal: 10,
    borderBottomWidth: 1,
    borderColor: "#e3e3e3",
  },
})
在这段中
    var rightButtonConfig = {
      title: I18n.t('common.save'),
      tintColor: '#ff4068',
      handler: () => {
        actions.updateUser({
          data: {
            first_name: this.state.first_name,
            last_name: this.state.last_name,
          },
          success: () => {
            this.context.page.navigator.pop()
          }
        })
      }
    }
定义了一个向右跳转的按钮如果按下则进行hundler的处理, 执行动作actions.updateUser这个动作传递当前页面state的值
success:()这个函数是执行成功的回调pop()函数是返回上级页面
actions.updateUser这个action在/app/host/actions/userActions.js这个文件中
这个文件的全部代码为:

import * as types from './actionTypes';

import Octopus from '../utils/octopus'


function fetchUser(params) {
  return (dispatch) => {
    dispatch(requestUser(params))
    return Octopus.fetch({url: `/v1/profile/detail.json`, body: {}})
      .then(response => response.json())
      .then(responseData => {
        if(responseData.meta.status == 200) {
          dispatch(receiveUser(params, responseData))
        } else {
          dispatch({type: types.NEED_LOGIN, params: params})
        }
      })
      .catch(reason => {
        __LOG.info(` ${reason} ............................... `)
      })
  }
}

export function loginSuccess() {
  return {
    type: types.LOGIN_SUCCESS,
    params: {}
  }
}

function requestUser(params) {
  __LOG.info(`  start  fetchMessages  ---------------- `)

  return {
    type: types.REQUEST_USER,
    params: params
  }
}

function receiveUser(params, responseData) {
  __LOG.info(` ${JSON.stringify(responseData)}  `)

  return {
    type: types.RECEIVE_USER,
    user: responseData.data.user,
    receivedAt: Date.now()
  }
}

function shouldFetchUser(state, params) {
  const chats = state.messages.chats
  if (chats.id_list.length <= 0) {
    return true
  } else if (chats.is_fetching) {
    return false
  } else {
    return true
  }
}

export function fetchUserIfNeeded(params = {}) {
  return (dispatch, getState) => {
    if (shouldFetchUser(getState(), params)) {
      return dispatch(fetchUser(params))
    }
  }
}

export function updateUser(params = {}) {
  return (dispatch, getState) => {
    if (shouldFetchUser(getState(), params)) {
      return dispatch(putUser(params))
    }
  }
}

function putUser(params) {
  return (dispatch) => {
    dispatch(requestUser(params))
    return Octopus.fetch({url: `/v1/profile/0.json`, method: "PUT", body: params.data})
      .then(response => response.json())
      .then((responseData) => {
        params.success && params.success()
        dispatch(receiveUser(params, responseData))
      })
      .catch(reason => {
        __LOG.info(` ${reason} ............................... `)
      })
  }
}
其中updateUser即为这个函数:
export function updateUser(params = {}) {
  return (dispatch, getState) => {
    if (shouldFetchUser(getState(), params)) {
      return dispatch(putUser(params))
    }
  }
}
这里的getState()返回的是redux中的那张大表的State(包含一些动作和变量值), params即为components中 actions.updateUser传递的data和执行成功后返回的参数(参看edit_name组建中定义的按钮中的action.updateUser这个动作传递的实参)
    var rightButtonConfig = {
      title: I18n.t('common.save'),
      tintColor: '#ff4068',
      handler: () => {
        actions.updateUser({
          data: {
            first_name: this.state.first_name,
            last_name: this.state.last_name,
          },
          success: () => {
            this.context.page.navigator.pop()
          }
        })
      }
    }
回到action中执行if中的 shouldFetchUser()这个函数:
function shouldFetchUser(state, params) {
  const chats = state.messages.chats
  if (chats.id_list.length <= 0) {
    return true
  } else if (chats.is_fetching) {
    return false
  } else {
    return true
  }
}
是对状态的一些判断,然后执行putUser()这个函数
function putUser(params) {
  return (dispatch) => {
    dispatch(requestUser(params))
    return Octopus.fetch({url: `/v1/profile/0.json`, method: "PUT", body: params.data})
      .then(response => response.json())
      .then((responseData) => {
        params.success && params.success()
        dispatch(receiveUser(params, responseData))
      })
      .catch(reason => {
        __LOG.info(` ${reason} ............................... `)
      })
  }
}
第一个dispatch(requestUser(params))执行requestUser()这个函数,
function requestUser(params) {
  __LOG.info(`  start  fetchMessages  ---------------- `)

  return {
    type: types.REQUEST_USER,
    params: params
  }
}
主要在其中做一些数据的验证, 这里先不看回到 putUser()这个函数:
return Octopus.fetch({url: `/v1/profile/0.json`, method: "PUT", body: params.data})
      .then(response => response.json())
      .then((responseData) => {
        params.success && params.success()
        dispatch(receiveUser(params, responseData))
      })
其中Octopus.fetch({url: `/v1/profile/0.json`, method: "PUT", body: params.data})做带参数请求api, 将api返回的response转化为json格式, 如果执行成功, 执行dispatch(receiveUser(params, responseData)),先执行 receiveUser(params, responseData)这个函数,
function receiveUser(params, responseData) {
  __LOG.info(` ${JSON.stringify(responseData)}  `)

  return {
    type: types.RECEIVE_USER,
    user: responseData.data.user,
    receivedAt: Date.now()
  }
}
则整个dispach如果有参数type: types.RECEIVE_USER, 则带着retuen一起的一些其他参数跳转到RECEIVE_USER对应的reducer中, 通过表 / app / host / actions / actionTypes.js这个文件中找到对应关系(当然如果写新的reducer对应action也要在这里进行注册):
export const NEED_LOGIN = 'NEED_LOGIN'
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
export const NETWORK_REQUEST_ERROR = 'NETWORK_REQUEST_ERROR'
export const ERROR = 'ERROR'

export const FETCH_CHATS = 'FETCH_CHATS'
export const REQUEST_CHATS = 'REQUEST_CHATS'
export const RECEIVE_CHATS = 'RECEIVE_CHATS'

export const FETCH_CHAT_MESSAGES = 'FETCH_CHAT_MESSAGES'
export const REQUEST_CHAT_MESSAGES = 'REQUEST_CHAT_MESSAGES'
export const RECEIVE_CHAT_MESSAGES = 'RECEIVE_CHAT_MESSAGES'

export const SUCCESS_POST_MESSAGES = 'SUCCESS_POST_MESSAGES'

export const RECEIVE_ROOMS = 'RECEIVE_ROOMS'
export const REQUEST_ROOMS = 'REQUEST_ROOMS'
export const RECEIVE_SINGALROOM = 'RECEIVE_SINGALROOM'
export const RECEIVE_DELETEROOM = 'RECEIVE_DELETEROOM'
现在通过dispath函数从action跳到对应type的reducer中, 也就是 / app / host / reducers / users.js这个文件中:
import {
  AsyncStorage
} from 'react-native';


import * as types from '../actions/actionTypes';


const initialState = {
  users: {},
  isLoggingIn: true
};

const _KEY = '@BNBTRIPMOBILE:session';

export default function users(state = initialState, action = {}) {
  switch (action.type) {
    case types.RECEIVE_USER:
      var users = state.users
      users[`${action.user.id}`] = action.user
      return Object.assign({}, state, {users: users})
      break;
    case types.POST_USER:
      var users = state.users
      users[`${action.user.id}`] = action.user
      return Object.assign({}, state, {users: users})
      break;
    case types.NEED_LOGIN:
      AsyncStorage.removeItem(_KEY, (error) => {
        __LOG.info(` ----AsyncStorage removeItem: ${_KEY}------------------------- `)

        if (error) {
          __LOG.error(' - (Error) clearing session in local storage! host... ' + error.message);
        }
      });

      CURRENT_USER = {id: undefined, token: undefined}
      return Object.assign({}, state, {isLoggingIn: false})
      break;
    default:
      return state;
  }
}
在这个文件中
const initialState = {
  users: {},
  isLoggingIn: true
};
这段就是从总的State树中取出对应action的reduter所要用到的异步分数据, 找到case types.RECEIVE_USER:也就是对用action所对应的types的数据处理过程,
      var users = state.users
      users[`${action.user.id}`] = action.user
      return Object.assign({}, state, {users: users})
      break;
action.user即为action中return时候传递带的一些参数, 即dispath函数执行receiveUser函数的return所带的一些其他参数:
function receiveUser(params, responseData) {
  __LOG.info(` ${JSON.stringify(responseData)}  `)

  return {
    type: types.RECEIVE_USER,
    user: responseData.data.user,
    receivedAt: Date.now()
  }
}

这些是把对应component中所要用到的值, 也就是State树中相关的值做对应的改变, 来渲染然dom, 到break直接完毕则这个页面就渲染成功了.

组件中props代表的是redux中state一整棵树的值, 但其中的值是上个页面保留的值(因为整棵树在每一次请求api通过返回值, 在reducer中重组state的时候都会改变), 组件中state的值即为当前组件保存的一些变量值, 可以通过回调来改变state的值来进行对本页面引用state值的dom进行重新渲染



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值