AI taught me to write a simplified Vue framework with 200 lines of code

AI is the best teacher of the future

Recently, I was preparing a study material on Vue basics. During this period, I had a whim: Can I use the popular ChatGPT to help me create learning content? In fact, Vue itself is not difficult to learn, especially the basic usage. However, if you want to master Vue in depth, you must read the source code of Vue to understand its principles. However, not everyone has enough time and patience to read Vue's source code. So I spent over an hour talking to ChatGPT, guiding it to generate some sample code that demonstrates the principles of Vue. In this process, I just act as a questioner and let ChatGPT generate the code I want according to my question. The results were a bit of a surprise to me, but not entirely unexpected. In view of the current rapid development trend of AI, I believe that by 2024, many occupations will be replaced by AI. Although AI cannot completely replace teachers at that time, it has the potential to become a personal teaching assistant tool. I believe that the future is not about AI replacing humans, but about people who know how to use AI replacing people who don't know how to use AI. Next, let us see the level of this AI teaching assistant together.

This article mainly talks about the following contents:

  • Let AI list Vue's learning outline from entry to advanced
  • Try to get AI to give code examples that explain how Vue works
  • Start learning the principles of the Vue life cycle
  • Learn the principles of Vue template syntax and directives
  • Learn the principles of Vue data monitoring and computing properties
  • Learn the principles of Vue event handling and methods
  • Learn the principle of Vue slot (slot)
  • Implement a simplified Vue framework with 200 lines of code

Let AI list the outline of learning Vue

In order to learn Vue systematically, you need to make a study outline first. Combined with my understanding of ChatGPT, the first question I asked it received a satisfactory reply. The following is the original text of my question:

请列出一个Vue.js从入门到高级的学习大纲

ChatGPT's answer is as follows:

当涉及Vue.js的学习大纲时,下面是一个从入门到高级的学习路线,涵盖了Vue.js的核心概念和高级主题:

  1. HTML, CSS, and JavaScript Basics
    • HTML elements and tags
    • CSS selectors and styles
    • JavaScript Basic Syntax and Concepts
  2. Getting started with Vue.js
    • Introduction and features of Vue.js
    • Vue.js installation and setup
    • Vue instances and lifecycle hooks
    • Vue template syntax and directives
    • Data Binding and Computed Properties
    • Event Handling and Methods
  3. Vue components
    • Basic concept and composition of components
    • Component registration and use
    • How components communicate (props, events, and vuex)
    • Dynamic and asynchronous components
    • Slots and scoped slots
  4. Vue-routing
    • Installation and configuration of Vue Router
    • Basic usage of routing (navigation, parameter passing)
    • Nested and named routes
    • Routing guards and navigation analysis
  5. Vue state management (Vuex)
    • The core concepts and usage scenarios of Vuex
    • Vuex installation and configuration
    • State、Mutation、Action和Getter
    • Modularity and Namespaces
  6. Advanced Vue
    • custom directive
    • Filter
    • Mixin
    • Plugins
    • Custom transitions and animations
    • Server Side Rendering (SSR)
  7. Integration of Vue with external libraries
    • HTTP requests using Axios
    • Use Vue to integrate with third-party libraries (such as Chart.js, Google Maps, etc.)
    • Project build with Vue and Webpack
  8. Vue best practices and performance optimization
    • Split and reuse of components
    • Judicious use of computed properties and listeners
    • Avoid unnecessary rendering
    • Async Components and Code Splitting
    • Optimize network requests and data loading
  9. Vue test
    • Concepts of unit testing and end-to-end testing
    • Unit testing with Vue Test Utils
    • End-to-end testing with Cypress
  10. Vue project combat
    • Build a complete application based on Vue.js
    • State management with Vue Router and Vuex
    • Integrate other libraries and tools
    • Test and optimize applications

请注意,这个学习大纲只是一个指导,你可以根据自己的学习进度和需求进行调整和扩展。此外,学习Vue.js最好通过实践项目和阅读官方文档来加深理解和掌握。

The above content is generated by ChatGPT. It has certain universality, but I am not going to follow this outline. I hope that everyone can learn and master the usage of Vue by reading the source code of Vue. For example, many people think that the use of Vue data two-way binding is very simple, but it is not easy to let yourself simulate the realization of data two-way binding. There are four main reasons why learning how to use Vue requires mastering its principles (these reasons are also generated by ChatGPT):

  • In-depth understanding of the framework : By learning the principles of Vue, you will better understand how the Vue framework is designed and works. This includes core concepts such as Vue's responsive data binding, virtual DOM, and component architecture. Understanding these principles can help you use Vue better and develop and debug applications more efficiently.

  • Custom extensions and optimizations : Mastering the principles of Vue enables you to do custom extensions and optimizations. You can customize Vue's instructions, plug-ins, filters, etc. according to project requirements, as well as optimize performance and reduce resource consumption. Understanding the principles can make you more flexible to deal with various development scenarios.

  • Troubleshooting and debugging : During the development process, it is inevitable that you will encounter some problems and errors. If you understand the principles of Vue, you can locate and solve problems faster. Understanding the principles can help you gain insight into Vue's internals, identify potential sources of errors, and take corrective action.

  • Cross-framework learning and migration : Mastering the principles of Vue can lay the foundation for you to learn other front-end frameworks in the future. Many front-end frameworks have similarities in concepts and principles, so once you master the principles of Vue, it will be easier for you to understand and learn other frameworks, such as React, Angular, etc. Also, if you need to migrate your project to another framework, understanding the principles can help you better understand how the new framework works.


