评论

路由的封装

小程序提供了路由功能来实现页面跳转,通过封装,我们可以实现诸如路由管理、简化api等功能。

小程序提供了路由功能来实现页面跳转,但是在使用的过程中我们还是发现有些不方便的地方,通过封装,我们可以实现诸如路由管理、简化api等功能。

页面的跳转存在哪些问题呢?

  1. 与接口的调用一样面临url的管理问题;
  2. 传递参数的方式不太友好,只能拼装url;
  3. 参数类型单一,只支持string。

alias

第一个问题很好解决,我们做一个集中管理,比如新建一个router/routes.js文件来实现alias:

// routes.js
module.exports = {
  // 主页
  home: '/pages/index/index',
  // 个人中心
  uc: '/pages/user_center/index',
};

然后使用的时候变成这样:

const routes = require('../../router/routes.js');

Page({
  onReady() {
    wx.navigateTo({
      url: routes.uc,
    });
  },
});

query

第二个问题,我们先来看个例子,假如我们跳转pages/user_center/index页面的同时还要传userId过去,正常情况下是这么来操作的:

const routes = require('../../router/routes.js');

Page({
  onReady() {
    const userId = '123456';
    wx.navigateTo({
      url: `${routes.uc}?userId=${userId}`,
    });
  },
});

这样确实不好看,我能不能把参数部分单独拿出来,不用拼接到url上呢?

可以,我们试着实现一个navigateTo函数:

const routes = require('../../router/routes.js');

function navigateTo({ url, query }) {
  const queryStr = Object.keys(query).map(k => `${k}=${query[k]}`).join('&');
  wx.navigateTo({
    url: `${url}?${queryStr}`,
  });
}

Page({
  onReady() {
    const userId = '123456';
    navigateTo({
      url: routes.uc,
      query: {
        userId,
      },
    });
  },
});

嗯,这样貌似舒服一点。

参数保真

第三个问题的情况是,当我们传递的参数argument不是string,而是number或者boolean时,也只能在下个页面得到一个string值:

// pages/index/index.js
Page({
  onReady() {
    navigateTo({
      url: routes.uc,
      query: {
        isActive: true,
      },
    });
  },
});

// pages/user_center/index.js
Page({
  onLoad(options) {
    console.log(options.isActive); // => "true"
    console.log(typeof options.isActive); // => "string"
    console.log(options.isActive === true); // => false
  },
});

上面这种情况想必很多人都遇到过,而且感到很抓狂,本来就想传递一个boolean,结果不管传什么都会变成string。

有什么办法可以让数据变成字符串之后,还能还原成原来的类型?

好熟悉,这不就是json吗?我们把要传的数据转成json字符串(JSON.stringify),然后在下个页面把它转回json数据(JSON.parse)不就好了嘛!

我们试着修改原来的navigateTo

const routes = require('../../router/routes.js');

function navigateTo({ url, data }) {
  const dataStr = JSON.stringify(data);
  wx.navigateTo({
    url: `${url}?jsonStr=${dataStr}`,
  });
}

Page({
  onReady() {
    navigateTo({
      url: routes.uc,
      data: {
        isActive: true,
      },
    });
  },
});

这样我们在页面中接受json字符串并转换它:

// pages/user_center/index.js
Page({
  onLoad(options) {
    const json = JSON.parse(options.jsonStr);
    console.log(json.isActive); // => true
    console.log(typeof json.isActive); // => "boolean"
    console.log(json.isActive === true); // => true
  },
});

这里其实隐藏了一个问题,那就是url的转义,假如json字符串中包含了类似?&之类的符号,可能导致我们参数解析出错,所以我们要把json字符串encode一下:

function navigateTo({ url, data }) {
  const dataStr = encodeURIComponent(JSON.stringify(data));
  wx.navigateTo({
    url: `${url}?encodedData=${dataStr}`,
  });
}

