通常在解析location.search(?及后续由&符号链接的字符串)的时候,我们会搬出类似qs、 query-string这些解析神器来把它们转成如下形式。
{[key:string]:string|string[]|undefined}
比如:
'?id=123456&name=jimmy&page=abc&active=true'
转成
{
id:'123456',
name:'jimmy',
page:'abc', //可能会引起应用问题
active:'true'
}
这些工具能把一段location.search转成这样一个object,这是一个很棒的功能。 但在上面这个例子中我们可能更希望这些解析出来的数据具备类型,以便我们可以直接用到渲染组件中去。 另外由于location对应的整个url在浏览器上是可以由用户随意修改的, 所以用户可能会修改search,并给你的应用输入一些错误(可能致命)的信息,比如上例中的page:'abc'。 当你直接带这page='abc'的数据状态去渲染分页器组件的时候,分页器能开心吗?如果你直接把这段数据发送给后端服务器,服务器能消化吗?
- 所有数据都是字符串,不方便渲染、请求以及数据处理。
- 缺少校验,可能引入致命错误。
针对以上问题,对qs进行一个简单的升级,让它的解析数据具备类型甚至错误矫正功能,这就是type-qs了。type-qs引用了以下包和技术:
并导出支持:
虽然在typescript环境下使用体验最好,但并不代表,离开typescript它就用不了了。
与qs的用法区别
- type-qs在qs parse方法的第二个参数option中增加了template(解析器模版)和defaults(解析值为undefined时可取的默认值)字段
校验及类型转化
import {parse} from 'type-qs';
import {Parsers} from 'type-qs/libs';
...
const template={ //解析模版
id:Parsers.natural(), //自然数解析器 0,1,2,3, ...... 当前字符串必须能解析成自然数,否则返回undefined
name:Parsers.string(true), //字符串解析器,如果传入参数true,最终结果中当前字段值为trim过的字符串
active:Parsers.boolean(), //布尔解析器,如果字符串数据是'true'或'false',则被解析成对应的布尔值,否则为undefined
role:Parsers.enum(['GUEST','USER','MASTER','ADMIN']),
//枚举规范器,规定字符串在枚举数组中有值==(注意不是===),并返回枚举中==的值,如找不到,返回undefined
page:Parsers.natural() //自然数解析器, 注意,因为被解析字符串中的page=abc,并非自然数,所以自然数解析器返回undefined
};
const query = parse('id=123456&name= jimmy &active=true&role=MASTER&page=abc',{template});
...
console.log(query);
/*** result ***/
{
id:123456,
name:'jimmy',
active:true,
role:'MASTER',
page:undefined
}
校验失败使用默认数据
import {parse} from 'type-qs';
import {Parsers} from 'type-qs/libs';
...
const template={
id:Parsers.natural(),
name:Parsers.string(true),
active:Parsers.boolean(),
role:Parsers.enum(['GUEST','USER','MASTER','ADMIN']),
page:Parsers.natural()
};
const defaults={
role:'GUEST', //注意:url中role=MASTERR(不是MASTER),在枚举数据中找不到,所以本该返回undefined
//但遇到了好心的默认数据,所以最终结果为默认数据GUEST
page:1 //page也遇到了默认数据,所以变成了1
};
const query = parse('id=123456&name= jimmy &active=true&role=MASTERR&page=abc',{template,defaults});
...
console.log(query);
/*** result ***/
{
id:123456,
name:'jimmy',
active:true,
role:'GUEST',
page:1
}
截取关心数据(只有在parse option.template中出现过的key才能被解析至最终结果)
import {parse} from 'type-qs';
import {Parsers} from 'type-qs/libs';
...
const template={
ids:Parsers.array(), //数组解析器,会把字符串split成字符串数组,或直接转接内部qs产生的字符串数组
}
const query=parse('ids=1%2C2%2C3&useless=123',{template,defaults:{useless:'123'}});
//ids=1,2,3&useless=123
...
console.log(query);
/*** result ***/
{
ids:['1','2','3']
} //key 'useless'并没有出现在最终结果中,因为我们没有在模版中描述它,所以,被忽略了
把字符串类型数组转成其他类型的数组(Parsers.array(mapper?:(value:string)=>any))
import {parse} from 'type-qs';
import {Parsers} from 'type-qs/libs';
...
const template={
ids:Parsers.array(Parsers.natural()), //Parsers.natural()返回的是一个方法(返回方法描述:(value?:string)=>number),
// 该方法的作用与array.map的入参方法作用相同(数据映射)
}
const query=parse('ids=1%2C2%2C3',{template}); //the url like ids=1,2,3
...
console.log(query);
/*** result ***/
{
ids:[1,2,3]
}
自定义Parser:(value?:string|string[])=>any
import {parse} from 'type-qs';
const numberToBoolean=(value:string='')=>{
if(value.trim()==='1'){
return true;
}
if(value.trim()==='0'){
return false
}
}
const template={
active:numberToBoolean
}
const query=parse('active=1',{template});
...
console.log(query);
/*** result ***/
{
active:true
}
嵌套对象解析
import {parse,stringify} from 'type-qs';
import {Parsers} from 'type-qs/libs';
const source={
id:1,
more:{
active:true,
name:'Jimmy',
size:'ab'
}
};
const template={
id:Parsers.natural(),
more:{
active:Parsers.boolean(),
name:Parsers.string(),
size:Parsers.natural()
}
};
const defaults={
more:{
size:10
}
};
const search=stringify(source); //id=1&more%5Bactive%5D=true&more%5Bname%5D=Jimmy&more%5Bsize%5D=ab
const result=parse(search,{template,defaults});
...
console.log(result);
/*** result ***/
{
id:1,
more:{
active:true,
name:'Jimmy',
size:10 //from defaults
}
}
时间解析
import {parse} from 'type-qs';
import {Parsers,startOfDay,pattern} from 'type-qs/libs';
const template={
start:Parsers.date(startOfDay,pattern('YYYY-MM-DD HH:mm:ss')),
end:Parsers.date(endOfDay,toDatetimeString)
};
const data=parse('start=2020-01-01%2011%3A11%3A11&end=2020-12-13%2010%3A01%3A18',{template});
/*** result ***/
{
start:'2020-01-01 00:00:00',
end:'2020-12-13 23:59:59'
}
function parse(search: string, opt?: IParseOptions & { defaults?: any, template?: Template }): T | ParsedQs
- search:url参数,比如:window.location.search
- option:配置项
option结构如下:
template:Template,可选项,如不配置,则失去类型转化功能
type Parser = (value?: string|string[]) => any|void; //解析方法,入参为当前解析得到的原字符串
type Template = {
[key: string]: Template | Parser //解析模版,由解析方法或子解析模版构成,key为需要解析的字段名
} | Parser[];
defaults:any,可选项,如设置了template,在解析值某项为undefined时,解析器会尝试到defaults的同路径下获取默认值
其他:IParseOption,可参考qs api中parse方法的选项
参考qs api中stringify方法
系统默认提供了一套基础类型解析器,大家可以直接使用
返回一个数字解析器,解析js中可以转换为number的类型,如:'1','1.2'......
如不能转为数字类型则返回undefined
返回一个自然数解析器,解析自然数形态,如:'0','1','2'......
如不能转为自然数则返回undefined
返回一个整数解析器,解析整数形态,如:'-1','0','1','2'......
如不能转为自然数则返回undefined
返回一个字符串解析器
如value为undefined返回空字符串,否则根据trim的设置来决定是否去字符串的前后空格
返回一个布尔解析器,'true'|'false'
如不能转为boolean则返回undefined
返回一个枚举解析器,必须以数组形式列举枚举范围,如
- [1,'B']=>'1',则解析为1,
- [1,'B']=>'B',则解析为'B',
- [1,'B']=>'2',则解析为undefined
返回一个数组解析器,可通过mapper对解析出的字符串数组做map操作
如:'1,2,3'=>['1','2','3'],若使用mapper,如:Parsers.array(Parsers.natural()),则解析为[1,2,3]
返回一个正则表达式解析器,可通过配置regExp正则表达式来判断是否时原字符串还是undefined
type DateLike = string | number | Date;
type DateLikeReduce = (dateLike: DateLike) => DateLike;
返回一个类Date类型解析器,可解析如:'YYYY-MM-DD'||'YYYY-MM-DD HH:mm:ss'||'1591258358051'这一类可转为Date类型的数据。
- 若字符串不能转成Date类型数据,则返回undefined。
- 若没有使用dateLikeReduces,则返回Date类型数据。
- 若使用dateLikeReduces,则根据dateLikeReduces顺序执行得到最终的DateLike结果。
在type-qs/libs库中提供了一些默认DateLikeReduce:
- startOfDay(dateLike: DateLike)=>Date; 每日开始时间,如:new Date('2020-03-12 00:00:00')[ms:000]
- endOfDay(dateLike: DateLike)=>Date; 每日结束时间,如:new Date('2020-03-12 23:59:59')[ms:999]
- toDateString(date: DateLike)=>string; 标准日期字符串,如:'2020-03-12'
- toDatetimeString(date: DateLike)=>string; 标准日期时间字符串,如:'2020-03-12 12:11:08'
- pattern(pat: string)=>formatDateLike(dateLike: DateLike)=>string; 自定是时间格式字符串,注意:它调用返回的方法才是DateLikeReduce
import {parse} from 'type-qs';
import {Parsers,startOfDay,pattern,endOfDay,toDatetimeString} from 'type-qs/libs';
const template={
start:Parsers.date(startOfDay,pattern('YYYY-MM-DD HH:mm:ss')),
end:Parsers.date(endOfDay,toDatetimeString)
};
const data=parse('start=2020-01-01%2011%3A11%3A11&end=2020-12-13%2010%3A01%3A18',{template});
/*** result ***/
{
start:'2020-01-01 00:00:00',
end:'2020-12-13 23:59:59'
}
Parsers.date的扩展方法,返回的是标准日期格式化后的字符串数据'YYYY-MM-DD'或undefined
Parsers.date的扩展方法,返回的是标准日期时间格式化后的字符串数据'YYYY-MM-DD HH:mm:ss'或undefined
#总结 如果觉得对您有帮助的话,请动动手,给个小星星呗。