Let AI give code examples that explain how Vue works

How do I ask ChatGPT a question so that it can generate sample code that simulates a feature implemented in Vue? According to past experience, at first I asked the question like this:

请给一个简单的JS代码实现的例子,说明Vue的生命周期及生命周期钩子的实现原理

ChatGPT's answer is as follows:

以下是一个简单的JavaScript代码示例,展示了Vue的生命周期及生命周期钩子的实现原理:

<!DOCTYPE html>
<html>
<head>
  <title>Vue Lifecycle Example</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <p>{
   
   { message }}</p>
    <button @click="updateMessage">Update Message</button>
  </div>

  <script>
    var app = new Vue({
      el: '#app',
      data: {
        message: 'Hello, Vue!'
      },
      beforeCreate: function() {
        console.log('beforeCreate hook');
      },
      created: function() {
        console.log('created hook');
      },
      beforeMount: function() {
        console.log('beforeMount hook');
      },
      mounted: function() {
        console.log('mounted hook');
      },
      beforeUpdate: function() {
        console.log('beforeUpdate hook');
      },
      updated: function() {
        console.log('updated hook');
      },
      beforeDestroy: function() {
        console.log('beforeDestroy hook');
      },
      destroyed: function() {
        console.log('destroyed hook');
      },
      methods: {
        updateMessage: function() {
          this.message = 'Updated message';
        }
      }
    });
  </script>
</body>
</html>

Obviously, this is just a usage example of the Vue life cycle, not an example of the principle of simulating the Vue life cycle that we want. Therefore, it is very important to master the hinting skills of AI. In the future, an AI prompter may become a profession, and learning AI prompting skills will become a survival skill just like using a smartphone today.


Use AI to learn the principles of the Vue life cycle

The previous code has let us understand how to use Vue's life cycle. In order to facilitate simulation, we use Vue's optional API, and make some adjustments to the usage of the life cycle, assuming that the sample code of the simulation usage is as follows:

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello, Vue!'
  },
  beforeCreate: function() {
    console.log('beforeCreate hook');
  },
  created: function() {
    console.log('created hook');
  },
  beforeMount: function() {
    console.log('beforeMount hook');
  },
  mounted: function() {
    console.log('mounted hook');
  },
  beforeUpdate: function() {
    console.log('beforeUpdate hook');
  },
  updated: function() {
    console.log('updated hook');
  },
  render: function() {
    return '<p>' + this.message + '</p>';
  }
});

What we adjust is to  render replace it with a method  methods, and the purpose is to  render render the content returned by the method to  app the DOM element with ID .  The content that  will be obtained in  render the method  is the value of the property  defined in the object   .this.messagedatamessage

Let's first think about how to realize the above functions. The key point is to realize the el, data, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, and render properties of the Vue configuration object.

Next, let's see how ChatGPT realizes the above properties. The following is my prompt for ChatGPT:

请给出一段代码,模拟实现Vue.js类似生命周期效果的示例代码

ChatGPT's answer is as follows:

以下是一个简单的JavaScript代码示例,模拟实现类似Vue.js生命周期的效果:

function Vue(options) {
  this.$options = options;

  if (typeof options.beforeCreate === 'function') {
    options.beforeCreate.call(this);
  }

  this._data = typeof options.data === 'function' ? options.data() : options.data;

  this._proxyData();

  if (typeof options.created === 'function') {
    options.created.call(this);
  }

  this.$mount(options.el);
}

Vue.prototype.$mount = function(el) {
  this.$el = document.querySelector(el);

  if (typeof this.$options.beforeMount === 'function') {
    this.$options.beforeMount.call(this);
  }

  this.render();

  if (typeof this.$options.mounted === 'function') {
    this.$options.mounted.call(this);
  }
};

Vue.prototype._proxyData = function() {
  var self = this;
  Object.keys(this._data).forEach(function(key) {
    Object.defineProperty(self, key, {
      get: function() {
        return self._data[key];
      },
      set: function(newValue) {
        self._data[key] = newValue;
        if (typeof self.$options.beforeUpdate === 'function') {
          self.$options.beforeUpdate.call(self);
        }
        self.render();
        if (typeof self.$options.updated === 'function') {
          self.$options.updated.call(self);
        }
      }
    });
  });
};

Vue.prototype.render = function() {
  if (typeof this.$options.render === 'function') {
    this.$el.innerHTML = this.$options.render.call(this);
  }
};

// 使用示例
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello, Vue!'
  },
  beforeCreate: function() {
    console.log('beforeCreate hook');
  },
  created: function() {
    console.log('created hook');
  },
  beforeMount: function() {
    console.log('beforeMount hook');
  },
  mounted: function() {
    console.log('mounted hook');
  },
  beforeUpdate: function() {
    console.log('beforeUpdate hook');
  },
  updated: function() {
    console.log('updated hook');
  },
  render: function() {
    return '<p>' + this.message + '</p>';
  }
});

In this example, we  Vue simulate the Vue.js lifecycle by defining a constructor. The constructor receives a configuration object as a parameter and triggers various lifecycle hook functions during the instantiation process.

In the constructor, we first determine whether the hook function is defined in the configuration object  beforeCreate ,  created and call it at the corresponding time. data Then, we process the data object, read the properties  in the configuration object  data , execute it to obtain its return value if it is a function, and assign the value directly if it is an object, and then  _proxyData proxy the properties in the data object to  Vue the instance through the method to achieve two-way binding of data.

