September 02, 2019

造了一个路由切换的轮子

之前一段时间就大概把这个轮子弄好了,但是一直没有整理成文章。今天完善一下使用这个轮子的例子,顺便发现之前的几个bug,一同修复。看来后面还是需要把测试用例补充,否则很难避免使用起来遗漏的地方。目前这个工具托管在friendly-query,欢迎大家尝试使用并提issue。

背景

通常路由库可以处理从:/path1 => path2的跳转切换不同的页面;但是对/path1?foo=1切换到/path2?foo=2这种情况支持不是很好。如果页面对应的参数不增加到url中,用户在页面更改部分参数后,刷新页面,用户之前选择的这一部分参数就会被重置而不会选中后的页面处理。 在开发的过程中,想实现页面的部分表单参数同步到url的query参数;

  1. 当用户在浏览器点击前进或者后退按钮,则需要监听popstate的事件,然后根据回调事件来监听参数发生变化,根据更改后的url参数,再进行处理;参数发生改变可能只影响一部分,通常只需要触发该部分的回调就可以了;
  2. 而且url的参数都是字符串,而实际可能还需要把字符串转换为需要的复杂数据类型;异步请求数据的时候,可能也需要把复杂的数据类型转换为字符串类型。

总体来说,这个工具就是解决以上两个问题

应用过程

image.png

上面图主要描述了三种情况:

  1. 初始化进入页面 enter page
  2. 页面数据发生改变update data
  3. 用户点击浏览器前进/后退按钮popstate

初始化进入页面

  • 初始化进入页面的时候,需要调用friendly-queryinit方法生成实例
  • 然后调用实例的instance.load()方法,该方法会从url上获取所有参数,并根据初始化传入的转换的数据类型,进行处理;返回的数据则为我们定义的数据类型
  • 有时候还会加多一步,把对应的数据,存到对应的store
  • 调用异步请求方法,通常在方法里面需要调用instance.convert方法,把原有复杂的数据类型转换为字符串类型,把参数发送到后端

页面数据发生改变

通常这一步是用户触发更改不同的参数,前端根据不同的参数请求相应的数据

  • 更新用户更改后的数据
  • 通过instance.convert方法,获取新的数据转换后的字符串参数
  • 根据生成的字符串参数,往history添加记录;添加记录这一步可能部分路由已支持;若不支持,则需要手动调用:history.pushState()添加记录
  • 调用异步请求方法,发送更新后的参数到后端,以获取新的展示内容

用户点击浏览器前进/后退按钮

  • 用户触发前进/后退按钮,触发popstate事件(监听事件这一步,在init的时候进行了绑定)
  • 调用callback函数;这个callback函数为初始化init传入的参数;若对参数进行分多组处理,则改变的参数对应的分组回调才被调用,例如:
1
2
3
4
5
6
7
8
9
10
11
init([{
    type: {
        foo: {}
    },
    callback () {}
}, {
    type: {
        bar: {}
    },
    callback () {}
}])

若在popstate事件发生的时候,foo参数相比旧url发生了改变,则foo对应分组的callback会调用;bar对应的分组的callback不会被调用;这种情况适用于页面有多个不同请求,并且这些请求都需要更新到url。

处理过程

image.png

从图可以看出,核心方法是loadconvert的处理,是字符串数据与复杂数据之间桥梁。

instance.load方法的核心是parse的方法,parse不是只有一个,而是对于多种不同的数据类型,从字符串转换为需要类型的转换函数,例如:IntArrayparse过程是不同的,parse函数主要接收三个参数:

  • {String} str url 对应参数的数据
  • {Any} value 初始化传入该参数的默认数据
  • {Object} option 该参数对应的数据类型的配置

parse对应的处理函数stringify,在instance.convert()实现,则是把不同的数据类型转换为字符串,例如把数组:['foo', 'bar']转换为字符串:'foo,bar',该函数主要接收两个参数:

  • {Array|Object} groupQuery 需要从设定的数据类型转换为url参数所用的字符串类型
  • {Boolean} isMerged 对转换后的数组字符串数据合并到一个对象中,默认合并

更改类型配置

默认支持的类型有:

  1. Int
  2. Float
  3. String
  4. Date
  5. Boolean
  6. Array
  7. IntArray
  8. FloatArray

每种类型都有自己的处理规则,例如Array的配置当中,默认分隔符是逗号:separator: ',';在parse的时候,会根据逗号,来分割字符串,在stringify的时候,会根据逗号,来拼接字符串。如果想更改为别的分隔符,例如更改为连接符号-,则可以在init的时候,传入第二个参数,对需要更改的规则进行处理:

1
2
3
4
5
6
7
8
9
10
init([
    // ...
], {
    Array: {
        separator: '-'
    },
    IntArray: {
        // ...
    }
})

不同数据类型详细的配置,可以看这里

扩充类型

如果上述默认的类型都不能支持项目所用,可以使用extend的全局方法来扩充所需要的类型,下面这个例子是扩充一个DateArray的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// dateformat 为一个日期格式类型处理的库
import dateformat from 'dateformat'
extend({
    DateArray: {
        // 从字符串转换为 Array 的方法
        parse (str, value, option) {
            if (!str) return value

            return str.split(option.separator).map(item => {
                return new Date(item)
            })
        },

        // 从 Array 转换为字符串的方法
        stringify (value, option) {
            if (!value.length) return ''

            return value.map(item => dateformat(item, option.format)).join(option.separator)
        },
        option: {
            // 分隔符
            separator: ',',
            // 转换的时间格式
            format: 'yyyy/mm/dd hh:MM'
        }
    }
})

需要注意的是,使用extend扩充的类型,会全局影响,因此最好在项目入口定义新的类型

目前暂时只支持HTML5 history的模式,暂还没支持hash的路由模式

更详细的API可以查看这里