(Chapter 5) Copying the "Analytical Template Event" of the "Vue Ecology" Series

(Chapter 5) Copying the "Analytical Template Event" of the "Vue Ecology" Series

This task

  1. Cancel'eval'and replace it with'new Function'.
  2. Support users to bind events with'@'and'v-on'.
  3. Support initialization of'methods'data.
  4. When using a function, you can pass participation without passing parameters, and you can use'$event'.
  5. Implement'c-center'and'c-show' instructions.
  6. Implement the'cc_cb'function and use if-else in the template.

eval and Function

In the project, eval function was always used before, but a special function Function was suddenly found some time ago. Let me show you its magic.

1. Strings can be executed

 let fn1 = new Function('var a = 1;return a');
 console.log(fn1()); // 1

2. Transferable parameters
The name and age described below are the two parameters of the incoming function.

let fn2 = new Function('name','age', ' return name+age');
console.log(fn2('lulu',24)); // lulu24

The second method of data transmission

let fn3 = new Function('name, age', ' return name+age');
console.log(fn3('lulu',24)); // lulu24

In summary, I can deduce that his principle is to regard the last parameter as an executor, and then if there is a parameter before, it is regarded as a parameter of the new generating function.

3. Global scope
When he executes, the scope inside the function is global. Even inside the function, the value inside the function can not be obtained at the time of execution. So the value we want to use needs to be passed in manually.

// Wrong report, you can't find u
function cc(){
    let u = 777;
    let fn = new Function('var a = 5;console.log(u); return a');
    console.log(fn());
  }
 cc()
// Successful implementation
function cc(){
    u = 777; // Hang directly on Windows
    let fn = new Function('var a = 5;console.log(u); return a'); // 777
    console.log(fn()); // 5
  }
 cc()

I also tried it. The var a inside won't pollute the whole situation. You can use it safely.

If I introduce it clearly, I can use it to replace the eval I wrote before.
Expression: An expression, such as'obj[name].age'

getVal(vm, expression) {
    let result, __whoToVar = '';
    for (let i in vm.$data) {
      __whoToVar += `let ${i} = vm['${i}'];`;
    }
      __whoToVar = `${__whoToVar} return ${expression}`;
    result = new Function('vm', __whoToVar)(vm);
    return result;
  },

In the future, it will be changed into a common'pool'for getting variables, which should be done in the next chapter.

II.'@'and'v-on'

Instructions are bound to elements, of course. We have a compileElement method to handle element nodes, so we can use it to separate out an instruction processing module.
For example, let's do the v-show instruction this time.
Events are all original events.

compileElement(node) {
    let attributes = node.attributes;
    [...attributes].map(attr => {
      let name = attr.name,
        value = attr.value,
        obj = this.isDirective(name);
      if (obj.type === 'instructions') {
        CompileUtil.dir[obj.attrName] &&
          CompileUtil.dir[obj.attrName](
            this.vm,
            node,
            CompileUtil.getVal(this.vm, value),
            value
          );
      } else if (obj.type === 'Event') {
        // Currently, only native events are handled.
        if(CompileUtil.eventHandler.list.includes(obj.attrName)){
         CompileUtil.eventHandler.handler(obj.attrName,this.vm, node, value);
        }else{
          // EvetHandler [obj. attrName] This event is not a native mount event and cannot be handled with handler
        }
      }
    });
  }

There is an isDirective event above, which is also a key point.
We are now divided into four forms.
Determine the type, segment the following instruction name and parameters, and return to the processing program.

  isDirective(attrName) {
    if (attrName.startsWith('c-')) {
      return { type: 'instructions', attrName: attrName.split('c-')[1] };
    } else if (attrName.startsWith(':')) {
      return { type: 'variable', attrName: attrName.split(':')[1] };
    } else if (attrName.startsWith('v-on:')) {
      return { type: 'Event', attrName: attrName.split('v-on:')[1] };
    } else if (attrName.startsWith('@')) {
      return { type: 'Event', attrName: attrName.split('@')[1] };
    }
    return {};
  }

cc_vue/src/CompileUtil.js
A special instruction processing module, temporarily named dir, is extracted.
Take c-html and c-show as examples
c-html, as its name implies, is a piece of HTML code passed by the user, and then I injected it into the dom structure.