Next, in  the method, we judge whether the  hook  function $mount is defined in the configuration object   , and call it at the corresponding time. In   the method, we call   the method to render the template on the specified DOM element.beforeMountmounted$mountrender

In  _proxyData the method, we use  Object.defineProperty the properties of the data object to be delegated to  Vue the instance, and  trigger  ,   and  hooks in set the method  of the property  , which means that as long as the properties of the data object change, the view update will be triggered.beforeUpdaterenderupdated

In  render the method, we judge whether  render a function is defined in the configuration object, obtain the rendered content through the function, and then assign it to  the attribute $el of the element  innerHTML , where  $el the element  $mount is initialized in the method  document.querySelector .

Finally, we use the configuration object in the example to instantiate  Vue , and trigger the life cycle hook function by simulating rendering, so as to achieve the effect of each life cycle similar to Vue.js.

The above explanation for the code is also generated by ChatGPT, I just added some explanations. It can be seen that ChatGPT's ability in this area is no less than that of professional private teachers. It not only saves me a lot of time for preparing lessons, but even speaks better than me in some parts.

The above JavaScript code cannot be run directly. We save the code as  lifecycle.js a file, then create a  index.html web page and reference the file. The code of the web page is as follows:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="./lifecycle.js"></script>
  </body>
</html>

In this way, we  index.html can run it  directly by opening it lifecycle.js, then view the rendering result in the browser, and view the log output in the console. Additionally, we can  app.message = 'ChatGPT' validate data binding and page update mechanisms on control inputs.


Use AI to learn the principles of Vue template syntax and instructions

According to the above learning path, before we learn template syntax and instructions, we also give example codes for easy-to-simulate usage:

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello, Vue!',
    inputValue: 'ChatGPT'
  },
  template: `
    <div>
      <p>{
   
   { message }}</p>
      <input v-model="inputValue" type="text">
      <p v-text="inputValue"></p>
    </div>
  `
});

We focus on the following snippets of HTML involving templates and directives:

<div>
  <p>{
   
   { message }}</p>
  <input v-model="inputValue" type="text">
  <p v-text="inputValue"></p>
</div>

This HTML fragment itself is the content of the template, which uses a template syntax and two directives, the template syntax is  and the directives are  and  { { message }}respectively  .v-modelv-text

In the same way, everyone first think about how to implement it, and then see how ChatGPT implements template syntax and instructions. The following are the prompts I gave:

请给出一段代码,模拟实现Vue.js类似模板语法和指令的示例代码

ChatGPT's answer is as follows:

以下是一个简单的JavaScript代码示例,模拟实现类似Vue.js的模板语法和指令:

function Vue(options) {
  this.$options = options;
  this._data = typeof options.data === 'function' ? options.data() : options.data;
  this._compileTemplate();
}

Vue.prototype._compileTemplate = function() {
  var self = this;
  var template = this.$options.template || '';

  var evalExpression = function(expression) {
    with (self._data) return eval(expression);
  }

  var compiledTemplate = template.replace(/\{\{(.*?)\}\}/g, function(match, expression) {
    var value = evalExpression(expression);
    return value !== undefined ? value : '';
  });

  var element = document.querySelector(this.$options.el);
  element.innerHTML = compiledTemplate.trim();

  element.querySelectorAll('[v-model]').forEach(function(element) {
    var value = element.getAttribute('v-model');
    element.value = self._data[value];
    element.addEventListener('input', function(event) {
      self._data[value] = event.target.value;
    });
  });

  element.querySelectorAll('[v-text]').forEach(function(element) {
    var value = element.getAttribute('v-text');
    element.textContent = self._data[value];
  });
};

// 使用示例
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello, Vue!',
    inputValue: 'ChatGPT'
  },
  template: `
    <div>
      <p>{
   
   { message }}</p>
      <input v-model="inputValue" type="text">
      <p v-text="inputValue"></p>
    </div>
  `
});

In this example, we  Vue simulate Vue.js' template syntax and directives by defining a constructor. The constructor receives a configuration object as an argument and compiles the template during instantiation.

In  _compileTemplate the method, we first get the template string in the configuration object and use the regular expression to match  { { expression }} the part. We then use  eval a function to dynamically evaluate the expression, substituting the value back into the template string. Next, we obtain the corresponding DOM element according to the properties in the configuration object  el , and assign the compiled template string to the  innerHTML property of the element.

Then, we  querySelectorAll get all  v-model elements with attributes via method and iterate over each element. During traversal, we parse  model the directive, set the element's value to the corresponding data attribute value, and add  input event listeners for  inputValue simple two-way binding.

Next, we  querySelectorAll get all  v-text elements with attributes through the method, and iterate over each element. During traversal, we parse  text the directive and set the element's text content to the corresponding data attribute value. Note: We have not implemented  inputValue two-way binding for this directive.

Finally, we instantiate using the config object from the example  Vue, passing in the template string. By simulating parsing templates and executing instructions, we have achieved the effect of template syntax and instructions similar to Vue.js. To view the effect, please open the page in the browser  index.html .

The above code is not completely generated by ChatGPT, because during the debugging process, two problems need to be corrected to run normally:

  • The first problem is that ChatGPT wants to directly use  eval the function to dynamically evaluate the expression. The code generated by ChatGPT is  eval(expression), which  expression is a string  " message ", please note that  message there are spaces on both sides. The value obtained by directly calling  eval the function  message will report an error, because there is no attribute defined in the global object  message , and  { { message }} the original intention of the expression is to obtain  the attribute value this._data of the object  message . To this end, I added a  evalExpression function  to  set the global object  through with the statement  , so that  the value can be obtained correctly. However,   it is not recommended to use the statement in strict mode, so it is not the optimal solution. Interested students can study the optimal solution.this._datathis._data.messagewith

  • The second problem is that ChatGPT wants to use  the content of querySelectorAll('[v-]') simultaneous acquisition  v-model and  v-text instruction, but it cannot be acquired during the actual debugging process. So I disassembled it into two statements: querySelectorAll('[v-model]') and  querySelectorAll('[v-text]') get the command content respectively.

