源码实现
数据劫持
vue3.x中使用了ES6新的语法proxy和reflect相结合进行数据劫持拦截,性能上有很大的提升,而且支持劫持数组
//vue3.x写法export default {setup(){const state = reactive({...});}}
//项目目录:├─package.json├─Readme.md├─webpack.config.js├─vue3| ├─shared| | └utils.js - 工具函数isObject/hasOwnPropery/isEqual| ├─reactivity| | ├─index.js - 响应式出口文件| | ├─mutableHandler.js - 管理handler底下的底层Object对象方法做响应式处理/新增业务逻辑编写| | └reactive.js - 实例化一个Proxy对象返回一个被代理后的响应式对象├─src| ├─index.html| └index.js├─public| └index.html
源码地址:https://gitee.com/kevinleeeee/data-hijacked-vue3.x-demo
视图绑定
案例:实现视图数据绑定
技术:vite/数据响应式
功能缺少:
AST树- 虚拟
dom - 渲染函数
- 依赖收集
功能:
- 编译视图模板里的事件绑定属性
- 关联
methods定义的方法和视图事件处理函数的绑定 - 编译视图模板绑定的变量以及标签属性
- 给
state数据发生更改时更新页面视图
//项目目录:├─src| └app.js├─reactivity| ├─index.js 出口文件/对象响应式/视图绑定/事件/状态| ├─render.js 视图绑定/绑定属性/绑定事件函数/更新视图函数| ├─shared| | └utils.js 工具函数| ├─reactive| | ├─index.js 对象响应式/创建响应式对象| | └mutableHandler.js 管理handler底下的底层Object对象方法| ├─compiler| | ├─event.js 绑定视图的处理函数| | └state.js 绑定视图的状态变量函数
问题:如何实现编译带事件处理函数绑定视图的标签?
- 编译视图模板时匹配
onClick="xxx"格式 - 随机生成一个字符串保存在定义
flag标识属性 - 组装一个新的对象包含
flag,handler,type属性 - 匹配
xxx内容后,将其内容保存到handler属性里 - 将组装好的对象存入事件池数组
eventPool里 - 匹配
onClick="xxx"格式最后替换为`data-dom=${_flag}的字符串实现在视图里 - 实现编译标签时
<div onClick="add(2)"></div>用一个标识替换原有的绑定句柄onClick="add(2)",如data-dom="1640242607185"
问题:如何关联视图上绑定的事件处理函数属性和用户methods上定义的方法?
- 假如视图上绑定
onClick="add(2)" - 假如用户在
methods定义的方法为add(){...} - 拿到页面所有的节点
- 遍历事件池里面的对象和所有节点找到符合标识属性
flag条件的那一项 - 给该项添加绑定事件处理函数
addEventListener - 找到
methods里定义的方法名和参数 - 执行定义在
methods里的对应的方法 - 实现视图和方法的关联
问题:如何关联视图上绑定的变量属性?
- 替换视图模板为用户定义标签名的标签
<myDiv></myDiv> - 给标签新增自定义属性
data-dom="1640273806381" - 替换视图模板里
{{}}为用户定义的state数据 - 缓存状态池
//缓存状态池包含标识flag和变量名数组varArr属性/*** console.log(statePool);* [* {* flag: 1640277702567,* state: ['count']* }* ]*/
问题:视图更新时,如何找到绑定变量数据更改时所在的节点?
- 在
setter函数里新增update方法实现state数据发生更改时更新视图 - 在
render文件里定义update方法 - 遍历所有的元素节点和状态池
- 当状态池最后一项和
state数据的key的变量名一样时 - 当元素节点带有
data-dom的标识跟状态池的的标识字符串一致时 - 对
state数据发生更改的那个节点的文本内容进行更新 - 实现更新
state数据时,页面内容实时发生更改
源码地址:https://gitee.com/kevinleeeee/compile-template-vue3-vite-demo
vue-loader
手写webpack实现vue-loader驱动和vue轮子(非虚拟dom)
实现一个加载器,来解析
.vue单文件组件里的模板和样式和脚本实现
v-if/v-show
通过一个程序将.vue转为.js,并且将es6转为es5并跑在node服务器上,
node的文件操作可以完成js,css文件的创建
最终形成一个完整的html引入script,style
webpack的运行过程:
resolve: {extensions: ['.js', '.vue']}处理js,vue后缀文件在模块规则定义的
loader里调用相应的vue-loader(这里的vue-loader是自定义的非vue的),自定义的vue-loader最终返回- 希望处理的结果:
- 实现
import引入的style标签内容引入到html里 - 实现
script标签的内容作为一个组件对象导出使用
- 处理
css文件调用相应的style-loader,css-loader - 将处理好的结果放入到插件
HtmlWebpackPlugin里 - 打包
js/css后放到目录/dist里
自定义一个vue-loader加载器:
当webpack处理vue后缀文件时会执行这个加载器,这个加载器会返回希望处理后的结果
function vueLoader(source) {//source打印的是App.vue里写的代码字符串/*** console.log(source);* <template><div><div><img v-if="isShowImg1" class="img" src="https://gimg2.baidu.com/image_search...=jpeg?sec=1649504657&t=2a48eec8e0e23a2192c960e60ce27b7f" /><img v-show="isShowImg2" class="img" src="https://gimg2.baidu.com/image_search/src...=jpeg?sec=1649504684&t=b8e0aa845d744916020406fa30d6217e" /></div><button @click="showImg1">Show Image 1</button><button @click="showImg2">Show Image 2</button></div></template><script>export default {name: 'App',data () {return {isShowImg1: true,isShowImg2: true}},methods: {showImg1 () {this.isShowImg1 = !this.isShowImg1;},showImg2 () {this.isShowImg2 = !this.isShowImg2;}}}</script><style>.img {width: 200px;}</style>*/return '123';}
目标:希望把字符串的内容转变为vue单文件组件写法的结构
- 提取
template标签内容 - 提取
script标签内容 - 提取
style标签内容 - 定义一个临时存放的样式文件并写入样式代码
- 将
import引入的路径返回实现html引入style标签 - 将
script标签的字符串返回组件对象
目标:希望编写vue里的createApp方法实现创建一个组件实例
- 定义
vm对象 - 挂载
component对象里的data,methods,template属性到vm实例 - 挂载
dom节点(template里定义的内容)到vm实例
目标:希望实现数据响应式和访问属性代理
通过访问和修改vm实例的属性时,代理Object.defineProperty的get/set更改this访问返回的属性
目标:希望定义两个Map数据类型的池子
- 池子1(
propsPool):保存的节点和类型(v-if/v-show)和绑定事件方法名称 - 池子2(
eventsPool):保存节点和事件类型(click/change/...)和绑定事件的处理函数方法(handler)
propsPool格式为:Map(2) {img.img => {type: 'v-if', prop: 'isShowImg1'},img.img => {type: 'v-show', prop: 'isShowImg2'}}eventsPool格式为:Map(2) {button => {type: 'click', handler: 'showImg1'},button => {type: 'click', handler: 'showImg2'}}
目标:希望根据eventPool的事件类型绑定事件处理函数
给实例挂载访问this.name = handler函数
目标:希望实现v-if和v-show的显示和隐藏效果
- 注释节点替换的方式来实现
v-if display: none的方式来实现v-show- 在
setter里如果数据发送更改时触发update函数
项目目录:
├─package.json├─webpack.config.js├─__temp - vue-loader驱动里对样式文件写入的临时目录存放临时样式文件| ├─css| | └__1647282035869.css├─src| ├─App.vue - vue单文件组件| └main.js - vue入口文件├─public| └index.html├─modules| ├─vue-loader - vue驱动实现一个单文件vue组件和样式的import引入| | └index.js| ├─vue - vue轮子/实例挂载/数据响应式/事件绑定/v-if/v-show指令实现| | ├─event.js - 处理绑定事件处理函数的方法| | ├─index.js - 实例入口文件/创建实例/创建DOM/挂载DOM| | ├─pools.js - map数据类型的池绑定节点和类型| | ├─propsType.js - 标识文件| | ├─reactive.js - 数据响应式getter和setter| | └render.js - 渲染函数/更新函数
源码地址: https://gitee.com/kevinleeeee/vue-loader-webpack-vshow-vif-demo
setup方法
//2.0写法data(){...},methods:{...}//3.0写法//创建引用import { ref } from 'vue';export default{//函数方式创建默认值为数值0const count = ref(0);const plus = () => { count.value --; }//注意:3.0注册的属性和方法必须通过return返回出去return {count,plus}}
//3.0写法:逻辑方法单独提取出去const useCompute = (count) => {const plus = () => { count.value --; }return {plus}}export default{const count = ref(0);const { plus } = useCompute(count);return {count,plus}}
