如何在React Native中动态更改<navigationcardstack>的方向?

时间:2016-08-24 17:15:42

标签: javascript reactjs react-native redux react-jsx

在React Native + Redux中,根据路由,为direction动态更改过渡动画的<NavigationCardStack/>属性的正确方法是什么?

例如,当我使用<Navigator/>时,我会动态设置configureScene属性,并根据不同的路径运行:

  configureScene(route, routeStack) {
    if(route.type === 'Modal'){
      return Navigator.SceneConfigs.FloatFromBottom
    }
    return Navigator.SceneConfigs.PushFromRight
  }

<Navigator
    configureScene={this.configureScene}
    ...
/>

修改

这是我的设置 - 终极版:

function mapStateToProps(state) {
  return {
    navigation: state.navReducer,
  }
}

export default connect(
  mapStateToProps,
  {
    pushRoute: (route) => push(route),
    popRoute: () => pop(),
  }
)(NavigationRoot)

我的减速机(navReducer.js):

const initialState = {
  index: 0,
  key: 'root',
  routes: [{
   key: 'login',
   title: 'Login',
   component: Login,
   direction: 'horizontal',
  }]
}

function navigationState (state = initialState, action) {
  switch(action.type) {
    case PUSH_ROUTE:
      if (state.routes[state.index].key === (action.route && action.route.key)) return state
    return NavigationStateUtils.push(state, action.route)

    case POP_ROUTE:
      if (state.index === 0 || state.routes.length === 1) return state
      return NavigationStateUtils.pop(state)

   default:
     return state

  }
}

export default navigationState

这些方法处理推送和弹出以及如何设置导航栏后退(弹出)按钮:

  _handleBackAction() {
    if (this.props.navigation.index === 0) {
      return false
    }
    this.props.popRoute()
    return true
  }

  _handleNavigate(action) {
    switch (action && action.type) {
      case 'push':
        this.props.pushRoute(action.route)
        return true
      case 'back':
      case 'pop':
        return this._handleBackAction()
      default:
        return false
    }
  }

