Mastering Sass
上QQ阅读APP看书,第一时间看更新

Data types in Sass

If we look at the base-font-sizes-calc() function, we know it takes in a font family and then calculates the correct value to return. However, what if someone passed in a number? It's reasonable to assume something called base-font-sizes-calc() deals with numbers, right? So we need to make sure we are only getting a string, and if not we need to tell the user this function needs a string which is the font-family and not a number.

Sass has 7 main data types (8 if you count arg list):

  • string [unquoted, "quoted string", 'single quoted string']
  • number [1, 0.5, 1rem, 5px, 2em]
  • bool [true, false]
  • null [null]
  • list [(1, 2, 3, 4), ("one" "two" "three")]
  • map [(h1: 2rem, h2: 1.5rem, h3: 1.17rem)]
  • color [#bada55, red, hsl(10, 30%, 50%), rgb(255, 160, 80)]

The way you check for a specific type is with the Sass function type-of(). So now we can update our function to perform this check with a simple ifelse statement:

// mastering-sass/ch02/scss/helpers/_functions.scss 
@function base-font-size-calc($current-font-family: $base-font-family) { 
 @if type-of($current-font-family) != string { 
 @error "The base-font-size-calc() function takes a string as it's parameter, #{type-of($current-font-family)} type was given."; 
 } @else { 
        @return if($current-font-family == $base-font-family-serif, $base-font-size * 1.15, $base-font-size); 
 } 
} 

We can quickly make sure our error is showing by creating a simple CSS rule and entering the wrong data type into our function:

// mastering-sass/ch02/scss/style.scss 
.test-error { 
    content: base-font-size-calc(1); 
} 

This will output the following message to the command line:

The base-font-size-calc() function takes a string as it's parameter, number type was given.

You can enter other data types and it will simply replace number in our message with the data type. This is due to the interpolated call to #{type-of($current-font-family)}, which allows for a more dynamic and helpful error message.

Let's look at different data types in detail.

@error

In the preceding example we used @error to product our message. Due to the fact functions a required to return a value, @error is the only one of the three error directives @error, @warn, and @debug which actually returns. However, this is the only reason I chose @error here.

@error is the highest priority level error message we have at our disposal. It stops everything once it's called and to the command line. This can't be turned off (@warn can be turned off at runtime with the --quiet option in Sass) and is ideal for any situation where an error cannot be overlooked or worked around. Adding a number instead of a font family is such an occurrence.

We'll mostly be dealing with @error for now, however let's take a look at the differences between all three error directives right now. As you've seen, @error stops Sass from compiling and displays the error in the command line. @warn and @debug do not stop Sass compiling, but they do display messages. I think of them as red, orange, and yellow level errors. @error is red, @warn is orange, and @debug is yellow.

@debug

@debug is the least severe. In fact, I wouldn't even call it an error directive. As the name implies it's mainly for debugging your functions and mixin during the process of development. It's should not be used in the finished production-ready Sass. It should be used much how we used the content property in our .test-error rule discussed previously. So let's write a rule with @debug to see the difference. We'll call our CSS the rule .test-debug and output base- font-size-calc($base-headings-font-family):

// mastering-sass/ch02/scss/style.scss 
.test-debug { 
    @debug base-font-size-calc($base-headings-font-family); 
} 

In the command line you should see something like this:

>>> Change detected to: scss/style.scss
/style.scss:40 DEBUG: 1.15rem
 write css/style.css
 write css/style.css.map

So it gives us the filename /style.scss, the line on which @debug was called, :40, and the value: DEBUG: 1.15. Sass will then continue to compile our css files as normal. However, what happens if we put a number in base-font-size-calc() like the previous example? Let's give it a go:

// mastering-sass/ch02/scss/style.scss 
.test-debug { 
    @debug base-font-size-calc(1); 
} 

Now in the command line you'll see the error with no debug info:

>>> Change detected to: scss/style.scss
 error scss/helpers/_functions.scss (Line 4: base-font-size-calc() takes a string as it's parameter. number given.)

This is because @error will always win. If you call something that results in an error being output, the error will simply prevent everything else from happening and therefore will be the only thing output to the command line.

@warn

@warn is useful if you want to show a message to a user in the command line without stopping Sass from compiling. However, @warn is useful in final production-ready Sass. @warn is useful when a function or mixin is being deprecated and therefore will still work for a time, but perhaps the user should be notified to use another function or mixin instead.

So let's assume one day another library comes out that also has a mixin called base-headings() and we realize it's madness not to be name spacing our mixins and functions. So we are going to deprecate our base-headings() mixin for a mixin called mastering-sass-base-headings(). To do this we would use the @warn directive to alert those using our mixins that in a future release they will have to start using the new mixin, but in the meantime their code will still work:

// mastering-sass/scss/helpers/_mixins.scss 
@mixin base-headings { 
 @warn "Please use @include mastering-sass-base-headings() instead of @include base-headings(). @mixin base-headings() will be deprecated in version 2."; 
    $i: 1; 
  @each $heading, $family in $base-headings { 
    #{$heading} { 
      @include base-headings-font-family-sizing($i, $family); 
    } 
    $i: $i + 1; 
  } 
} 

Then we would obviously need to copy our base-headings mixin and call it mastering-sass-base-headings so it can be used as well in the transition period, and of course when we remove the availability of the base-headings mixin.

Now that we've looked at each of the error directives let's get back to adding our error checking to our functions and mixins. Our next function is base-heading-sizes-calc. This function actually requires a few checks to be robust enough to be release into the wild web. We need to make sure the first parameter is a number and the second is a string. We then need to provide a user friendly error if a number greater than 6 is entered for our headings. First let's do our type checking and test that works:

// mastering-sass/ch02/scss/helpers/_functions.scss 
@function base-heading-sizes-calc($heading: 2, $font-family: $base-headings-font-family) { 
 @if type-of($heading) != number { 
 @error "The first parameter of base-heading-sizes-calc() must be a number, #{type-of($heading)} was given."; 
 } @else if type-of($font-family) != string { 
 @error "The second parameter base-heading-sizes-calc() must be a string, #{type-of($font-family)} was given."; 
 } @else { 
        $h4-font-size: base-font-size-calc($font-family); 
        $h1-font-size: $h4-font-size * 2; 
        $h2-font-size: $h1-font-size / 1.3333; 
        $h3-font-size: $h2-font-size / 1.2821; 
        $h5-font-size: $h4-font-size / 1.2048; 
        $h6-font-size: $h5-font-size / 1.2388; 
        $headings: $h1-font-size, $h2-font-size, $h3-font-size, $h4-font-size, $h5-font-size, $h6-font-size; 
 
        @return nth($headings, $heading); 
    } 
} 

Now let's do a quick test to ensure it's working correctly:

// mastering-sass/ch02/scss/style.scss 
.test-data-types { 
    content: base-heading-sizes-calc("", 2); 
} 

This will give the following error in the command line:

error scss/helpers/_functions.scss (Line 11: The first parameter of base-heading-sizes-calc() must be a number, string was given.)

Again, this is because we can only get one error and then everything after that is prevented from running. So change the first parameter to a number and you should get the following error:

error scss/helpers/_functions.scss (Line 13: The second parameter of base-heading-sizes-calc() must be a string, number was given.)

Everything works, so we can move onto checking our number is from between 1 and 6 and not -1 or 0 or 7 or anything else which would could cause unexpected issues down the line. So once both variables have passed the data type, checks we'll end up inside the @else block of our conditional. Therefore, we need to write our check inside our @else statement, in a way that displays an error if the number given is outside of 1 and 6, otherwise our function runs the calculations as intended:

// mastering-sass/ch02/scss/helpers/_functions.scss 
@function base-heading-sizes-calc($heading: 2, $font-family: $base-headings-font-family) { 
    @if type-of($heading) != number { 
        @error "The first parameter of base-heading-sizes-calc() must be a number, #{type-of($heading)} was given."; 
    } @else if type-of($font-family) != string { 
        @error "The second parameter of base-heading-sizes-calc() must be a string, #{type-of($font-family)} was given."; 
    } @else { 
 @if $heading < 1 or $heading > 6 { 
 @error "Second parameter of base-heading-sizes-calc() must be between 1 and 6, #{$heading} was given."; 
 } 
        $h4-font-size: base-font-size-calc($font-family); 
        $h1-font-size: $h4-font-size * 2; 
        $h2-font-size: $h1-font-size / 1.3333; 
        $h3-font-size: $h2-font-size / 1.2821; 
        $h5-font-size: $h4-font-size / 1.2048; 
        $h6-font-size: $h5-font-size / 1.2388; 
        $headings: $h1-font-size, $h2-font-size, $h3-font-size, $h4-font-size, $h5-font-size, $h6-font-size; 
 
        @return nth($headings, $heading); 
    } 
} 

Now we test our error works correctly (you know what I mean):

.test-data-types { 
    content: base-heading-sizes-calc(0, $base-headings-font-family); 
} 

This should output the error message:

 error sass/helpers/_functions.sass (Line 16: Second parameter of baseheading-sizes-calc() must be between 1 and 6, 0 was given.)

You'll notice we didn't wrap the rest of our code in an @else block. This is because if the number is outside of 1 and 6 the error will trigger and stop the remainder of our function from running anyways, and if the number is between 1 and 6 the if statement will simply be ignored and our calculations will run as usual. So for brevity we can leave the @else out in these situations. The same would be true if we were returning a value. Once a function returns nothing else inside that function will run, similar to an error.

From here I think you how to check for errors. Basically, ask yourself the following:

  • What happens if a user puts in the wrong data type?
  • What happens if they enter a number which is outside of the intended range?
  • Is this something I can account for without breaking the intending functionality?

Bonus round

I'm going to leave it up to you to go through the remaining functions and mixins and add useful error messages for any errors that may arise. However, for bonus points you could also add some smarts to the base-heading-sizes-calc function. For example, I think we could safely assume if the value was less than 0 we could automatically set it to 1, and if it was greater than 6 we could set it to 6 and a warning should be displayed to the user instead of an error.