// pages/user_center/index.js
Page({
  onLoad(options) {
    const json = JSON.parse(decodeURIComponent(options.encodedData));
    console.log(json.isActive); // => true
    console.log(typeof json.isActive); // => "boolean"
    console.log(json.isActive === true); // => true
  },
});

这样使用起来不方便,我们封装一下,新建文件router/index.js

const routes = require('./routes.js');

function navigateTo({ url, data }) {
  const dataStr = encodeURIComponent(JSON.stringify(data));
  wx.navigateTo({
    url: `${url}?encodedData=${dataStr}`,
  });
}

function extract(options) {
  return JSON.parse(decodeURIComponent(options.encodedData));
}

module.exports = {
  routes,
  navigateTo,
  extract,
};

页面中我们这样来使用:

const router = require('../../router/index.js');

// page home
Page({
  onLoad(options) {
    router.navigateTo({
      url: router.routes.uc,
      data: {
        isActive: true,
      },
    });
  },
});

// page uc
Page({
  onLoad(options) {
    const json = router.extract(options);
    console.log(json.isActive); // => true
    console.log(typeof json.isActive); // => "boolean"
    console.log(json.isActive === true); // => true
  },
});

route name

这样貌似还不错,但是router.navigateTo不太好记,router.routes.uc有点冗长,我们考虑把navigateTo换成简单的push,至于路由,我们可以使用name的方式来替换原来url参数:

const routes = require('./routes.js');

function push({ name, data }) {
  const dataStr = encodeURIComponent(JSON.stringify(data));
  const url = routes[name];
  wx.navigateTo({
    url: `${url}?encodedData=${dataStr}`,
  });
}

function extract(options) {
  return JSON.parse(decodeURIComponent(options.encodedData));
}

module.exports = {
  push,
  extract,
};

在页面中使用:

const router = require('../../router/index.js');

Page({
  onLoad(options) {
    router.push({
      name: 'uc',
      data: {
        isActive: true,
      },
    });
  },
});

navigateTo or switchTab

页面跳转除了navigateTo之外还有switchTab,我们是不是可以把这个差异抹掉?答案是肯定的,如果我们在配置routes的时候就已经指定是普通页面还是tab页面,那么程序完全可以切换到对应的跳转方式。

我们修改一下router/routes.js,假设home是一个tab页面:

module.exports = {
  // 主页
  home: {
    type: 'tab',
    path: '/pages/index/index',
  },
  uc: {
    path: '/pages/a/index',
  },
};

然后修改router/index.jspush的实现:

function push({ name, data }) {
  const dataStr = encodeURIComponent(JSON.stringify(data));
  const route = routes[name];
  if (route.type === 'tab') {
    wx.switchTab({
      url: `${route.path}`, // 注意tab页面是不支持传参的
    });
    return;
  }
  wx.navigateTo({
    url: `${route.path}?encodedData=${dataStr}`,
  });
}

搞定,这样我们一个router.push就能自动切换两种跳转方式了,而且之后一旦页面类型有变动,我们也只需要修改route的定义就可以了。

直接寻址

alias用着很不错,但是有一点挺麻烦得就是每新建一个页面都要写一个alias,即使没有别名的需要,我们是不是可以处理一下,如果在alias没命中,那就直接把name转化成url?这也是阔以的。

function push({ name, data }) {
  const dataStr = encodeURIComponent(JSON.stringify(data));
  const route = routes[name];
  const url = route ? route.path : name;
  if (route.type === 'tab') {
    wx.switchTab({
      url: `${url}`, // 注意tab页面是不支持传参的
    });
    return;
  }
  wx.navigateTo({
    url: `${url}?encodedData=${dataStr}`,
  });
}

在页面中使用:

Page({
  onLoad(options) {
    router.push({
      name: 'pages/user_center/a/index',
      data: {
        isActive: true,
      },
    });
  },
});