It can be seen that we are not yet finished trusting the code generated by ChatGPT. If you find that there is a problem with the code, you can question it, it will say sorry and then regenerate a new code. Whether the updated code is correct or not, you still need to verify it again.


Use AI to learn the principles of Vue data monitoring and computing properties

The next thing to learn is still relatively basic data monitoring and computing attributes. In fact, the previous example of the life cycle has included data two-way binding. The principles of data monitoring and computing attributes are very similar to data two-way binding.

Similarly, we first give the usage example code for easy simulation:

var app = new Vue({
  data: {
    message: 'Hello, Vue!',
    firstName: 'John',
    lastName: 'Doe'
  },
  computed: {
    fullName: function() {
      return this.firstName + ' ' + this.lastName;
    }
  },
  watch: {
    message: function(newValue) {
      console.log('Message changed:', newValue);
    }
  }
});

console.log(app.message);       // Output: Hello, Vue!
app.message = 'Hello, Vue.js!'; // Output: Message changed: Hello, Vue.js!
console.log(app.message);       // Output: Hello, Vue.js!
console.log(app.fullName);      // Output: John Doe
app.message = 'New message';    // Output: Message changed: New message

As can be seen from the sample code, the configuration object has added  computed and  watch attributes, which respectively define computed attributes  fullNameand  message functions to monitor attribute changes.

Everyone first think about how to implement it yourself, and then see how ChatGPT implements data monitoring and calculation attributes. The following are the prompts I gave:

请给出一段代码,模拟实现Vue.js类似数据监听和计算属性的示例代码

ChatGPT's answer is as follows:

要模拟实现Vue.js类似数据监听和计算属性的功能,可以使用Object.defineProperty来定义属性的get和set方法,并使用watch函数来监听属性的变化。以下是一个示例代码:

function Vue(options) {
  this._data = options.data;
  this._computed = options.computed;
  this._watch = options.watch;

  this._proxyData();
  this._createComputed();
  this._createWatchers();
}

Vue.prototype._proxyData = function() {
  var self = this;
  Object.keys(this._data).forEach(function(key) {
    Object.defineProperty(self, key, {
      get: function() {
        return self._data[key];
      },
      set: function(newValue) {
        self._data[key] = newValue;
      }
    });
  });
};

Vue.prototype._createComputed = function() {
  var self = this;
  var computed = this._computed || {};

  Object.keys(computed).forEach(function(key) {
    Object.defineProperty(self, key, {
      get: function() {
        return computed[key].call(self);
      }
    });
  });
};

Vue.prototype._createWatchers = function() {
  var self = this;
  var watch = this._watch || {};

  Object.keys(watch).forEach(function(key) {
    var callback = watch[key]
    var value = self._data[key];

    Object.defineProperty(self._data, key, {
      get: function() {
        return value;
      },
      set: function(newValue) {
        value = newValue;
        callback.call(self, newValue);
      }
    });
  });
};

// 使用示例
var app = new Vue({
  data: {
    message: 'Hello, Vue!',
    firstName: 'John',
    lastName: 'Doe'
  },
  computed: {
    fullName: function() {
      return this.firstName + ' ' + this.lastName;
    }
  },
  watch: {
    message: function(newValue) {
      console.log('Message changed:', newValue);
    }
  }
});

console.log(app.message);       // Output: Hello, Vue!
app.message = 'Hello, Vue.js!'; // Output: Message changed: Hello, Vue.js!
console.log(app.message);       // Output: Hello, Vue.js!
console.log(app.fullName);      // Output: John Doe
app.message = 'New message';    // Output: Message changed: New message

In this example, we define a  Vue constructor that takes an   options object containing data, , computed and  .watch

In  _proxyData the method, we use  the  methods   defined  Object.defineProperty for  the properties on the instance  to delegate to   the object.Vuegetset_data

In  the method, we  define a method for each computed property  _createComputed by traversing  the object so that it can be used as a normal property.computedget

In  _createWatchers the method, we traverse  the object, use  the monitor  object for the change of the property in the object watch for each property   , and trigger the callback function when the change occurs.Object.defineProperty_data

Finally, we use the configuration object in the example to instantiate  Vueand pass in the functions of the calculated property and the monitored property, and execute a script to read and change the value of the configured property, realizing the effect of data monitoring and calculated property similar to Vue.js.

As before, the above code is not exactly generated by ChatGPT. In the original generated code, the listener attribute needs to execute  app.$watch('message', ...) the statement that calls  app the  $watch method to add the listener function. The revised monitoring method is more in line  Vue with official usage.


Use AI to learn the principles of Vue event handling and methods

Event processing is a very important part in the component development process. Next, we simulate the mechanism of implementing Vue's event processing and methods. The following is a usage example code for easy simulation:

var app = new Vue({
  data: {
    message: 'Hello, Vue!'
  },
  methods: {
    greet: function() {
      this.$emit('greet', this.message);
    },
    updateMessage: function(newMessage) {
      this.message = newMessage;
    }
  },
});

