LESS: How can I pass a mixin as an argument to ano

2019-02-19 10:56发布

问题:

I have some basic mixins that apply some rules using media queries

.on-small(@rules) {
  @media (@minWidthSmall) { @rules(); }
}

.on-medium(@rules) {
  @media (@minWidthMedium) { @rules(); }
}

// and .on-large, .on-x-large and so on

And I'm trying to build a very simple flex-based grid system, I'm trying to pass the mentioned mixins as parameters so I can have a generic .make-column mixin. as follows:

.make-col(@break-point-mixin, @span, @size) {
  flex: 1;
  box-sizing: border-box;


  /***********************************************************
  Is the following line possible in LESS somehow?
  ***********************************************************/
  @break-point-mixin({
    width: percentage(@span/@size);
    min-width: percentage(@span/@size);
  });
}

.grid-col-on-small(@span: 1, @size: 1) {
  .make-col(@break-point-mixin: .on-small, @span, @size);
}

.grid-col-on-medium(@span: 1, @size: 1) {
  .make-col(@break-point-mixin: .on-medium, @span, @size);
}

But unfortunately passing @break-point-mixin as a parameter and calling it from inside .make-col crashes with:

Unrecognised input. Possibly missing opening '('

回答1:

In this particular case (unlike a general case with an arbitrary mixin name) I'd say you're missing the fact that in .on-small/.on-medium these small and medium things are also nothing but parameters and thus should not be a part of the mixin names. With this in mind your example becomes:

.on(small, @rules) {
  @media (@minWidthSmall) {@rules();}
}

.on(medium, @rules) {
  @media (@minWidthMedium) {@rules();}
}

.make-col(@device, @span, @size) {
  flex: 1;
  box-sizing: border-box;
  .on(@device, {
    width: percentage(@span/@size);
    min-width: percentage(@span/@size);
  });
}

// usage:

.make-col(small, @span, @size);

Same for your .grid-col-on-* mixins, they are just a single:

.grid-col-on(@device, @span: 1, @size: 1) {
  .make-col(@device, @span, @size);
}

and so on.

If you really want a flexible/generic grid - never hardcode device/breakpoint names into mixin or variable names (for more rationale and examples see https://github.com/less/less.js/issues/2702).



回答2:

No, you can't send a mixin name as a parameter and use it in that way.

Instead you can do something like the below where the media query mixin is called directly from within the wrapper mixin instead of the .make-col mixin. As the wrapper mixin is aware of the variables that the media query mixin needs this wouldn't result in any problems.

.grid-col-on-small(@span: 1, @size: 1) {
  .make-col(@span, @size);
  .on-small({
    width: percentage(@span / @size);
    min-width: percentage(@span / @size);
  });
}

.grid-col-on-medium(@span: 1, @size: 1) {
  .make-col(@span, @size);
  .on-medium({
    width: percentage(@span / @size);
    min-width: percentage(@span / @size);
  });  
}

If you are concerned about rewriting the rules in the above mixins then you can set them to a ruleset like below and use it.

@colRules: {
             width: percentage(@span / @size);
             min-width: percentage(@span / @size);
           };

.grid-col-on-small(@span: 1, @size: 1) {
  .make-col(@span, @size);
  .on-small(@colRules);
}

.grid-col-on-medium(@span: 1, @size: 1) {
  .make-col(@span, @size);
  .on-medium(@colRules);  
}

Or, you can send the mixin name as a parameter and use guards like below. As we are dealing with break-points here and there shouldn't be lots of them, this approach should help and would probably get my vote.

@colRules: {
             width: percentage(@span / @size);
             min-width: percentage(@span / @size);
           };

.make-col(@breakpoint, @span, @size) {
  flex: 1;
  box-sizing: border-box;
  & when (@breakpoint = s) {
    .on-small(@colRules); /* or you could replace this with that mixin's content also */
  }
  & when (@breakpoint = m) {
    .on-medium(@colRules);
  }
  /* and so on for the rest */
}

.grid-col-on-small(@span: 1, @size: 1) {
  .make-col(s, @span, @size);
}

.grid-col-on-medium(@span: 1, @size: 1) {
  .make-col(m, @span, @size);
}