注意,为了方便维护,我们规定了每个页面都必须存放在一个特定的文件夹,一个文件夹的当前路径下只能存在一个index页面,比如pages/index下面会存放pages/index/index.jspages/index/index.wxmlpages/index/index.wxsspages/index/index.json,这时候你就不能继续在这个文件夹根路径存放另外一个页面,而必须是新建一个文件夹来存放,比如pages/index/pageB/index.jspages/index/pageB/index.wxmlpages/index/pageB/index.wxsspages/index/pageB/index.json

这样是能实现功能,但是这个name怎么看都跟alias风格差太多,我们试着定义一套转化规则,让直接寻址的name与alias风格统一一些,pagesindex其实我们可以省略掉,/我们可以用.来替换,那么原来的name就变成了user_center.a

Page({
  onLoad(options) {
    router.push({
      name: 'user_center.a',
      data: {
        isActive: true,
      },
    });
  },
});

我们再来改进router/index.jspush的实现:

function push({ name, data }) {
  const dataStr = encodeURIComponent(JSON.stringify(data));
  const route = routes[name];
  const url = route ? route.path : `pages/${name.replace(/\./g, '/')}/index`;
  if (route.type === 'tab') {
    wx.switchTab({
      url: `${url}`, // 注意tab页面是不支持传参的
    });
    return;
  }
  wx.navigateTo({
    url: `${url}?encodedData=${dataStr}`,
  });
}

这样一来,由于支持直接寻址,跳转home和uc还可以写成这样:

router.push({
  name: 'index',  // => /pages/index/index
});

router.push({
  name: 'user_center',  // => /pages/user_center/index
});

这样一来,除了一些tab页面以及特定的路由需要写alias之外,我们也不需要新增一个页面就写一条alias这么麻烦了。

其他

除了上面介绍的navigateTo和switchTab外,其实还有wx.redirectTowx.navigateBack以及wx.reLaunch等,我们也可以做一层封装,过程雷同,所以我们就不再一个个介绍,这里贴一下最终简化后的api以及原生api的映射关系:

router.push => wx.navigateTo
router.replace => wx.redirectTo
router.pop => wx.navigateBack
router.relaunch => wx.reLaunch

最终实现已经在发布在github上,感兴趣的朋友可以移步了解:mp-router

最后一次编辑于  2019-04-26  
点赞 47
收藏
评论

