Understand the label function of template string in ES6

Posted by j007ha on Sat, 27 Nov 2021 01:01:50 +0100

Template string is probably a new feature of ES6 that we are familiar with. It allows us to insert variables in the string, wrap lines, etc. it is really very convenient to use. However, ES6 also adds a tag function that is mainly used with template strings.

1. What label function?

As the name suggests, the tag function is also a function. This is a very simple label function declaration:

function tagFoo() {
    console.log('This is a label function');
}

Take a closer look and see what is the difference between tagFoo and our common functions? Can't you see the difference? It is normal to draw this conclusion, because the label function is actually a function, so its declaration is exactly the same as the function declaration.

So, how do we know that tagFoo is a tag function, or what's special about the tag function compared with ordinary functions? The answer is to see how tagFoo is called.

tagFoo();//This is a label function
tagFoo`A template string`;//This is a label function

In addition to being called as a normal function through (), the tag function can also be called using the template string `. In other words, when a function is called in the form of template string, this function can be called label function, so we might as well understand label function as a new function call method.

Then we may think, what is the difference between the two invocation methods? What are their return values?

No matter which call method, this function is called, and the final return value is the result of executing this function. Let's add a return value to tagFoo to see:

function tagFoo() {
    return 'I am the return value';
}

let res1 = tagFoo(); 
let res2 = tagFoo`A template string`; 
console.log({ res1, res2 });//{res1: 'I am the return value', res2: 'I am the return value'}

2. Parameters of label function

excuse me? Do these two calling methods only look different? Of course not. I think we have noticed that the call mode of ordinary functions allows us to pass in parameters by ourselves, while the call mode of tag functions does not seem to provide us with the opportunity to pass in parameters. Let's take a look at the parameters of the label function when there are parameters:

function tagFoo(...args) {
    console.log(...args);
}

tagFoo`An ordinary template string`; // ['an ordinary template string']
tagFoo`A template string with interpolation: ${'var'}`; //['a template string with interpolation:', ''] var
tagFoo`A template string with interpolation: ${'var1'}-${'var2'}`; //['a template string with interpolation:' - ',' '] var1 var2

As can be seen from the above, when calling the tag function, the received parameter is always an array, and the elements in the array are the string part of the template string; The remaining parameters starting from the second parameter receive the interpolation variables of the template string, and the number of these variables is arbitrary. It may be more intuitive to declare in another way:

function tagFoo(templateStrings, ...insertVars) {
    console.log({ templateStrings, insertVars });
}
tagFoo`An ordinary template string`; //{templateStrings: ['an ordinary template string'], insertVars: []}
tagFoo`A template string with interpolation: ${'var'}`; //{templateStrings: ['a template string with interpolation:' ''], insertvars: ['var']}
tagFoo`A template string with interpolation: ${'var1'},${'var2'}`; //{templateStrings: ['a template string with interpolation:' - ',' '], insertvars: [' VAR1 ',' var2 ']}

It can also be represented by a diagram:

Perhaps it can be described as that there should be a variable inserted in insertVars between every two elements in templateStrings. There is a corresponding relationship between the order of elements in the two arrays.

3. What is the use of tag functions?

The tag function allows us to perform our own logical behavior according to the template string, making some operations very simple.

For a simple example, convert the price n in the template string to $n:

function $(templateStrings, ...insertVars) {
    return templateStrings.reduce((res, temp, i) => {
        return res + temp + (i >= insertVars.length ? '' : '$' + insertVars[i]);
    }, '');
}

console.log($`1 Original price of dress No ${42},What's the discount price ${2}`); //The original price of dress No. 1 is $42, and the discount price is $2
console.log($`2 Original price of shoes ${58},What's the discount price ${88}`); //The original price of shoe No. 2 is $58 and the discounted price is $88

Of course, this effect can be achieved without using the label function, which is also very simple:

function $(n) {
    return '$' + n;
}
console.log(`1 Original price of dress No ${$(42)},What's the discount price ${$(2)}`); //The original price of dress No. 1 is $42, and the discount price is $2
console.log(`2 Original price of shoes ${$(58)},What's the discount price ${$(88)}`); //The original price of shoe No. 2 is $58 and the discounted price is $88

Which way to use depends on our own preferences.

However, when dealing with some special situations, the tag function may give us a big surprise.

such as styled-components What has been done:

const Button = styled.a`
  /* This renders the buttons above... Edit me! */
  display: inline-block;
  border-radius: 3px;
  padding: 0.5rem 0;
  margin: 0.5rem 1rem;
  width: 11rem;
  background: transparent;
  color: white;
  border: 2px solid white;

  /* The GitHub button is a primary button
   * edit this to target it specifically! */
  ${props => props.primary && css`
    background: white;
    color: black;
  `}
`

The resulting Button is a React component. adopt styled-components , we can write css style in JS!

We can also imitate styled-components , implement a simple styled.a

const styled = {
    a(stringProps, ...getProps) {
        const varProps = getProps.map((f) => f({}) || '');
        const style = stringProps
            .reduce((prop, stringProp, i) => {
                return (
                    prop +
                    stringProp +
                    (i >= varProps.length ? '' : varProps[i])
                );
            }, '')
            //Delete comments and line breaks
            .replace(/(\n|(\/\*[\s\S]*?\*\/))/g, '')
            //Remove Spaces 
            .replace(
                /\s*?(?<propName>\w+):(?<propValue>[\s\S]*?);\s*/g,
                (...args) => {
                    const { propName, propValue } = args.pop();
                    return `${propName}:${propValue};`;
                }
            );

        //For display purposes, a string is returned
        return `<a style="${style}"></a>`;
    },
};
const Button = styled.a`
    /* This renders the buttons above... Edit me! */
    display: inline-block;
    border-radius: 3px;
    padding: 0.5rem 0;
    margin: 0.5rem 1rem;
    width: 11rem;
    background: transparent;
    color: white;
    border: 2px solid white;

    /* The GitHub button is a primary button
   * edit this to target it specifically! */
    ${(props) => !props.primary && 'background: white;'}
`;

console.log(Button);
//<a style="display: inline-block;border-radius: 3px;padding: 0.5rem 0;margin: 0.5rem 1rem;width: 11rem;background: transparent;color: white;border: 2px solid white;background: white;"></a>

It has to be said that the tag function is really attractive when dealing with strings.

Of course, whether you will use the tag function or not depends on your preferences. Radishes and cabbage have their own love.

Topics: Javascript Front-end ECMAScript