Vue源码你读过吗?
Vue的响应式原理是什么?
Vue是怎么收集依赖的?
Vue到底是怎么把数据渲染到页面上去的?
这些问题网上有很多答案,并且源码第几行都有人写出来,但是我发现我还是无法直接看懂源码,感觉自己看懂了,但又似懂非懂。
这种状态直到我开始着手自己手写mini-vue,才慢慢悟了。
废话不多说,直接进入正题。如果你对vue源码还处于一知半解,可以顺着我的思路,让我们一点点的揭开Vue的面纱。
每个分支完成一个小功能,根据自身情况从不同的分支看起。
正文开始
V0.0.1 响应式原理入门
先简单实现一个小功能,一个数字,一个+1按钮,点击按钮数字+1
要求:
- 要使用Object.defineProperty完成DOM更新
很简单吧,几分钟就能搞定,快动手试试。
js
// 初始化app
const app = document.createElement('div')
const h2 = document.createElement('h2')
const button = document.createElement('button')
const data = {
count: 0
}
const plus = function(){
this.count++
}
h2.innerText = data.count
button.innerText = '+1'
button.addEventListener('click', plus.bind(data))
let val = data.count
Object.defineProperty(data, 'count', {
get: function(){
return val
},
set: function(newVal){
val = newVal
h2.innerText = val
}
})
app.append(h2)
app.append(button)
document.body.append(app)然后再增加点难度,按照Vue的写法来实现,也就是使用new Vue() 创建一个vue对象
为了方便后续编码,我们这里先简单规划下项目的一个结构
- 新建一个文件夹mini-vue, 或者你自己随便起名,都无所谓的
- 在mini-vue文件夹下,执行
bash
npm init -y- 安装一下http-server, 并修改下package.json 中的执行脚本
bash
npm i http-serverjson
// package.json
... 前后我就省略啦,只改这部分就行
"scripts": {
"dev": "http-server -p 13003"
},
...- 新建一个index.html文件
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>nkxrb mini-vue</title>
<style>
#app {
text-align: center;
padding-top: 10%;
}
button {
cursor: pointer;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/example/index.js"></script>
</body>
</html>- 新建一个example文件夹,里面再新增一个index.js文件
js
/**
* 遵循Vue2.0的写法,new 一个Vue对象,并挂载到#app节点上
*/
import Vue, { App } from '../src/vue.js'
new Vue(App).$mount('#app')- 新建一个src文件夹,里面新增一个vue.js文件
js
const h2 = document.createElement('h2')
const button = document.createElement('button')
export const App = {
data: function () {
return {
count: 0
}
},
methods: {
plus () {
this.count++
}
},
render () {
// 简易的渲染函数,直接操作真实dom
// 下列代码将生成一个
/**
* <div>
* <h2>0</h2>
* <button>+1</button>
* </div>
*/
const app = document.createElement('div')
// 将count值渲染到h2标签中
h2.innerText = this.count
button.innerText = '+1'
// 给button绑定点击事件,并利用bind将事件的this指向调用render函数的this对象
button.addEventListener('click', this.plus.bind(this))
app.append(h2)
app.append(button)
return app
}
}
/**
* 使用函数式写法,声明一个Vue类
* @param app 此处传入的app, 为了简单,已经转换成了一个对象,详情将/example/app.js
* @returns 返回值需要包含一个mount函数,用于将节点挂载到页面上去
*/
function Vue (app) {
// 因为vue中data是函数,此处需要执行data()来获取app中声明的对象
const data = app.data()
// 将data转换为响应式的
abserver(data)
// 将data,methods合并,并一起放入vm中,注意此处的vm===data, 这样可以保证响应式生效
const vm = Object.assign(data, app.methods)
// 执行app的render函数,得到最终渲染的dom对象
const root = app.render ? app.render.call(vm) : ''
const $mount = selector => {
// 找到#app元素
const page = document.querySelector(selector) || document.body
// 先清空里面的内容
page.innerHTML = ''
// 将上面app生成的dom对象,放入page中
page.append(root)
}
// new Vue对象,返回一个$mount函数,用来将生成的元素挂载到 #app DOM上
return {
$mount
}
}
/**
* 为对象的每个属性添加响应
* @param {*} data
*/
function abserver (data) {
for (let key in data) {
// 在外边定义一个值用来存储值,此处可看作是一个闭包应用
let val = data[key]
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
// 此处应该是用来依赖收集的,等到v0.0.2再详细设计
return val
},
set: function (newVal) {
val = newVal
// 此处应该是用来更新所有依赖的,等到v0.0.2再详细设计
h2.innerText = newVal
}
})
}
}
export default Vue如果你已经按照上面做完了,就可以启动服务看效果了
bash
npm run dev主要逻辑都在vue.js中,代码注释已经很详细了,写的都比较简单,如果你都看懂了。甚至自己都能很快写出来一样的了,那就开始往下学: v0.0.2 模块化与响应式的依赖收集
如果觉得不错,欢迎点赞关注收藏,mini-vue系列文章持续更新中...