app.$on('greet', function(message) {
  console.log('Greet:', message);
});

app.greet(); // Output: Greet: Hello, Vue!
app.updateMessage('Hello, World!');
app.greet(); // Output: Greet: Hello, World!

As can be seen from the sample code, the configuration object adds new  methods attributes for defining instance methods, and also adds built-in  $emit and  $on methods for throwing events and listening to events.

Everyone first think about how to implement it yourself, and then see how ChatGPT implements event processing and methods. The following are the prompts I gave:

请给出一段代码,模拟实现Vue.js类似事件处理和方法的示例代码

ChatGPT's answer is as follows:

以下是一个简单的JavaScript代码示例,模拟实现类似Vue.js的事件处理和方法:

function EventBus() {
  this._events = {};
}

EventBus.prototype.on = function(eventName, callback) {
  if (!this._events[eventName]) {
    this._events[eventName] = [];
  }
  this._events[eventName].push(callback);
};

EventBus.prototype.emit = function(eventName, payload) {
  if (this._events[eventName]) {
    this._events[eventName].forEach(function(callback) {
      callback(payload);
    });
  }
};

function Vue(options) {
  this._data = typeof options.data === 'function' ? options.data() : options.data;
  this._methods = options.methods;
  this._eventBus = new EventBus();

  this._proxyData();
  this._proxyMethods();
}

Vue.prototype._proxyData = function() {
  var self = this;
  Object.keys(this._data).forEach(function(key) {
    Object.defineProperty(self, key, {
      get: function() {
        return self._data[key];
      },
      set: function(newValue) {
        self._data[key] = newValue;
      }
    });
  });
};

Vue.prototype._proxyMethods = function() {
  var self = this;
  var methods = this._methods;
  if (methods) {
    Object.keys(methods).forEach(function(key) {
      self[key] = methods[key].bind(self);
    });
  }
};

Vue.prototype.$emit = function(eventName, payload) {
  this._eventBus.emit(eventName, payload);
};

Vue.prototype.$on = function(eventName, callback) {
  this._eventBus.on(eventName, callback);
};

// 使用示例
var app = new Vue({
  data: {
    message: 'Hello, Vue!'
  },
  methods: {
    greet: function() {
      this.$emit('greet', this.message);
    },
    updateMessage: function(newMessage) {
      this.message = newMessage;
    }
  },
});

app.$on('greet', function(message) {
  console.log('Greet:', message);
});

app.greet(); // Output: Greet: Hello, Vue!
app.updateMessage('Hello, World!');
app.greet(); // Output: Greet: Hello, World!

In this example, we first use  EventBus objects to simulate the event bus. EventBus Objects have  on methods for listening to events and  emit methods for firing events.

In  Vue the constructor, we create an  EventBus instance and assign it to  _eventBus properties.

In  _proxyData the method, we use to  Object.defineProperty proxy the properties of the data object to the Vue instance in order to realize the two-way binding of data.

In  _proxyMethods the method, we bind the methods in the configuration object to the Vue instance, so that these methods can be directly accessed and called in the instance.

In  Vue the prototype, we define  $emit the and  $on methods. $emit The method is used to throw an event and receives two parameters: event name and optional data payload. $on The method is used to listen to events and receives two parameters: event name and callback function.

Finally, we create an  Vue instance, and the method of the existing  Vue configuration object  greet uses  $emit the method trigger  greet event to trigger the callback function and output the message. $on Then, use methods to listen to  events in the instance  greet , and define callback functions to output messages. In this way, we simulate the function of throwing and receiving events similar to Vue.js.

As before, the above code is not exactly generated by ChatGPT. In the original generated code, there is no mock implementation of throwing events and receiving events. I gave a new prompt,  EventBus the code generated by ChatGPT, and then I merged the two pieces of code together to get the above sample code.


Use AI to learn the principle of Vue slot (slot)

Finally, let's learn about Vue's slots. In order to simulate the usage of slots, we have to simulate Vue's component declaration first, define the slots in the components, then import the components through the template, and set the content of the component slots. The following is a usage example code for easy simulation:

var HelloComponent = {
  data: function() {
    return {
      name: 'John'
    };
  },
  template: `
    <div>
      <h1>{
   
   { name }}</h1>
      <slot></slot>
    </div>
  `
};

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello, Vue!'
  },
  components: {
    HelloComponent
  },
  template: `
    <HelloComponent>
      <p>{
   
   { message }}</p>
    </HelloComponent>
  `
});

In the above sample code, we declare a  HelloComponent component, add a slot in the template of the component,  slot then introduce the component in the template of the application  , and add  the content as the slot HelloComponent in the component  .<p>{ { message }}</p>

The result of running the code will generate the following HTML code:

<div id="app">
  <hellocomponent>
    <div>
      <h1>John</h1>
      <slot>
        <p>Hello, Vue!</p>
      </slot>
    </div>
  </hellocomponent>
</div>

For the sake of code simplification, HelloComponent and  slot two nodes are still retained. Everyone first think about how to implement it yourself, and then see how ChatGPT implements slots. The following are the prompts I gave:

请给出一段代码,模拟实现Vue.js类似插槽(slot)的示例代码

ChatGPT's answer is as follows:

要模拟实现Vue.js类似插槽(slot)的功能,可以在组件定义中使用特殊的标记来表示插槽,并在组件实例中通过相应的属性将内容传递到插槽中。以下是一个示例代码:

function Vue(options) {
  this.$options = options;
  this._data = typeof options.data === 'function' ? options.data() : options.data;
  this._components = options.components || {};

  this._proxyData();
  this._compileTemplate();
  this._proxyComponents();
}