17 个评论

  • 陈式坚
    陈式坚
    2019-04-29

    嗯 实现了一个路由配置文件.. 继续加油


    路由还有

    - isCurrent

    - from

    - to

    - webview_id

    - query边缘情况

    - decode

    - normalize

    - 当前路由信息

    - success回调

    - onItemTap

    - Webview里js sdk跳转


    2019-04-29
    赞同 2
    回复
  • 王可乐
    王可乐
    2019-05-08

    不错,学习了

    2019-05-08
    赞同 1
    回复
  • 太阳
    太阳
    2019-04-26

    挺好的,又学了一招,去 站猫网,又可以接单了,

    2019-04-26
    赞同 1
    回复
  • Axs
    Axs
    2019-04-26

    /**

      * 页面跳转

      * @param {object} _this 传入当前 object this

      * @param {string} url 跳转的 url

      * @param {object} objectData 携带参数

      * @param {number} type 0.为默认新页面打开 1.为当前页面打开

      */

    pageJump(_this, url, objectData = {}, type = 0) {

            let currentUrl = getCurrentPages()[getCurrentPages().length - 1].route;

            let targetparentUrl = this.analysisUrl(_this, url);

            let urlText = "";

            let bannerList = _this.$parent.config.pages;

            if (!targetparentUrl && bannerList.indexOf('pages/' + url) > -1) { // 返回根目录

                    type = 2;

                    if (currentUrl.split('/').length == 3) {

                            urlText += '../' + url

                    } else if (currentUrl.split('/').length == 2) {

                            urlText += './' + url

                    }

            } else {

                    if (currentUrl.split('/').indexOf(targetparentUrl) == -1) {

                            if (currentUrl.split('/').length == 3) {

                                    urlText += '../' + (targetparentUrl == false ? '' : targetparentUrl + '/') + url;

                            } else {

                                    urlText += './' + (targetparentUrl == false ? '' : targetparentUrl + '/') + url;

                            }

                    } else {

                            urlText += './' + url;

                    }

            }

            if (objectData) {

                    let tempUrlText = [];

                    for (let key in objectData) {

                            if (typeof objectData[key] == 'object') {

                                    tempUrlText.push(key + '=' + JSON.stringify(objectData[key]))

                            } else {

                                    tempUrlText.push(key + '=' + objectData[key])

                            }

                    }

                    urlText += '?' + tempUrlText.join('&');

            }

            wx.showTabBar();

            if (type == 0) {

                    _this.$preload('data', objectData)

                    wepy.navigateTo({

                            url: urlText

                    });

            } else if (type == 1) {

                    _this.$preload('data', objectData)

                    wepy.redirectTo({

                    url: urlText

            });

            } else if (type == 2) {

                    let objectDataTextList = [];

                    for (let key in objectData) {

                            objectDataTextList.push(`${key}=${objectData[key]}`)

                    }

                    wx.reLaunch({

                            url: urlText + '?' + objectDataTextList.join('&')

                    })

            }

    },


    2019-04-26
    赞同 1
    回复
  • Axs
    Axs
    2019-04-26

    楼主,您好。个人观点,感觉您的这个写法还是有些繁琐,比如需要提前在 routes/index.js  中配置你好的路由,再假设在需要分包或者是迁移分包(将A包中a1页面迁移到B包中)的场景下就需要重新配置您的 routes/index.js  中的配置。以下是我所使用的路由发法(代码是wepy的),个人认为还不错。迁移分包或者是其他的处理只需要修改该 app.js 即可。

    /**

      * 拆包查询前缀

      * @param {object} _this 传入当前的 this 对象

      * @param {string} url 需要跳转的url

      */

    analysisUrl(_this, url) {

            for (let i = 0; i < _this.$parent.config.pages.length; i++)

                    if (_this.$parent.config.pages[i].indexOf(url) != -1)

                            return false;

                    for (let i = 0; i < _this.$parent.config.subPackages.length; i++)

                            for (let j = 0; j < _this.$parent.config.subPackages[i].pages.length; j++) {

                                    if (_this.$parent.config.subPackages[i].pages[j] == url)

                                            return _this.$parent.config.subPackages[i].name

            }

    }

    2019-04-26
    赞同 1
    回复
  • 2019-05-14

    不错

    2019-05-14
    赞同
    回复
  • 黄昏
    黄昏
    2019-04-30

    感觉还可以,路径最好可以可以动态的获取,就像path 模块那样处理,不过无伤大雅,

    改进的我觉得每个页面都引入路由文件 很烦的,可以通过装饰器的方法 注入到每个页面中,写起来更舒服

    还有写道的  数据解析 也可以统一注入到页面的 onLoad 函数中  体验会好很多

    2019-04-30
    赞同
    回复 1
    • 2022-06-08
      请问什么是装饰器方法注入?
      2022-06-08
      回复
  • Zzx
    Zzx
    2019-04-28

    唯一的好处就是改页面路径,或者说分包的时候不用一个个找着改。。。但是其实找也挺快的

    2019-04-28
    赞同
    回复
  • 逐梦冰川
    逐梦冰川
    2019-04-27

    楼主你好,如果url参数是中文的时候,分享转发之后,直接打开中文参数会出现乱码问题。请问如何解决。


    问题链接如下

    https://developers.weixin.qq.com/community/develop/doc/000c44a3fd47308d2676ba2725bc00?highLine=url%2520%25E5%258F%2582%25E6%2595%25B0%2520%25E4%25B9%25B1%25E7%25A0%2581

    2019-04-27
    赞同
    回复
  • 梁海涛
    梁海涛
    2019-04-27

    很有价值,感谢分享!


    2019-04-27
    赞同
    回复

正在加载...

登录 后发表内容