renderOverlay = (sceneProps) => {
if(0 < sceneProps.scene.index)
{
  return (
    <NavigationHeader
      {...sceneProps}
      renderLeftComponent={() => {
        switch(sceneProps.scene.route.title){
          case 'Home':
            return (
              <TouchableHighlight onPress={() => this._handleBackAction()}>
                <Text}>X</Text>
              </TouchableHighlight>
            )

并通过如此组件调用:

const route = {
  home: {
    type: 'push',
    route: {
      key: 'home',
      title: 'Home',
      component: Home,
      direction: 'vertical',
    }
  }
}

2 个答案:

答案 0 :(得分:1)

ActionTypes.js

/*
* action types
*/
export const PUSH_ROUTE = 'PUSH_ROUTE'
export const POP_ROUTE = 'POP_ROUTE'

navigation.js(导航动作创建者)

import { PUSH_ROUTE, POP_ROUTE} from '../constants/ActionTypes'
export function pushRoute(route){
  return ({
    type: PUSH_ROUTE,
    route
  })
}
export function popRoute(){
  return ({
    type:POP_ROUTE
  })
}

navigation.js(导航缩减器)

import { PUSH_ROUTE, POP_ROUTE } from '../constants/ActionTypes'
import { NavigationExperimental } from 'react-native'

const {
    StateUtils: NavigationStateUtils
} = NavigationExperimental
//direction : horizontal vertical
const initialState = {
  index: 0,
  routes: [{
    key: 'tabapp',
    direction: 'horizontal'
  }]
}

function navigation(state = initialState, action){
  switch(action.type) {
    case PUSH_ROUTE:
      if(state.routes[state.index].key === (action.route && action.route.key))
        return state
      return NavigationStateUtils.push(state, action.route)

    case POP_ROUTE:
      if(state.index === 0 || state.routes.length === 1)
        return state
      return NavigationStateUtils.pop(state)

    default:
      return state
  }
}
export default navigation

D8Navigator.js(应用程序导航)

//Note:NavigationCardStack dirction 


import React, { Component } from 'react';
import {
    NavigationExperimental,
  StyleSheet,
  Text,
  View
} from 'react-native';
import { connect } from 'react-redux'
import { pushRoute, popRoute } from './actions/navigation'
import D8TabsView from './tabs/D8TabsView'
import LoginView from './tabs/user/LoginView'

const {
  CardStack: NavigationCardStack
} = NavigationExperimental

class D8Navigator extends Component {
  constructor(props){
    super(props)
    this._onPopRoute = this._onPopRoute.bind(this)
    this._renderScene = this._renderScene.bind(this)
  }
  _onPopRoute(){
    this.props.dispatch(popRoute())
  }
  _renderScene(sceneProps){
    const {route} = sceneProps.scene
    switch(route.key){
      case "tabapp":
      return (
        <D8TabsView />
      )
      case "loginview":
        return (
          <LoginView />
        )
      default:
        return
    }
  }
  render(){
    let { navigationState } = this.props
    return (
      <NavigationCardStack
        direction={navigationState.routes[navigationState.index].direction}
        navigationState={this.props.navigationState}
        onNavigateBack={this._onPopRoute}
        renderScene={this._renderScene} />
    )
  }
}

function mapStateToProps(state){
  return {
    navigationState: state.navigation
  }
}
export default connect(mapStateToProps)(D8Navigator)

D8TabsView.ios.js

...
import { connect } from 'react-redux'
import { pushRoute, popRoute } from '../actions/navigation'
...
render(){
        return (
            <TabBarIOS
        tintColor="#f33"
        barTintColor="#fff"
        unselectedTintColor="#888">
                .....

        <Icon.TabBarItemIOS
          title="My"
          iconName="ios-person-outline"
          selectedIconName="ios-person"
          selected={this.state.selectedTab === 'starred'}
          onPress={() => {
            this.props.dispatch(pushRoute({
              key:'loginview', direction:'vertical'
            }))
          }}>
        </Icon.TabBarItemIOS>
            </TabBarIOS>
        )
    }

“我的”tabBarItem onPress使用'vertical'调度loginview场景。

LoginView.js

import React, { Component } from 'react';
import {
  StyleSheet,
  Text,
  View,
  TouchableHighlight
} from 'react-native';
import { connect } from 'react-redux'
import { popRoute } from '../../actions/navigation'

class LoginView extends Component {
  render(){
    return (
      <View>
        <Text>Login</Text>
        <TouchableHighlight
          underlayColor="#D0D0D0"
          onPress={()=>this.props.dispatch(popRoute())}>
          <Text>
            Close
          </Text>
      </TouchableHighlight>
      </View>
    )
  }
}
export default connect()(LoginView) 

答案 1 :(得分:0)

当您的导航卡引用路线对象并导航弹出动作时, 当弹出操作删除路径对象时,导航卡丢失了参考。

所以我推荐写在底部的方法。

/constants/ActionTypes.js

// navigation action types
export const NAV_PUSH_ROUTE = 'NAV_PUSH_ROUTE';
export const NAV_POP_ROUTE = 'NAV_POP_ROUTE';

// tab action types
export const TAB_SELECT = 'TAB_SELECT';

/actions/navigationActions.js

/**
 * Navigation Actions
 * @flow
 */

import { NAV_POP_ROUTE, NAV_PUSH_ROUTE } from '../constants/ActionTypes';

const navPush = route => {
  const rtRoute = route;
  if (route && route.direction === undefined) rtRoute.direction = 'horizontal';

  return {
    type: NAV_PUSH_ROUTE,
    route: rtRoute,
  };
};

const navPop = () => (
  {
    type: NAV_POP_ROUTE,
  }
);

module.exports = {
  navPush,
  navPop,
};

/reducer/navigationReducer.js

/**
 * Navigation reducer
 * @flow
 */

import { NavigationExperimental } from 'react-native';

import { NAV_POP_ROUTE, NAV_PUSH_ROUTE } from '../constants/ActionTypes';

const {
  StateUtils: NavigationStateUtils,
} = NavigationExperimental;

const initialState = {
  index: 0,
  key: 'global',
  routes: [
    {
      key: 'feed',
      title: 'Feed',
    },
  ],
};

const navigationState = (state = initialState, action) => {
  switch (action.type) {
    case NAV_PUSH_ROUTE:
      if (state.routes[state.index].key === (action.route && action.route.key)) {
        return state;
      }

      const navigationNewState = NavigationStateUtils.push(state, action.route);
      return {
        ...navigationNewState,
        direction: action.route.direction,
      };

    case NAV_POP_ROUTE:
      if (state.index === 0 || state.routes.length === 1) {
        return state;
      }
      return NavigationStateUtils.pop(state);

    default:
      return state;
  }
};

module.exports = navigationState;

/components/global/ECNavigator.js

/**
 * Everychoose navigation compoment for example
 * @flow
 */

import React, { Component, PropTypes } from 'react';
import {
  NavigationExperimental,
  BackAndroid,
  TouchableHighlight,
  PixelRatio,
  StyleSheet,
  Text,
  View,
} from 'react-native';
import { connect } from 'react-redux';
import { navPush, navPop } from '../../../actions';

import LoginScreen from '../../views/LoginScreen';

const {
  CardStack: NavigationCardStack,
} = NavigationExperimental;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#fff',
  },
  row: {
    padding: 15,
    backgroundColor: '#30b3d8',
    borderBottomWidth: 1 / PixelRatio.get(),
    borderBottomColor: '#CDCDCD',
  },
  rowText: {
    fontSize: 17,
  },
  buttonText: {
    fontSize: 17,
    fontWeight: '500',
  },
});

const routes = {
  loginScreenV: {
    type: 'push',
    route: {
      key: 'LoginScreen',
      title: 'Login screen vertical',
      direction: 'vertical',
    },
  },
  loginScreenH: {
    type: 'push',
    route: {
      key: 'LoginScreen',
      title: 'Login screen horizontal',
    },
  },
};

class ECNavigator extends Component {
  constructor(props) {
    super(props);

    this._renderScene = this._renderScene.bind(this);
    this._handleBackAction = this._handleBackAction.bind(this);
    this._handleNavigate = this._handleNavigate.bind(this);
  }

  componentDidMount() {
    BackAndroid.addEventListener('hardwareBackPress', this._handleBackAction);
  }

  componentWillUnmount() {
    BackAndroid.removeEventListener('hardwareBackPress', this._handleBackAction);
  }

  _handleBackAction() {
    if (this.props.navigation.index === 0) {
      return false;
    }

    this.props.popRoute();
    return true;
  }

  _handleNavigate(action) {
    switch (action && action.type) {
      case 'push':
        this.props.pushRoute(action.route);
        return true;

      case 'back':
      case 'pop':
        return this._handleBackAction();

      default:
        return false;
    }
  }

  _renderScene(props) {
    const { route } = props.scene;

    switch (route.key) {
      case 'LoginScreen':
        return <LoginScreen navGoBack={this._handleBackAction} />;
      default:
        return (
          <View style={styles.container}>
            <TouchableHighlight
              style={styles.row}
              underlayColor="#d0d0d0"
              onPress={() => this._handleNavigate(routes.loginScreenV)}
            >
              <Text style={styles.buttonText}>
                Go to LoginScreen (Vertical)
              </Text>
            </TouchableHighlight>
            <TouchableHighlight
              style={styles.row}
              underlayColor="#d0d0d0"
              onPress={() => this._handleNavigate(routes.loginScreenH)}
            >
              <Text style={styles.buttonText}>
                Go to LoginScreen (Horizontal)
              </Text>
            </TouchableHighlight>
          </View>
        );
    }
  }

  render() {
    const { navigation } = this.props;

    return (
      <NavigationCardStack
        style={{ flex: 1 }}
        direction={navigation.direction}
        onNavigateBack={this._handleBackAction}
        navigationState={this.props.navigation}
        onNavigate={this._handleNavigate}
        renderScene={this._renderScene}
      />
    );
  }
}

ECNavigator.propTypes = {
  navigation: PropTypes.object,
  pushRoute: PropTypes.func,
  popRoute: PropTypes.func,
};

const select = store => ({
  navigation: store.navigation,
});

const actions = dispatch => ({
  pushRoute: route => dispatch(navPush(route)),
  popRoute: () => dispatch(navPop()),
});

module.exports = connect(select, actions)(ECNavigator);

/components/view/LoginScreen.js

/**
 * LoginScreen compoment
 * @flow
 */

import React, { PropTypes } from 'react';
import {
  TouchableHighlight,
  PixelRatio,
  StyleSheet,
  Text,
  View,
} from 'react-native';
import { connect } from 'react-redux';
import { navPush, navPop } from '../../../actions';

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#fff',
  },
  row: {
    padding: 15,
    backgroundColor: '#30b3d8',
    borderBottomWidth: 1 / PixelRatio.get(),
    borderBottomColor: '#CDCDCD',
  },
  rowText: {
    fontSize: 17,
  },
  buttonText: {
    fontSize: 17,
    fontWeight: '500',
  },
});

const LoginScreen = props => (
  <View style={styles.container}>
    <Text style={styles.text}>Login Screen</Text>
    <TouchableHighlight
      style={styles.row}
      underlayColor="#d0d0d0"
      onPress={props.navGoBack}
    >
      <Text style={styles.buttonText}>
        Go back
      </Text>
    </TouchableHighlight>
  </View>
);

module.exports = LoginScreen;

Result screen - GIF