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的核心概念和高级主题:
- HTML, CSS, and JavaScript Basics
- HTML elements and tags
- CSS selectors and styles
- JavaScript Basic Syntax and Concepts
- 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
- 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
- Vue-routing
- Installation and configuration of Vue Router
- Basic usage of routing (navigation, parameter passing)
- Nested and named routes
- Routing guards and navigation analysis
- Vue state management (Vuex)
- The core concepts and usage scenarios of Vuex
- Vuex installation and configuration
- State、Mutation、Action和Getter
- Modularity and Namespaces
- Advanced Vue
- custom directive
- Filter
- Mixin
- Plugins
- Custom transitions and animations
- Server Side Rendering (SSR)
- 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
- 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
- Vue test
- Concepts of unit testing and end-to-end testing
- Unit testing with Vue Test Utils
- End-to-end testing with Cypress
- 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.message
data
message
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 objectdata
, 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 toVue
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.beforeMount
mounted
$mount
render
In
_proxyData
the method, we useObject.defineProperty
the properties of the data object to be delegated toVue
the instance, and trigger , and hooks inset
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
render
updated
In
render
the method, we judge whetherrender
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 elementinnerHTML
, where$el
the element$mount
is initialized in the methoddocument.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-model
v-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 useeval
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 objectel
, and assign the compiled template string to theinnerHTML
property of the element.Then, we
querySelectorAll
get allv-model
elements with attributes via method and iterate over each element. During traversal, we parsemodel
the directive, set the element's value to the corresponding data attribute value, and addinput
event listeners forinputValue
simple two-way binding.Next, we
querySelectorAll
get allv-text
elements with attributes through the method, and iterate over each element. During traversal, we parsetext
the directive and set the element's text content to the corresponding data attribute value. Note: We have not implementedinputValue
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 browserindex.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 iseval(expression)
, whichexpression
is a string" message "
, please note thatmessage
there are spaces on both sides. The value obtained by directly callingeval
the functionmessage
will report an error, because there is no attribute defined in the global objectmessage
, and{ { message }}
the original intention of the expression is to obtain the attribute valuethis._data
of the objectmessage
. To this end, I added aevalExpression
function to set the global object throughwith
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._data
this._data.message
with
-
The second problem is that ChatGPT wants to use the content of
querySelectorAll('[v-]')
simultaneous acquisitionv-model
andv-text
instruction, but it cannot be acquired during the actual debugging process. So I disassembled it into two statements:querySelectorAll('[v-model]')
andquerySelectorAll('[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 fullName
and 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 containingdata
, ,computed
and .watch
In
_proxyData
the method, we use the methods definedObject.defineProperty
for the properties on the instance to delegate to the object.Vue
get
set
_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.computed
get
In
_createWatchers
the method, we traverse the object, use the monitor object for the change of the property in the objectwatch
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
Vue
and 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 haveon
methods for listening to events andemit
methods for firing events.In
Vue
the constructor, we create anEventBus
instance and assign it to_eventBus
properties.In
_proxyData
the method, we use toObject.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 existingVue
configuration objectgreet
uses$emit
the method triggergreet
event to trigger the callback function and output the message.$on
Then, use methods to listen to events in the instancegreet
, 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 containingdata
, ,template
and .components
In
_proxyData
the method, we use toObject.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 useeval
a function to dynamically evaluate the expression, substituting the value back into the template string. Note: Hereeval
the previouswith
statement followsself
, not the previousself._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 andiv
element instead, and then assign the compiled template string to theinnerHTML
property of the element. Then set$el
the properties for the Vue instance. Note: If you usediv
an element instead, you need tochildNodes[0]
exclude thediv
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 ofVue
the$el
componentslot
asinnerHTML
theinnerHTML
instance$el
elementouterHTML
.Finally, we define an
HelloComponent
object first, and add a slot to the component's templateslot
. Then create anotherVue
instance, introduce the component in the template of the configuration objectHelloComponent
, 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.updated
greet
firstName
- In the template of the app application, the event of
<HelloComponent v-on:greet="greetMessage">
the listeningHelloComponent
component is declaredgreet
, and the event will trigger the method in the configuration object of the app applicationgreetMessage
. - 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.greet
app.$on('greet', ...)
greet
firstName
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, createEventBus
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 useObject.defineProperty
the properties of the data object to be delegated toVue
the instance, and trigger , and hooks inset
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
$render
updated
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.computed
get
In
_createWatchers
the method, we traverse the object, use the monitor object for the change of the property in the objectwatch
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
_data
set
oldValue
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 useeval
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 andiv
element instead, and then assign the compiled template string to theinnerHTML
property of the element. It then sets$el
properties on the Vue instance and_handleDirective
invokes method handlers. Note: If you usediv
an element instead, you need tochildNodes[0]
exclude thediv
element.In
_handleDirective
the method, wequerySelectorAll
get allv-model
elements with attributes through the method, and iterate through each element. During traversal, we parsemodel
the directive, set the value of the element to the corresponding data attribute value, and addinput
event listeners. Note: InaddEventListener
the method, compared with the previous ones, we have added$emit
actions to triggerupdate:inputValue
events, so as to achieveinputValue
complete two-way data binding.Next, we
querySelectorAll
get allv-text
elements with attributes through the method, and iterate over each element. During traversal, we parsetext
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 toupdate:inputValue
events and let the text contentinputValue
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 anVue
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 allslot
the slots through the properties of the instance, set the original oneinnerHTML
as the content of the slot, and reset the content of the componentinnerHTML
as the instance$el
elementouterHTML
.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. Ifself[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 determineisNewComponent
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