Vue.prototype._proxyData = function() {
  var self = this;
  Object.keys(this._data).forEach(function(key) {
    Object.defineProperty(self, key, {
      get: function() {
        return self._data[key];
      },
      set: function(newValue) {
        self._data[key] = newValue;
      }
    });
  });
};

Vue.prototype._compileTemplate = function() {
  var self = this;
  var el = this.$options.el
  var template = this.$options.template || '';

  var evalExpression = function(expression) {
    with (self) return eval(expression);
  }

  var compiledTemplate = template.replace(/\{\{(.*?)\}\}/g, function(match, expression) {
    var value = evalExpression(expression);
    return value !== undefined ? value : '';
  });

  var element = el ? document.querySelector(el) : document.createElement('div');
  element.innerHTML = compiledTemplate.trim();
  this.$el = el ? element : element.childNodes[0];
};

Vue.prototype._proxyComponents = function() {
  var self = this;
  var components = this._components;

  Object.keys(components).forEach(function(componentName) {
    var component = new Vue(components[componentName]);
    self.$el.querySelectorAll(componentName).forEach(function(element) {
      component.$el.querySelectorAll('slot').forEach(function(slot) {
        slot.innerHTML = element.innerHTML;
      });
      element.innerHTML = component.$el.outerHTML;
    });
  });
};

// 使用示例
var HelloComponent = {
  data: function() {
    return {
      name: 'John'
    };
  },
  template: `
    <div>
      <h1>{
   
   { name }}</h1>
      <slot></slot>
    </div>
  `
};

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello, Vue!'
  },
  components: {
    HelloComponent
  },
  template: `
    <HelloComponent>
      <p>{
   
   { message }}</p>
    </HelloComponent>
  `
});

In this example, we define a  Vue constructor that takes an   options object containing data, , template and  .components

In  _proxyData the method, we use to  Object.defineProperty proxy the properties of the data object to the Vue instance in order to realize the two-way binding of data.

In  _compileTemplate the method, we first get the template string in the configuration object and use the regular expression to match  { { expression }} the part. We then use  eval a function to dynamically evaluate the expression, substituting the value back into the template string. Note: Here  eval the previous  with statement follows  self, not the previous  self._data, because of the method added in this example  _proxyData . el Next, we get the corresponding DOM element  according to the properties in the configuration object  . If the DOM element does not exist, we create an div element instead, and then assign the compiled template string to the  innerHTML property of the element. Then set  $el the properties for the Vue instance. Note: If you use  div an element instead, you need to  childNodes[0] exclude the  div element.

In  _proxyComponents the method, we first obtain the component declaration in the configuration object, then traverse all components, obtain the component object according to the component name, create an instance of the object, and use the properties of the instance to traverse all the slots, set the original as the content of the slot, and then reset the content of  Vue the  $el component  slot as  innerHTML the  innerHTML instance  $el element  outerHTML .

Finally, we define an  HelloComponent object first, and add a slot to the component's template  slot . Then create another  Vue instance, introduce the component in the template of the configuration object  HelloComponent , and add the content of the slot in the component. In this way, we simulate the function of throwing and receiving events similar to Vue.js.

Similarly, the above code is not entirely generated by ChatGPT. In the original generated code, a  VueComponent constructor is also created, but the replacement of the slot content is not implemented. There is no slot-related code in other places except the usage example, and even the  components objects in the usage example code are added by me.


Implement a simplified Vue framework with 200 lines of code

We have simulated Vue's life cycle, template syntax, instructions, two-way binding, data monitoring, computed properties, event handling, methods, components, and slots before. Now it's time to combine these simulated features and build a simple version of the Vue framework. Here's an example code of usage for easy mocking:

var HelloComponent = {
  emits: ['greet'],
  data: function() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    };
  },
  computed: {
    fullName: function() {
      return this.firstName + ' ' + this.lastName;
    }
  },
  updated: function() {
    this.$emit('greet', this.firstName);
  },
  template: `
    <div>
      <h1>{
   
   { fullName }}</h1>
      <slot></slot>
    </div>
  `
};

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello, Vue!',
    inputValue: 'ChatGPT'
  },
  watch: {
    message: function(newValue, oldValue) {
      console.log('Message changed:', oldValue, ' -> ', newValue);
    },
    inputValue: function(newValue, oldValue) {
      console.log('InputValue changed:', oldValue, ' -> ', newValue);
    }
  },
  methods: {
    greetMessage: function(message) {
      this.$emit('greet', message);
    },
    updateMessage: function(newMessage) {
      this.message = newMessage;
    }
  },
  components: {
    HelloComponent
  },
  beforeCreate: function() {
    console.log('beforeCreate hook');
  },
  created: function() {
    console.log('created hook');
  },
  beforeMount: function() {
    console.log('beforeMount hook');
  },
  mounted: function() {
    console.log('mounted hook');
  },
  beforeUpdate: function() {
    console.log('beforeUpdate hook');
  },
  updated: function() {
    console.log('updated hook');
  },
  template: `
    <div>
      <HelloComponent v-on:greet="greetMessage">
        <p>{
   
   { message }}</p>
      </HelloComponent>
      <input v-model="inputValue" type="text">
      <p v-text="inputValue"></p>
    </div>
  `
});

app.$on('greet', function(message) {
  console.log('Greet:', message);
});

app.inputValue = 'OpenAI'
app.HelloComponent.firstName = 'Tom';
app.updateMessage('Hello, World!');

