MVC and Vue

Time:2020-11-24

MVC and Vue

This article was written on July 27, 2020

The first question is: is Vue an MVC or an MVVM framework?

Wikipedia tells us: MVVM is a variant of PM, and PM is a variant of MVC.

So to a certain extent, whether Vue is MVC or MVVM or not, its ideological direction is roughly the same as those of these design patterns.

And Vue’s official website also said: “although it does not fully follow the MVVM model, but Vue’s design has been inspired by it.”

This problem is more noisy online, this article is not to discuss this problem, but the face is to beginners shallow analysis of big brotherThe embodiment of MVC in Vue

0 novice confusion

In college, there were several web courses before and after my major. First, I taught HTML and CCC; then I taught JS; finally, I taught Vue.

Because my university is not a computer major, but the art of digital media art. So the enthusiasm for programming is almost negative.

Last semester’s JS did not learn well, listen to say to learn Vue, everyone’s heart is naturally broken. In the course, there is a code:

let app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
});

At the beginning, everyone’s voice was like this: what?! what is it? Who can read it!

Besides, not only beginners, but also some people who have written about Vue for a period of time know what el is and what data is, but they may not know itWhy does Vue organize code like this — unless he’s studied MVC

1 MVC counter

An MVC module is a combination of three objects: m, V, C.

  • M. That is, model, representing data;
  • 5. That is view, representing the view;
  • C. It is called controller, which stands for control (business logic).

Strictly speaking MCV is not strictly speaking, and the definition of MVC is not clear. Therefore, I think MVC is actually a kind of ideological direction, which represents that the view and business logic do not interfere with each other.

Or that sentence, put the code here. Let’s first implement a very common example: Add button and subtract button.

General version JS counter

0
  +
  -

What we want is that when we click on the + sign,When you click the – sign, it will be – 1.

I believe this JS code should be handy, right.

const numberWrapper = document.querySelector('#app span');
const addBtn = document.querySelector('#add');
const minusBtn = document.querySelector('#minus');

addBtn.addEventListener('click', () => {
  const newNumber = parseInt(numberWrapper.innerText) + 1;
  numberWrapper.innerText = newNumber.toString();
});

minusBtn.addEventListener('click', () => {
  const newNumber = parseInt(numberWrapper.innerText) - 1;
  numberWrapper.innerText = newNumber.toString();
});

But this is just a normal version. Let’s refactor the code step by step in MVC.

MVC version JS counter

First of all, we want to write a counter like this. If it needs to be modified, on the one hand, I have to change the HTML file and on the other hand, I have to modify the JS file. How troublesome!

Let’s write it together

const app = document.querySelector('#app');

const html = `
  0
  +
  -
`;
const counter = document.createElement('div');
counter.innerHTML = html;
app.appendChild(counter);

Let’s take a look at the current code:

  1. First, we need to create HTML elements;
  2. Then find the corresponding DOM element through CSS selector;
  3. Then add various listening events and operations to them.

Let’s guess, how can we use MVC?

First create a new object called view, and then put our HTML code in it

const view = {
  html: `
    0
    +
    -
  `
};

The operations we use to create a div, put HTML code into div, and then put div into app should also belong to the view layer.

So we add a render method to the view object:

const view = {
  // ...html...
  render() {
    const counter = document.createElement('div');
    counter.innerHTML = view.html;
    app.appendChild(counter);
  }
};

view.render();

So we’re done with V, and then look at C. In addition to views and data, everything else should belong to C, so DOM element acquisition and event binding are placed in C.

const controller = {
  ui: {},
  bindEvents() {}
};

Here we are going to put DOM elements in UI objects, but we need to think about it.

Once we’ve written it herequerySelectorThen we must not find the element because we don’t have render yet. We don’t have the buttons and numbers.

So we have to write an init function in it, so that after we perform initialization, it will first get the DOM and then bind the event

init() {
  this.ui = {
    numberWrapper: document.querySelector('#app span'),
    addBtn: document.querySelector('#add'),
    minusBtn: document.querySelector('#minus')
  };
  controller.bindEvents();
},

The binding event is very simple to write:

bindEvents() {
  controller.ui.addBtn.addEventListener('click', () => {
    const newNumber = parseInt(controller.ui.numberWrapper.innerText) + 1;
    controller.ui.numberWrapper.innerText = newNumber.toString();
  });
  controller.ui.minusBtn.addEventListener('click', () => {
    const newNumber = parseInt(controller.ui.numberWrapper.innerText) - 1;
    controller.ui.numberWrapper.innerText = newNumber.toString();
  });
}

