刘贵学博客

Ant Design Mobile 渐进系列教程 4:路由嵌套 Main页面实现

1. 目标

上一章介绍 React-Router的简单实用,本文将继续介绍路由嵌套 ,React父子组件的数据传递方式。

本文中的页面组件关系如下:

其中 Main 是父页面,Index、List 与Setting页面继承自 Main,如下:

2. 原型功能

本文需要实现的功能如下:

  1. 点击Tab按钮,切换页面;
  2. 设置当前页面Tab按钮 selected;
  3. 子页面可以通过属性传入 NavBar 标题;

3. 实现

3.1 Tab 事件

我们接下来,要做的任务是:

  1. 点击Tab按钮,切换页面;

思路:Tab 点击事件触发后,将浏览器的 URL 设置为对应的锚标记。

Tab的点击事件为 onPress,如下官方例子所示:

<TabBar.Item 
...
     onPress={() => {
       this.setState({
         selectedTab: m.url,
       });
     }} 
...

此例子的 this.setState 只是为了切换 TabBar Item的 选中状态(selected),我们可以修改为:

<TabBar.Item 
...
     onPress={() => {
       this.linkTo(m.url);
     }} 
...
linkTo(link) {
     this.context.router.push(link);
  },

this.context.router 需要先声明router为context的对象:

contextTypes: {
        router: React.PropTypes.object
  },

此功能的 预览效果如下:(开始时请等待2秒)
1

总结一下:

  1. 完成目标:点击Tab按钮,切换页面;
  2. 未解问题: 点击底部 Tab后,按钮没有被选中;
  3. 引入Bug:连续点击中间的Tab会产生警告:

    Warning: You cannot PUSH the same path using hash history

意思是: 要跳转的链接跟当前链接不能相同。

不用担心,#2 和 #3 其实是一个问题,将#2解决后,Tab状态为当前链接的Tab为selected,就不会响应 onPress事件了,所以#3也就不存在了。

3.2 Tab selected

上一节,点击 【清单】按钮时,状态没有修改为 selected,我们参考官方例子知道设置为选中其实很简单,对onPress里设置 selectedTab 即可:

 linkTo(link) {
     this.context.router.push(link);
     this.setState({selectedTab: link});
  },

但是,这样也不是最完美的,加入我直接输入

http://localhost:8989/#/list

就不会执行 onPress 函数,所以解决方案应该还要初始化
selectedTab ,实现在 getInitialState 函数开始:

  getInitialState() {

    let link=this.getLink()||'index';

    return {
      selectedTab: link,
    };
  },

  getLink(){
      let links = window.location.hash.match(/(\w+)/g);
      if (!links)
          return null;
      return links[0].toLowerCase();
    },

注:其中 getLink函数里的 window.location.hash.match(/(\w+)/g);
意思是:获取最后一个 / 之后的单词。

上面代码的预览效果如下:
2

3.3 NavBar Title

上面两个例子,我们已经完成切换了 TabBar 与router 的配合使用。

我们可以发现Main页面的 标题区域还没有根据不同的子页面切换。

Main 和 List 的关系是父子组件,如下:

NavBar Title, title 定义在子组件List内, 要传递给 父组件Main,并设置到父组件的方法中。

React的通信是单向的,即从父组件到子组件,总结组件间的通讯场景如下:

  1. 父组件->子组件:props
  2. 子组件->父组件:callback
  3. 子组件->子组件:子组件通过回调改变父组件中的状态,通过props再修改另一个组件的状态

    所以,我们这个例子符合场景2,通过 callback 子组件的数据送给父组件 。

3.3.1 List组件中新建 Prop

  getDefaultProps : function () {
      return {
        title : '历史列表'
      };
    },

3.3.2 Main组件定义 callback函数

增加一个 state: title;

 getInitialState: function() {
    return {
      title: "没有传入标题"
    };
  },
  
   setTitle: function(title) {
    this.setState({title: title});
  },
  
  
  render() {
  <NavBar ref="myNavBar" mode="dark" iconName="">
            {this.state.title}
          </NavBar>
},

将 setTitle 传递给 子组件 List:

 render() {
      ....
            <ContextBox>
              {this.props.children && React.cloneElement(this.props.children, {
                  "setTitle":this.setTitle
                })}
            </ContextBox>
           
           ....
},

注:由于咱们使用了react-router,父子组件的嵌套方式是借助 this.props.children,导致我们不能直接将 属性增加到子组件的标签内,需要React.cloneElement来增加子组件的属性。详情参考 React router and this.props.children - how to pass state to this.props.children

3.3.3 子组件使用callback

   componentDidMount(){
      let title = this.props.title;
      
      //callback: setTitle
      this.props.setTitle(title);
  },

3.3.4 效果预览

Index 与 Setting 页面 参考 List 页面修改后:

3

4. 小结

本文主要实现了三个功能:

  1. 点击Tab按钮,切换页面;
  2. 设置当前页面Tab按钮 selected;
  3. 子页面可以通过属性传入 NavBar 标题;

本文所有代码可以下载:

git clone git@git.coding.net:guixue/antdm-demo.git
cd antdm-demo
git checkout demo4

5. 更新

本文提到的功能,除了使用路由的嵌套外,直接将重复的内容封装为父控件,更更简单。

git clone git@git.coding.net:guixue/antdm-demo.git
cd antdm-demo
git checkout demo4-1

为了长期交流,antdm-demo 仓库被为私有,注册 coding.net 后, 加 guixue 为好友后,会添加进来。