This code adds new functions based on the previous ones, such as  app.HelloComponent.firstName the application can obtain the instance of the sub-component through the component name, v-on:greet listen to the events of the sub-component, and so on. Especially with regard to  greet events, the sequence of actions that take place is as follows:

  •  Events   are thrown during  the  HelloComponent component's life cycle  , and the parameters of the event are  properties.updatedgreetfirstName
  • In the template of the app application,  the event of <HelloComponent v-on:greet="greetMessage"> the listening  HelloComponent component  is declared greet , and the event will trigger the method in the configuration object of the app application  greetMessage .
  • In the method of the app application   , the event  greetMessage is thrown out again  , and the instance of the application  listens  to  the event to output   the value.greetapp.$on('greet', ...)greetfirstName

As a result of the above code running, the output HTML page code is as follows:

<div id="app">
  <div>
    <hellocomponent v-on:greet="greetMessage">
      <div>
        <h1>Tom Doe</h1>
        <slot>
          <p>Hello, World!</p>
        </slot>
      </div>
    </hellocomponent>
    <input v-model="inputValue" type="text" />
    <p v-text="inputValue">OpenAI</p>
  </div>
</div>

The console output results are as follows. app.inputValue = 123 In addition, you can observe the effect of data two-way binding in console input  and other ways.

beforeCreate hook
created hook
beforeMount hook
mounted hook
InputValue changed: ChatGPT  ->  OpenAI
beforeUpdate hook
updated hook
Greet: Tom
Message changed: Hello, Vue!  ->  Hello, World!
beforeUpdate hook
updated hook

The following is the essence of this article, a simplified version of the Vue framework that only needs 200 lines of code:

function EventBus() {
  this._events = {};
}

EventBus.prototype.on = function(eventName, callback) {
  if (!this._events[eventName]) {
    this._events[eventName] = [];
  }
  this._events[eventName].push(callback);
};

EventBus.prototype.emit = function(eventName, payload) {
  if (this._events[eventName]) {
    this._events[eventName].forEach(function(callback) {
      callback(payload);
    });
  }
};

function Vue(options) {
  this.$options = options;

  if (typeof options.beforeCreate === 'function') {
    options.beforeCreate.call(this);
  }

  this._data = typeof options.data === 'function' ? options.data() : options.data;
  this._eventBus = new EventBus();
  this._proxyData();
  this._proxyMethods();
  this._createComputed();
  this._createWatchers();

  if (typeof options.created === 'function') {
    options.created.call(this);
  }

  this.$mount();
}

Vue.prototype.$render = function() {
  if (typeof this.$options.render === 'function' && this.$options.el) {
    this.$el = document.querySelector(this.$options.el);
    this.$el.innerHTML = this.$options.render.call(this);
  } else {
    this._compileTemplate();
    this._proxyComponents();
  }
};

Vue.prototype.$mount = function() {
  if (typeof this.$options.beforeMount === 'function') {
    this.$options.beforeMount.call(this);
  }

  this.$render();

  if (typeof this.$options.mounted === 'function') {
    this.$options.mounted.call(this);
  }
};

Vue.prototype._proxyData = function() {
  var self = this;
  Object.keys(this._data).forEach(function(key) {
    Object.defineProperty(self, key, {
      get: function() {
        return self._data[key];
      },
      set: function(newValue) {
        self._data[key] = newValue;
        if (typeof self.$options.beforeUpdate === 'function') {
          self.$options.beforeUpdate.call(self);
        }

        self.$render();

        if (typeof self.$options.updated === 'function') {
          self.$options.updated.call(self);
        }
      }
    });
  });
};

Vue.prototype._createComputed = function() {
  var self = this;
  var computed = this.$options.computed || {};

  Object.keys(computed).forEach(function(key) {
    Object.defineProperty(self, key, {
      get: function() {
        return computed[key].call(self);
      }
    });
  });
};

Vue.prototype._createWatchers = function() {
  var self = this;
  var watch = this.$options.watch || {};

  Object.keys(watch).forEach(function(key) {
    var callback = watch[key]
    var value = self._data[key];

    Object.defineProperty(self._data, key, {
      get: function() {
        return value;
      },
      set: function(newValue) {
        var oldValue = value
        value = newValue;
        callback.call(self, newValue, oldValue);
      }
    });
  });
};

Vue.prototype._proxyMethods = function() {
  var self = this;
  var methods = this.$options.methods || {};
  Object.keys(methods).forEach(function(key) {
    self[key] = methods[key].bind(self);
  });
};

Vue.prototype.$emit = function(eventName, payload) {
  this._eventBus.emit(eventName, payload);
};

Vue.prototype.$on = function(eventName, callback) {
  this._eventBus.on(eventName, callback);
};

Vue.prototype._compileTemplate = function() {
  var self = this;
  var el = this.$options.el
  var template = this.$options.template || '';

  var evalExpression = function(expression) {
    with (self) return eval(expression);
  }

  var compiledTemplate = template.replace(/\{\{(.*?)\}\}/g, function(match, expression) {
    var value = evalExpression(expression);
    return value !== undefined ? value : '';
  });

  var element = el ? document.querySelector(el) : document.createElement('div');
  element.innerHTML = compiledTemplate.trim();
  this.$el = el ? element : element.childNodes[0];
  this._handleDirective()
};

