思路图基本是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中注册)
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()函数是返回上级页面
这个文件的全部代码为:
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'
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进行重新渲染