思路图基本是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进行重新渲染

被折叠的 条评论
为什么被折叠?