Vue.prototype._handleDirective = function() {
  var self = this;

  this.$el.querySelectorAll('[v-model]').forEach(function(element) {
    var value = element.getAttribute('v-model');
    element.value = self._data[value];
    element.addEventListener('input', function(event) {
      self._data[value] = event.target.value;
      self.$emit(`update:${value}`, event.target.value);
    });
  });

  this.$el.querySelectorAll('[v-text]').forEach(function(element) {
    var value = element.getAttribute('v-text');
    element.textContent = self._data[value];
    self.$on(`update:${value}`, function(newValue) {
      element.textContent = newValue;
    });
  });
};

Vue.prototype._proxyComponents = function() {
  var self = this;
  var components = this.$options.components || {};

  Object.keys(components).forEach(function(componentName) {
    var component = self[componentName] || new Vue(components[componentName]);
    var isNewComponent = typeof self[componentName] === 'undefined';
    self[componentName] = component;

    self.$el.querySelectorAll(componentName).forEach(function(element) {
      component.$el.querySelectorAll('slot').forEach(function(slot) {
        slot.innerHTML = element.innerHTML;
      });
      element.innerHTML = component.$el.outerHTML;

      isNewComponent && component.$options?.emits.forEach(function(event) {
        var method = element.getAttribute('v-on:' + event);
        if (typeof self[method] === 'function') {
          component.$on(event, self[method]);
        }
      });
    });
  });
};

In  Vue the constructor, we do several things: handle the lifecycle hook function, create  EventBus an instance, use  _proxyData , _proxyMethods, _createComputed, _createWatchers methods to proxy or bind the properties, methods, computed properties, and listeners of the data object to the Vue instance.

Then call  $mount the method to mount the component, trigger the life cycle hook function and execute  $render the method. In  $render the method, execute a user-defined render function, or use  _compileTemplate the , _proxyComponents method to compile templates and resolve subcomponents.

In  _proxyData the method, we use  Object.defineProperty the properties of the data object to be delegated to  Vue the instance, and  trigger  ,   and  hooks in set the method  of the property  , which means that as long as the properties of the data object change, the view update will be triggered.beforeUpdate$renderupdated

In  the method, we  define a method for each computed property  _createComputed by traversing  the object so that it can be used as a normal property.computedget

In  _createWatchers the method, we traverse  the object, use  the monitor  object for the change of the property in the object watch for each property   , and trigger the callback function when the change occurs. Note: In   the method, we have added new   parameters compared to before.Object.defineProperty_datasetoldValue

In  _proxyMethods the method, we bind the methods in the configuration object to the Vue instance, so that these methods can be directly accessed and called in the instance.

In  Vue the prototype, we define  $emit the and  $on methods. $emit The method is used to throw an event and receives two parameters: event name and optional data payload. $on The method is used to listen to events and receives two parameters: event name and callback function.

In  _compileTemplate the method, we first get the template string in the configuration object and use the regular expression to match  { { expression }} the part. We then use  eval a function to dynamically evaluate the expression, substituting the value back into the template string. el Next, we get the corresponding DOM element  according to the properties in the configuration object  . If the DOM element does not exist, we create an div element instead, and then assign the compiled template string to the  innerHTML property of the element. It then sets  $el properties on the Vue instance and  _handleDirective invokes method handlers. Note: If you use  div an element instead, you need to  childNodes[0] exclude the  div element.

In  _handleDirective the method, we  querySelectorAll get all  v-model elements with attributes through the method, and iterate through each element. During traversal, we parse  model the directive, set the value of the element to the corresponding data attribute value, and add  input event listeners. Note: In  addEventListener the method, compared with the previous ones, we have added  $emit actions to trigger  update:inputValue events, so as to achieve  inputValue complete two-way data binding.

Next, we  querySelectorAll get all  v-text elements with attributes through the method, and iterate over each element. During traversal, we parse  text the directive and set the element's text content to the corresponding data attribute value. Note: Compared with the previous ones, we have added new  $on actions to listen to  update:inputValue events and let the text content  inputValue change with the value of .

In  _proxyComponents the method, we first obtain the component declaration in the configuration object, then traverse all components, obtain the component object according to the component name, and create an  Vue instance of the object. Note: Compared with before, we will save the object to the instance, and get the created object from the instance first. Then  $el traverse all  slot the slots through the properties of the instance, set the original one  innerHTML as the content of the slot, and reset the content of the component  innerHTML as the instance  $el element  outerHTML .

Finally, we also added  v-on the component listening event function. emits First, we get all the event names thrown by the component from the array in the  component configuration object  , and then traverse the array to determine whether the app application has listened to the event. If self[method] the corresponding listener function is found from the app application, the  $on listener function is bound to the component through a method. Note: Since component update will trigger multiple  _proxyComponents methods, it is necessary to determine  isNewComponent whether it is a newly created component to prevent repeated  $on method binding to the same listener function.


Summarize

The above sample codes for simulating the principle of Vue are all written in the way of Vue's Option API, which is different from the current Composition API we commonly use. This may be related to the fact that the ChatGPT version I use can only obtain information before 2021, but this does not prevent us from using it to learn the usage of Vue and understand the principles of Vue.

The content covered in this article is relatively basic. Vue also has many advanced features and usages. We can use AI to guide us in learning. Of course, in the process of learning, we must always pay attention to the fact that the answers of AI are not completely correct, and we need to identify them one by one through practice. In the actual development process of Vue, we can also use AI to locate and analyze problems. After all, it is tireless and has a good temper. It is a rare good teacher.

All source codes and sample projects in this article are on  OpenTiny the site, please visit  GitHub - opentiny/ai-vue

Guess you like

Origin blog.csdn.net/OpenTiny/article/details/131142949