dir: {
    html(vm, node, value, expr) {
    // Only such an operation can be done. There is nothing profound about it.
      node.innerHTML = value;
      // Don't forget to subscribe to changes with watcher to achieve bi-directional binding.
      new Watcher(vm, expr, (old, newVale) => {
        node.innerHTML = newVale;
      });
    }
  },

The remaining'c-center'and'c-show' after warm-up are very interesting.

  1. Control the'display:none'property of'dom', display when'true', and disappear when'false'.
  2. This attribute does not affect the inter-line style of the DOM itself, such as the user-defined'none', which still does not display the'dom'element when it is'true'.
  3. This attribute can't change any of the properties of dom itself, but it has the highest priority. What happens in the brain is'! important'.

In summary, two schemes are obtained:
The first is to take all external factors into account and make an overall analysis each time to draw concrete conclusions as to whether'block'or'none' or'flex'and'grid', etc.
Second: I want to find a new way to insert'css'code dynamically. This idea is interesting. When the framework is executed, first insert a piece of CSS code, then use this CSS to do many interesting things, which will be expanded in the future.
Independent module for inserting'css'code.
new alone
cc_vue/src/index.js

import CCStyle from './CCStyle.js';
class C {
  constructor(options) {
     for (let key in options) {
      this['$' + key] = options[key];
    }
    new CCStyle();
    // ...

cc_vue/src/CCStyle.js

class CCStyle {
  constructor() {
     // I want to insert it to the top. There is no such statement as inserting the first position in js. I can only get the first element and insert it in front of him.
    let first = document.body.firstChild,
        style = document.createElement('style'); // Make a style tag, of course.
    // Here we first set an absolute hidden property of c-show.
    style.innerText='.cc_vue-hidden{display:noneimportant}';
    // Put it in and it will take effect. To control v-show later, you just need to add and remove the class name for the element.
    document.body.insertBefore(style, first);
  }
}

export default CCStyle;

The above code obviously does not conform to the design pattern. Let's optimize its'extensibility'.

class CCStyle {
  constructor() {
    let first = document.body.firstChild,
      style = document.createElement('style'),
      typeList = this.typeList();
    // No matter what the specific attributes are, we just need to circulate in it and splice it up. Here we will compress him.
    for (let key in typeList) {
       style.innerText += `.${key}{${typeList[key]}}\n`;
    }
    document.body.insertBefore(style, first);
  }
// We can expand many attributes in different categories.
  typeList() {
     return {
      // 1: Hidden control elements
      'cc_vue-hidden': 'display:none!important'

      // 2: Control elements are centered from top to bottom
      'cc_vue-center':'display: flex;justify-content: center;align-items: center;'
    };
  }
}

export default CCStyle;

v-center instruction
cc_vue/src/CompileUtil.js

center(vm, node, value, expr) {
      value
        ? node.classList.remove('cc_vue-center')
        : node.classList.add('cc_vue-center');
      new Watcher(vm, expr, (old, newVale) => {
        newVale
          ? node.classList.remove('cc_vue-center')
          : node.classList.add('cc_vue-center');
      });
    }

The principle of c-show is the same as above.

show(vm, node, value, expr) {
      value
        ? node.classList.remove('cc_vue-hidden')
        : node.classList.add('cc_vue-hidden');
      new Watcher(vm, expr, (old, newVale) => {
        newVale
          ? node.classList.remove('cc_vue-hidden')
          : node.classList.add('cc_vue-hidden');
      });
    },

III. Binding of methods to events

methods is later than data definitions, and when users have duplicate definitions, give a friendly hint.
cc_vue/src/index.js

class C {
  constructor(options) {
    // ...
    // Processing $methods after proxyVm $data
    this.proxyVm(this.$methods, this, true);

The binding function needs to be changed slightly. As long as no target is bound to the vm instance, whether noRepeat detects duplicate data or reports no errors.

 proxyVm(data = {}, target = this, noRepeat = false) {
    for (let key in data) {
      if (noRepeat && target[key]) { // Prevent duplication of variable names and other attributes in data
        throw Error(`Variable name ${key}repeat`);
      }
      Reflect.defineProperty(target, key, {
        enumerable: true, // Describes whether attributes appear in traversals for in or Object.keys().
        configurable: true, // Describes whether attributes are configured and whether they can be deleted
        get() {
          return Reflect.get(data, key);
        },
        set(newVal) {
          if (newVal !== data[key]) {
            Reflect.set(data, key, newVal);
          }
        }
      });
    }
  }

Processing the data of methods well, we need to deal with the binding of events.
The logic of allocation has been shown before.

// If there is an event in the event list, bind the event.
if(CompileUtil.eventHandler.list.includes(obj.attrName)){
   CompileUtil.eventHandler.handler(obj.attrName,this.vm, node, value);
}

cc_vue/src/CompileUtil.js
Event-specific modules

  eventHandler: {
     // This option is used to maintain handled native events, but the following examples are not comprehensive.
    list: [
      'click',
      'mousemove',
      'dblClick',
      'mousedown',
      'mouseup',
      'blur',
      'focus'
    ],
    // Determine the actions to be performed when events are present
    handler(eventName, vm, node, type) {
      // ...
     }
    }
  }

The form of problem handler wants to solve

  1. Add - > Call directly.
  2. Add () - > parentheses.
  3. Add () - > Inclusion blank.
  4. Add (n, m, 9) - > Inclusive Spaces, Constants, Parameters of Variables.
  5. Add (n, $event) - > Users want to get event object $event.

Let's deal with these situations step by step.

 handler(eventName, vm, node, type) {
    // The first step is to match whether one contains'()';
      if (/\(.*\)/.test(type)) {
        // Step 2: Take out the contents of'()'
        let str = /\((.*)\)/.exec(type)[1];
        // Remove blank space
        str = str.replace(/\s/g, '');
        // Split by "(" to get the name of the event
        type = type.split('(')[0];
        // '()'contains the content to carry out this step;
        if (str) {
        // Step 3: Parametric'group'
          let arg = str.split(',');
          // Part 4: Binding events and parsing parameters
          node.addEventListener(
            eventName,
            e => {
            // Loop this parameter group
              for (let i = 0; i < arg.length; i++) {
                // This does the mapping of $event
                arg[i] === '$event' && (arg[i] = e);
              }
              vm[type].apply(vm, arg);
            },
            false
          );
          return;
        }
      }
      // Step 2: Just register directly without parentheses.
      node.addEventListener(
        eventName,
        () => {
          vm[type].call(vm); // this must point to vm, after all, users will use attributes like $data and so on.
        },
        false
      );
    }

There's no need to deal with variables on $data, because it's not necessary. When we write c-for later, we'll focus on rewriting the logic here.

IV. Use if in templates

When we use vue to develop, we only allow expressions to be used in the template. This time I play this project, allowing users to write in any form. Of course, there are some drawbacks such as performance, but I am willing to try anything for fun. I have abandoned the way of writing return value and adopted the callback mode.
The key cc_cb(value) value is the value to be passed out.
The usage is as follows:

<div>
{{ 
  if(n > 3){
    cc_cb(n) 
  }else{
    cc_cb('n Less than or equal to 3')
  };
}}
</div>

In fact, this function is not complicated, but it is very annoying to write, and the wife is too contrary to the design pattern.
Just change the getVal function

  getVal(vm, expression) {
    let result,
      __whoToVar = '';
    for (let i in vm.$data) {
      __whoToVar += `let ${i} = vm['${i}'];`;
    }
    // When cc_cb is called is detected
    if (/cc_cb/.test(expression)) {
         // It's all about returning the returned value.
      __whoToVar = `let _res;function cc_cb(v){ _res = v;}${__whoToVar}${expression};return _res`;
    } else {
      __whoToVar = `${__whoToVar} return ${expression}`;
    }
    result = new Function('vm', __whoToVar)(vm);
    return result;
  },

Hey, hey, you just need a little change to do this amazing thing.

end

This framework has just done a little bit and many performance problems have arisen. Next, I will make a deep optimization for the value problem. It's exciting to think about it.

Next episode:

  1. Optimize the value.
  2. Add hook lifecycle hook.

github:Link description
Personal Technology Blog: Link description
More articles, a list of articles written by the ui library: Link description

Tags: Front-end Vue Attribute Windows less

Posted on Mon, 07 Oct 2019 22:03:57 -0700 by stomlin