路由的封装

发布于 6 年前作者 mingduan2555 次浏览最后编辑 6 年前来自 share

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

10 回复
leigu
leigu1 楼6 年前

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

yaoli
yaoli2 楼6 年前

/**

  * 页面跳转

  * @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(‘&’)

                })

        }

},

fangli
fangli3 楼6 年前

楼主你好,如果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

naxia
naxia4 楼6 年前

楼主,您好。个人观点,感觉您的这个写法还是有些繁琐,比如需要提前在 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

        }

}

linxiuying
linxiuying5 楼6 年前

不错

chao99
chao996 楼6 年前

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

路由还有

  • isCurrent

  • from

  • to

  • webview_id

  • query边缘情况

  • decode

  • normalize

  • 当前路由信息

  • success回调

  • onItemTap

  • Webview里js sdk跳转

yaoli
yaoli7 楼6 年前

不错,学习了

mintao
mintao8 楼6 年前

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

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

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

chao09
chao099 楼6 年前

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

pingxie
pingxie10 楼4 年前

很有价值,感谢分享!