Existing problems

As a front-end cut figure son, writing style is also our daily base operation, but inevitably there will be a need to dynamically change the element style, in the small program dynamic control style may be the following code, to a basic button component as an example:

    <view class="l-btn {{ 'l-btn-' + size }} {{ 'l-btn-' + type }} {{ 'l-btn-' + shape }} {{plain? 'l-btn-plain':''}} {{ disabled ? 'l-btn-disabled' : ''}} l-class" />
Copy the code

In order to control the style of a button using props such as size, Type, shape, etc., the code used a lot of double curly braces to splicing the class name. If the class name was too many, the style of an element would be confused and the class name to be modified could not be found.

How to solve

React applet is a relatively new technology. Some of its problems can be solved by other technologies. Let’s take a look at how react solves this problem.

React uses the NPM package classnames– a utility function that concatenates all styles.

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>; }}Copy the code

We can use the same idea to implement a similar tool function in a small program to help us solve this problem.

The solution

Because this tool function does not involve some interaction on the interface, but only does a process to the input variables, it can be considered to use WXS to implement it, which can also improve the performance of the page. First, we need to clarify the parameter types accepted by this function, including numbers, strings, arrays and objects.

First we create an array called classname to store the classname.

  • Number, string: directly add push toclassnameIn the.
  • Array:classnameSplice with array.
  • Object: traverses pairs of objectsvalueIs truekeyAdded to theclassnameIn the.

Given the above rules, we can write the following WXS and extract the corresponding class name.

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
Copy the code

You thought you were done? The console threw two errors: Array and Object, two built-in JS objects, cannot be used in WXS at all.

Since it doesn’t exist, let’s build one, of course we can’t build built-in objects, but we’re going to use a method that we can emulate.

We can check whether the current argument’s constructor is an Array by checking whether its constructor equals Array.

function isArray(array) {
    return array && array.constructor === "Array"
}
Copy the code

The keys method is a bit more difficult to implement. We can first convert the object to a string, then go to the re match replacement, and finally get an array of keys.

function keys(obj) {
    return JSON.stringify(obj)
        .replace(REGEXP, "")
        .split(",")
        .map(function (item) {
            return item.split(":") [0]})}Copy the code

After completing these two alternative methods, we can replace the methods in the previous code, and the final code is as follows:

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
Copy the code

With this utility function, we can have fun writing styles in WXML.

<wxs module="classname" src="./index.wxs" />
<view class="container {{classname(1, ['c'],'less', {a: true, b: 1})}}">
  111
</view>
Copy the code

conclusion

Although we solved the above problem with WXS, the following notation is not supported in objects.

{
    'a-b': true} {[`l-${a}`] :true
}
Copy the code

If you can try to implement a similar approach in JS, it is also possible to dynamically generate style bindings through the component’s Observer function.