Then there was a turning point,We want to create a model object to save the data

const model = {
  data: {
    number: 100
  }
};

At this time, I don’t know if you have realized something.

Now that we have the model, why do we have to manipulate DOM to get data?

How elegant to operate the model directly!

So bindevents can be changed to this:

controller.ui.addBtn.addEventListener('click', () => {
  model.data.number += 1;
});
controller.ui.minusBtn.addEventListener('click', () => {
  model.data.number -= 1;
});

Then our view object needs to be modified. It should also get data from the model

const view = {
  html: `
    {{number}}
    ......
  `,
  render() {
    const counter = document.createElement('div');
    counter.innerHTML = view.html.replace('{{number}}', model.data.number);
    app.appendChild(counter);
  }
};

However, we did not render the data to the page again, although we said that the data was modified. Therefore, it is necessary to render again after each submission.

At this point, the problem arises: after clicking + or – the number will only change once, and the second click will be useless!

Why is that?

It’s very simple, because we’re rerender, leading to twoNone of the buttons that bind events are the same as he was

So we use the event broker to solve this problem — bind the event to the div in the outer layer, and then determine the ID of the object to be clicked.

It is written as follows:

const compute = e => {
  switch (e.target.id) {
    case 'add':
      model.data.number += 1;
      break;
    case 'minus':
      model.data.number -= 1;
      break;
    default:
      return;
  }
  view.render();
};

Next, we will add an EL attribute to the view object to store the outer div we created.

const view = {
  el: null,
  // ......
  render() {
    if (!view.el) {
      //Create div and assign div to El
    } else {
      //Replace the innerHTML of El with the new content
    }
  }
};

Finally, we optimize it one more step.

We should not know which element should be appended when render. This element should be passed on to me by others, so it should be written as follows:

Total code:

V of MVC

const view = {
  el: null,
  html: `
    {{n}}
    +
    -
  `,
  render(container) {
    if (!view.el) {
      const counter = document.createElement('div');
      view.el = counter;
      counter.innerHTML = view.html.replace(
        '{{n}}',
        model.data.number.toString()
      );
      container.appendChild(counter);
    } else {
      view.el.innerHTML = view.html.replace(
        '{{n}}',
        model.data.number.toString()
      );
    }
  }
};

M of MVC

const model = {
  data: {
    number: parseInt(window.localStorage.getItem('number')) || 0
  },
  save() {
    window.localStorage.setItem('number', model.data.number.toString());
  }
};

C of MVC

const controller = {
  init(container) {
    controller.ui = {
      container
    };
    view.render(container);
    controller.bindEvents();
  },
  bindEvents() {
    controller.ui.container.addEventListener('click', e => {
      switch (e.target.id) {
        case 'add':
          model.data.number += 1;
          break;
        case 'minus':
          model.data.number -= 1;
          break;
        default:
          return;
      }
      model.save();
      view.render();
    });
  }
};

Usage:

const app = document.querySelector('#app');

controller.init(app);

At this time, our program is already a relatively complete MVC mode, but it is a waste of performance to render all directly.

So frameworks like react use virtual Dom and diff algorithms to modify only the changed dom.

In general, our MVC idea can be abstracted into a formulaview = render(data)

Use class to optimize code

Class optimization code can improve the degree of code reuse. Remember:Programmers should never repeat themselves

Let’s take a look at the model

class Model {
  constructor(options) {
    for (let key in options) {
      this[key] = options[key];
    }
  }

  save() {
    console.error ('save function has not been passed in');
  }
}

export default Model;

This is very simple. Everything we want to pass in is in this option, like this:

const model = new Model({
  data: {},
  save() {}
});

In retrospect, when we use Vue, is it the same?

export default new Vue({
  data() {
    return {
      msg: 'hello world'
    };
  },
  methods: {}
});

I haven’t read the source code of Vue, and I don’t know whether Vue built the code according to this article.

But Vue, react and other frameworks can be found in MVC. So there is no doubt that the idea of MVC is a design pattern that every programmer needs to learn.

When learning a program, you use several useful frameworks and tools. You should not only indulge in its convenience, but also be good at finding the clues left by the author from the use of tools. Only by doing so can we gradually evolve into more and more God programmers who are not afraid of new technologies and frameworks!

Tools may change month by month and day by day, but thinking is eternal.

(end)