在styled-components的样式声明中做px到rem的自动转换

最近在尝试使用styled-components来做React下的样式开发,这样可以利用js语言的灵活来增强css的能力。

在多屏适配这块,一直是在使用淘宝的lib-flexible

在用styled-components处理px到rem的转换时,有点问题了。styled-components自己本着基础库的原则,是没有附加这些复杂的配置,所以一开始想的是自己写一个函数来做单位换算:

function r(pxValue) {
    const ratio = 20; // 根据项目配置比例的方式自行设定
    pxValue = parseInt(pxValue);

    return pxValue / ratio + 'rem';
}

然后在写样式的时候这样写

// 保留了px的单位而不是传递数字,使得代码具有可读性,方便维护
let AvatarImage = styled(Image)`
    width: ${r('132px')}; 
    height: ${r('132px')};
`;

这样满足了基本需求。

不过作为喜欢偷懒喜欢折腾的程序员,总觉得这样写很啰嗦,所以想着能不能也跟styled-components那样使用标签模版的功能

所以也就有了第二版的function r:

function r(pxValue) {
    const ratio = 20; // 根据项目配置比例的方式自行设定

    // 针对template literals
    if (Array.isArray(pxValue)) {
        pxValue = pxValue[0];
    }

    pxValue = parseInt(pxValue);

    return pxValue / ratio + 'rem';
}

在样式书写上,就可以这样写了:

let AvatarImage = styled(Image)`
    width: ${r`132px`};
    height: ${r`132px`};
`;

少写了两个括号,看着也清晰多了。

不过还是不太满意,因为样式里面如果数值一多,写起来还是有点费事。

既然能够实现单个单位的换算,那用正则表达式匹配所有样式字符串里面的px值,再替换为rem值,再把计算的结果返回给styled-components不就可以了。

想法是好的,不过在读取解析已有的样式模版字符串,正则匹配,还有如何把结果传递给styled-components这些方面,坑还是很多的,折腾了一个多小时,也总算解决了。

这里面,最核心的解析已有的样式模版字符串,styled-components提供了一个helper: css,使得工作量大大减少。

function r(pxValue) {
    const ratio = 20; // 根据项目配置比例的方式自行设定

    // 针对template literals
    if (Array.isArray(pxValue)) {
        pxValue = pxValue[0];
    }

    pxValue = parseInt(pxValue);

    return pxValue / ratio + 'rem';
}

// 把字符串样式里面的px单位换算成rem的
// 支持多行匹配
function transformPxToRem(style) {
    // 避免处理了函数等情况
    if (typeof style !== 'string') {
        return style;
    }

    return style.replace(/\d+px/gm, matched => {
        return r(matched);
    });
}

// 实现在把样式传递给styled之前,预先用transformPxToRem处理
function t(strings, ...interpolations) {
    let styles = css(strings, ...interpolations); // css是styled-components的一个helper
    styles = styles.map(transformPxToRem);

    // 模拟raw的调用
    return [[""], styles]
};

基于以上代码,在最终书写样式的时候就可以这样:

let AvatarImage = styled(Image)(...t`
    width: 132px;
    height: 132px;
`);

以函数的形式调用styled-compoent,而不是标签模版的形式,同时解构t函数的返回值,这样的结果和styled-components直接计算标签模版是一样的。

不过在实现这个功能之后,在写这篇文章的时候,发现lib-flexible不再用rem作为多端适配的方案了,而是改用的vm

不过最核心的预处理部分已经实现了,后面的调整还是很容易做的。

后期的计划:

现在css helper返回的是一个数组,可以考虑join成字符串,去掉多余的空白符,然后实现css to object和object to css。然后再加上plugin机制,使得整个系统更加灵活多变。

说着说着,好像实现了一个简版的postCss。

现在styled-components是有增加babel plugin的计划来打通postCss和styled-compoent,期待。