存在的问题
作为一个前端的切图仔,编写样式也是我们日常的基操,但是难免会出现需要动态改变元素样式的情况,在小程序中动态控制样式可能会是下面的代码,以一个基础的按钮组件为示例:
<view class="l-btn
{{ 'l-btn-' + size }}
{{ 'l-btn-' + type }}
{{ 'l-btn-' + shape }}
{{plain?'l-btn-plain':''}}
{{ disabled ? 'l-btn-disabled' : ''}}
l-class" />
复制代码
为了通过 size
、type
、shape
等props去控制一个按钮的样式,导致代码中使用了很多的双花括号去拼接类名,类名较多的情况会使得一个元素的样式混乱不堪,找不到所要修改的类名。
如何解决
小程序作为一个较新的技术,它所出现的一些问题在其他的技术里面都会找到类似的问题及解决方案,我们不妨看下react中是如何解决这个问题的。
开发react是经常会用到一个npm包classnames–一个工具函数,可以帮助我们却拼接所有的样式,示例代码如下:
var classNames = require('classnames');
class Button extends React.Component {
// ...
render () {
var btnClass = classNames({
btn: true,
'btn-pressed': this.state.isPressed,
'btn-over': !this.state.isPressed && this.state.isHovered
});
return <button className={btnClass}>{this.props.label}</button>;
}
}
复制代码
我们可以借鉴相同的思路在小程序中实现一个类似的工具函数,去帮助我们解决这个问题。
解决方案
因为这个工具函数不涉及到界面上的一些交互,只是对输入的变量做一个处理,所以可以考虑使用wxs
去实现,也可以提升页面的性能,首先我们要明确这个函数所要接受的参数类型,分别为数字、字符串、数组以及对象。
首先我们创建一个名为classname
的数组用于存储类名。
- number、string: 直接添加push到
classname
中。 - array:将
classname
与array进行拼接。 - object: 遍历对对象,将
value
为true的key
添加到到classname
中。
明确了以上的规则,我们可以写出一下的wxs取出对应的类名。
function classNames() {
var classes = []
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i]
if (!arg) continue
var argType = typeof arg
if (argType === "string" || argType === "number") {
classes.push(arg)
} else if (Array.isArray(arg)) {
if (arg.length) {
classes = classes.concat(arg)
}
} else if (argType === "object") {
Object.keys(arg).forEach(function (key) {
if (arg[key]) {
classes.push(key)
}
})
}
}
return classes.join(" ")
}
module.exports = classNames
复制代码
本以为大功告成了?还是我太年轻了,控制台直接抛出了两条错误,Array
和Object
这两个js内置对象在wxs中根本不能使用。
既然不存在,那我们就造一个,当然内置的对象我们是不可能造出来的,但是我们要使用的方法还是可以去模拟实现的。
isArray
我们可以通过判断当前参数的constructor是否等于Array
来判断是否是一个数组。
function isArray(array) {
return array && array.constructor === "Array"
}
复制代码
keys
方法实现就比较麻烦一点,我们可以先把对象转为字符串,然后去正则匹配替换,最终获取到一个key的数组。
function keys(obj) {
return JSON.stringify(obj)
.replace(REGEXP, "")
.split(",")
.map(function (item) {
return item.split(":")[0]
})
}
复制代码
完成这两个替代方法之后我们对之前代码中的方法进行替换即可,最终代码如下:
function isArray(array) {
return array && array.constructor === "Array"
}
var REGEXP = getRegExp('{|}|"', "g")
function keys(obj) {
return JSON.stringify(obj)
.replace(REGEXP, "")
.split(",")
.map(function (item) {
return item.split(":")[0]
})
}
function classNames() {
var classes = []
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i]
if (!arg) continue
var argType = typeof arg
Object.keys({})
if (argType === "string" || argType === "number") {
classes.push(arg)
} else if (isArray(arg)) {
if (arg.length) {
classes = classes.concat(arg)
}
} else if (argType === "object") {
keys(arg).forEach(function (key) {
if (arg[key]) {
classes.push(key)
}
})
}
}
return classes.join(" ")
}
module.exports = classNames
复制代码
有了这个工具函数之后,我们就可以愉快的在wxml里面写样式了。
<wxs module="classname" src="./index.wxs" />
<view class="container {{classname(1, ['c'],'less', {a: true, b: 1})}}">
111
</view>
复制代码
总结
虽然我们用wxs解决了上述问题,但是在对象里面下列的写法是不支持的。
{
'a-b': true
}
{
[`l-${a}`]: true
}
复制代码
如果有上述需求的同学可以在js中尝试实现类似的方法,可以通过组件的observer函数动态生成样式绑定也是可以的。