【问题标题】:Passing props to a dynamic TabNavigator将道具传递给动态 TabNavigator
【发布时间】:2019-01-10 16:39:57
【问题描述】:

(在https://github.com/react-navigation/react-navigation/issues/4059#issuecomment-453100740也问过)

我已将静态 TabNavigator 替换为动态 TabNavigator,并且一切正常。
但是,按预期传递的道具不再以相同的方式传递。
知道如何解决这个问题吗?通过像在静态解决方案中一样传递道具,或者通过传递所需的道具(this.props.navigation)。

这是我的顶级导航器:

export default createDrawerNavigator({
    Drawer: MainDrawerNavigator,
    Main: MainTabNavigator
}, {
  contentComponent: props => <Drawer {...props} />,
});

这是静态选项卡导航器和堆栈之一:

const ProfileStack = createStackNavigator({
  Profile: {
    screen: Profile,
    navigationOptions: () => ({
        title: 'Profile'
    })
  }
}, {
  initialRouteName: 'Profile'
});

ProfileStack.navigationOptions = {
  tabBarLabel: 'Profile',
  tabBarIcon: ({ focused }) => (
    <TabBarIcon
      focused={focused}
      name= 'md-person' />
  )};

const MainTabNavigator = createBottomTabNavigator({
  RequestStack,
  ProfileStack
}, {
    headerMode: 'none',
    initialRouteName: ProfileStack
});

还有Profile 屏幕:

import React from 'react';
import { View, TouchableOpacity } from 'react-native';
import { Container, Header, Content, Text
} from 'native-base';

export default class Profile extends React.Component {
  static navigationOptions = {
    header: null
  };

  constructor(props) {
    super(props);
  }

  render() {
    console.log('in Profile. this.props:');
    console.log(this.props);
    return (
        <Container style={styles.container}>
          <Header>
              <TouchableOpacity
                onPress={() => this.props.navigation.openDrawer()}>
                 <Icon name="md-more" />
              </TouchableOpacity>
          </Header>
          <Content>

           <Text>aaa</Text>
          </Content>
        </Container>
    );
  }
}

点击“md-more”图标打开抽屉(this.props.navigation.openDrawer)。

使用动态选项卡导航器 - openDrawer 不再传递给“配置文件”。

当我将上面的静态选项卡导航器替换为以下动态选项卡时,this.props.navigation.openDrawer 未通过,因此未在“配置文件”中定义(配置文件没有更改,更改仅在底部选项卡中航海家)。

这里是动态标签导航器:

export default class DynamicTabNavigator extends React.Component {

  constructor(props) {
    super(props);
  }

  _tabNavigator() {
    let tabs = {};
    const a = 2; 
    if (a > 1) {   // the actual line is obviously different, I am trying to simplify the example
      tabs = { RequestStack, ManageStack, MessagesStack, ProfileStack };
    } else {
      tabs = { WorkStack, ManageStack, MessagesStack, ProfileStack };
    }

    console.log('in _tabNavigator. this.props.navigation:');
    console.log(this.props.navigation);
    return createBottomTabNavigator(tabs, {
        headerMode: 'none',
    });
  }

  render() {
    const Tabs = this._tabNavigator.bind(this)();
    return (
      <Tabs/>
    );
  }
}

这是来自 DynamicTabNavigator 的 console.log() 的输出:

in _tabNavigator. this.props.navigation:
 Object {
   "actions": Object {
     "closeDrawer": [Function closeDrawer],
     "goBack": [Function goBack],
     "navigate": [Function navigate],
     "openDrawer": [Function openDrawer],
     "setParams": [Function setParams],
     "toggleDrawer": [Function toggleDrawer],
   },
   "addListener": [Function addListener],
   "closeDrawer": [Function anonymous],
   "dangerouslyGetParent": [Function anonymous],
   "dispatch": [Function anonymous],
   "getChildNavigation": [Function getChildNavigation],
   "getParam": [Function anonymous],
   "getScreenProps": [Function anonymous],
   "goBack": [Function anonymous],
   "isFocused": [Function isFocused],
   "navigate": [Function anonymous],
   "openDrawer": [Function anonymous],
   "router": undefined,
   "setParams": [Function anonymous],
   "state": Object {
     "key": "Main",
     "params": undefined,
     "routeName": "Main",
   },
   "toggleDrawer": [Function anonymous],
 }

这是来自 Profile 的 console.log() 的输出,当 DynamicTabNavigator 就位时:

(我希望所有道具,例如 openDrawer,都与 DynamicTabNavigator 相同,但我不明白为什么它们不是)

 in Profile. this.props:
 Object {
   "appMode": "WORK_MODE",
   "dispatch": [Function anonymous],
   "navigation": Object {
     "actions": Object {
       "dismiss": [Function dismiss],
       "goBack": [Function goBack],
       "navigate": [Function navigate],
       "pop": [Function pop],
       "popToTop": [Function popToTop],
       "push": [Function push],
       "replace": [Function replace],
       "reset": [Function reset],
       "setParams": [Function setParams],
     },
     "addListener": [Function addListener],
     "dangerouslyGetParent": [Function anonymous],
     "dismiss": [Function anonymous],
     "dispatch": [Function anonymous],
     "getChildNavigation": [Function getChildNavigation],
     "getParam": [Function anonymous],
     "getScreenProps": [Function anonymous],
     "goBack": [Function anonymous],
     "isFocused": [Function isFocused],
     "navigate": [Function anonymous],
     "pop": [Function anonymous],
     "popToTop": [Function anonymous],
     "push": [Function anonymous],
     "replace": [Function anonymous],
     "reset": [Function anonymous],
     "router": undefined,
     "setParams": [Function anonymous],
     "state": Object {
       "key": "id-1547113035295-8",
       "routeName": "Profile",
     },
   },
   "screenProps": undefined,
 }

关于@dentemm 解决方案的问题:

我不确定如何实施您的解决方案...

  • 假设我的TabRoutes 中有您在示例中指定的三个屏幕
  • 在我的 redux 状态下,我有一个名为“appState”的变量。如果是真的,我想显示第一和第二,如果是假的,我想显示第一和第三。
  • 这是我根据您的示例编写的代码。但是,我不确定CustomTabBar 中包含哪个组件。能详细点吗?

    import React from 'react';
    class CustomTabBar extends React.Component {
    
    render() {
    // a tab bar component has a routes object in the navigation state
    const { navigation } = this.props;
    
      // appState is extracted from redux state, see below
      if (this.props.appState) {
        return (
          <View>
            <???
              name='First'
              onPress={this.navigationHandler}
              focused={navigation.state.index === index}
            />
            <???
              name='Second'
              onPress={this.navigationHandler}
              focused={navigation.state.index === index}
            />
          </View>
        );
      } else {
        return (
          <View>
            <???
              name='First'
              onPress={this.navigationHandler}
              focused={navigation.state.index === index}
            />
            <???
              name='Third'
              onPress={this.navigationHandler}
              focused={navigation.state.index === index}
            />
          </View>
          );
        }
      }
    
      navigationHandler = (name) => {
        const {navigation} = this.props;
        navigation.navigate(name);
      }
    }
    
    const mapStateToProps = state => {
     const { appState } = state.app;
     return { appState };
    };
    
    export default connect(mapStateToProps)(CustomTabBar);
    

【问题讨论】:

  • this.props.navigation 未定义?尝试在组件中传递它&lt;Tabs navigation={this.props.navigation}/&gt;
  • 它不是未定义的。我为两个模块添加了 console.log() 输出。添加您建议的内容会导致应用程序中止并显示以下消息:TypeError: TypeError: TypeError: No "routes" found in navigation state。您是否尝试将 React 组件的导航属性传递给 Navigator 子组件?见reactnavigation.org/docs/en/…
  • 只是一个问题:您真的需要动态的 TabNavigator,还是动态的 TabBar 就足够了?
  • @dentemm 什么是动态 TabBar?我需要根据 redux 状态变量动态设置显示哪些选项卡
  • 您可以轻松创建自定义 TabBar 组件,并将其连接到 redux。我会创建一个答案。

标签: javascript react-native react-navigation


【解决方案1】:

您也可以保持 TabNavigator 不变,并使用自定义 TabBarItem 组件创建自定义 TabBar 组件。您可以将该自定义 TabBar 连接到您的 redux 状态,并根据您的需要隐藏/显示自定义 TabBarItems。

然后您只需像往常一样将所有可能的路线添加到 TabNavigator。

路线

const TabRoutes = createBottomTabNavigator({
  First: {screen: SomeScreen},
  Second: {screen: SomeStack},
  Third: {screen: AnotherStack}
},{
  initialRouteName: 'First',
  tabBarComponent: CustomTabBar
});

自定义标签栏

一些关于如何隐藏标签栏项目的基本示例,显然这需要根据您自己的要求进行调整

import CustomTabBarItem from '...'  ; 

class CustomTabBar extends React.Component {

  render() {

    // a tab bar component has a routes object in the navigation state
    const {navigation, appState} = this.props;
    const routes = navigation.state.routes;

    return (
      <View style={styles.container}>
        // You map over all existing routes defined in TabNavigator
        {routes.map((route, index) => {

              // This could be improved, but it's just to show a possible solution
              if (appState && route.routeName === 'x') {
                return <View/>;
              } else if (!appState && route.routeName === 'y') {
                return <View/>;
              }

              return (<CustomTabBarIcon
                key={route.key}
                name={route.routeName}
                onPress={this.navigationHandler}
                focused={navigation.state.index === index}
                appState={appState}
              />);
        })}
      </View>
    );
  }

  navigationHandler = (name) => {

    const {navigation} = this.props;
    navigation.navigate(name);
  }
}

const styles = StyleSheet.create({
  container: {
    width: '100%',
    flexDirection: 'row'
  }
})

const mapStateToProps = (state) => {
  return {
    appState: state.app.appState // boolean
  };
};

export default connect(mapStateToProps)(CustomTabBar);

CustomTabBarItem

class CustomTabBarItem extends React.PureComponent {
  render() {

    const {name, focused} = this.props;

    return (
      <View style={styles.tabItem}>
        // Some icon maybe
        <Text style={/*different style for focused / unfocused tab*/}>{name}</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  tabItem: {
    flex: 1
  }
})

【讨论】:

  • 谢谢。我会在接下来的几天内查看它并添加我的评论。
  • 我不确定如何实现这一点,但其他答案似乎有效。谢谢
  • 我坚持使用其他解决方案(仍在尝试使其工作),尝试实施您的解决方案。在我上面的问题的末尾看到我的问题。
  • 我更新了我的答案,当 appState 为真或假时,您还创建了一个 CustomTabBarItem 并在 routeName 上进行过滤
  • 感谢您的及时回复,非常感谢!现在我遇到了一些问题,使它看起来像导航选项中定义的默认选项卡。您有完整的工作示例吗? (我在 SO 中看到有一些问题,例如stackoverflow.com/questions/45099753/…)
【解决方案2】:

好的,在查看您的代码后,我发现问题出在哪里......问题是......您没有以正确的方式嵌套导航道具。当您在抽屉导航器中嵌套一个普通的 tabnavigator 时,它可以正常工作,因为您没有渲染任何东西,您正在以反应导航方式进行操作。

但是..当您尝试使用动态选项卡导航器时,您不会将选项卡导航器返回给抽屉导航器,而是返回一个组件 export default class DynamicTabNavigator extends React.Component {},在他的渲染函数里面你有一个tabnavigator...

所以.. 你有 2 种可能的解决方案...

第一个只使用一个函数并在没有自定义组件的情况下调用它

export function tabNavigator() {
    let tabs = {};
    const a = 2; 
    if (a > 1) {   // the actual line is obviously different, I am trying to simplify the example
      tabs = { RequestStack, ManageStack, MessagesStack, ProfileStack };
    } else {
      tabs = { WorkStack, ManageStack, MessagesStack, ProfileStack };
    }

   // console.log('in _tabNavigator. this.props.navigation:');
    //console.log(navigation);
    return createBottomTabNavigator(tabs, {
        headerMode: 'none',
    });
}

在你的根导航器中

import {tabNavigator} from './TabNavigator'
    const Tabs = tabNavigator()
    export default createDrawerNavigator({
        Drawer: MainDrawerNavigator,
        Main: Tabs
    }, {
      contentComponent: props => <Drawer {...props} />,
    });

不知道这样行不行

第二种解决方案

手动将navigator prop传递给动态tabnavigator的所有屏幕,确实很丑,但是当你把navigator放到一个组件里面就可以解决了

_tabNavigator() {
    let tabs = {};
    const a = 2; 
    if (a > 1) {   // the actual line is obviously different, I am trying to simplify the example
      tabs = { RequestStack: {screen:<RequestStack navigation={this.props.navigation}/>, navigationOptions: () => ({
    tabBarLabel:'Request',
    tabBarIcon:<YourComponent/>})}, 
               ManageStack:{screen:<ManageStack navigation={this.props.navigation}/>}, MessagesStack:{screen:<MessagesStack navigation={this.props.navigation}/>},
               ProfileStack:{screen:<ProfileStack navigation={this.props.navigation}/>}};
    } else {
      tabs = { WorkStack, ManageStack, MessagesStack, ProfileStack }; //repeat
    }

    console.log('in _tabNavigator. this.props.navigation:');
    console.log(this.props.navigation);
    return createBottomTabNavigator(tabs, {
        headerMode: 'none',
    });

专业提示:不要在组件内部做动态导航,如果不手动传递,导航道具会丢失

编辑 N2:

const ProfileStack = ({props}) => createStackNavigator({
  Profile: {
    screen: <Profile navigation={props.navigation}/>,
    navigationOptions: () => ({
        title: 'Profile'
    })
  }
}, {
  initialRouteName: 'Profile'
});

【讨论】:

  • 感谢@ValdaXD 的努力。第一个解决方案给出了错误:“函数作为 React 子级无效。”第二种解决方案给出错误:“路由'ProfileStack'的组件必须是React组件。例如:从'./MyScreen'导入MyScreen; ... ProfileStack:MyScreen,}”
  • 实际上第二种解决方案有效,不是使用 ProfileStack,而是使用组件本身。我仍然有显示标签图标的问题,但我相信它可以解决。我建议你删除第一个解决方案...
  • 哦,好的,我编辑了答案以显示热门,以便在每个选项卡上使用 navigationOptions,作为替代方案,您可以根据路由名称(RequestStack 等...)传递它,这里有一个示例 [@ 987654321@,这样你就可以使用默认的导航选项了;)
  • 是的,您还需要将导航道具传递给 stacknavigator [edit n2] 中的每个屏幕。这是做动态事情的问题,react-navigation 在普通导航器的后台处理。作为替代方案,您可以创建 2 个单独的选项卡导航器并根据条件返回它们,或者使用 redux state 作为下面的答案,第一个不是真正动态的,但它使事情变得容易,第二个需要一点练习但是做动态的东西是值得的。
  • 为 profilestack 实现编辑 N 2 ,我想我在导入道具时搞砸了,它应该用花括号 ({props}) ,对不起 ;-;
猜你喜欢
  • 2018-03-12
  • 2018-02-26
  • 2019-01-27
  • 2021-07-08
  • 2018-07-17
  • 1970-01-01
  • 2017-10-03
  • 2021-12-21
相关资源
最近更新 更多