手写MVVM框架-实现v-model(单向绑定)
开始之前我们先修复一下之前的一个BUG、由于我们使用了html-webpack-plugin并且配置了html模板,所以在html模板里面就不需要再引用打包后的bundle.js了,webpack打包时会自动添加上去,不然的话就会导致调用两遍初始化方法!
修改了index页面的UI并且删除了手动引用的bundle.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box
}
#container {
width: 100%;
height: 100vh;
display: flex;
align-items: center;
flex-direction: column;
padding-top: 20%
}
.inputItems {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
margin-top: 50px;
}
.inputItems .inputItem {
border: 1px solid #ccc;
padding: 5px 10px;
width: 80%;
border-radius: 23px;
margin-top: 30px;
height: 40px;
display: flex;
align-items: center;
}
.inputItems .inputItem input {
border: none;
outline: none;
text-align: center;
width: 100%;
}
.fixInfo {
position: fixed;
bottom: 3%;
color: #878787;
font-size: 12px;
}
button {
width: 80%;
background-color: #eacc20;
height: 40px;
border-radius: 23px;
margin-top: 50px;
}
</style>
</head>
<body>
<div id="container">
<div>{
{title}}</div>
<div class='inputItems'>
<div class='inputItem'>
<input v-model="user.account" placeholder='请输入登录账号'/>
<div>{
{user.account}}</div>
</div>
<div class='inputItem'>
<input v-model="user.password" placeholder='请输入登录密码'/>
<div>{
{user.password}}</div>
</div>
</div>
<button>登录</button>
<div class='fixInfo'>{
{info.name}} {
{info.version}}</div>
</div>
</body>
</html>
现在的效果是这样
现在开始实现v-model
实现双向绑定的原理就是构建虚拟dom时,检查当前节点是否含有v-model节点,如果有当前v-model的话就给这个输入标签添加一个input事件或者change事件
我们现在创建一个directive文件夹并且添加一个vmodel.js
import { setObjectValue } from '../../utils/ObjectUtils.js'
/**
* 注册v-model指令
*/
export function VModel(vm, node, key) {
// 如果不是元素节点直接返回
if(node.nodeType != 1) return;
// 监听元素的onchange事件
node.oninput = function(e) {
setObjectValue(vm, key, node.value)
}
}
并且添加一个ObjectUtils.js 用于给data中的属性设置值
/**
* 设置Object中的值
* @param {Object} obj
* @param {String} key a, obj.a
*/
export function setObjectValue(obj, keys, value) {
if(!obj) return obj
let keyList = keys.split('.')
let temp = obj;
for(let i = 0; i < keyList.length -1; i++) {
if(temp[keyList[i]]) {
temp = temp[keyList[i]]
}
}
if(temp[keyList[keyList.length -1]] != null) {
temp[keyList[keyList.length -1]] = value
}
}
第二步:input事件添加v-model解析
我们在构建虚拟节点的时候,检测节点是否有v-model属性,检测到了之后就添加调用v-model,当前代码只支持input的双向绑定,如果需要其他标签,请自行添加
/**
* 构建虚拟dom
* @param {} vm
* @param {} el
* @param {} parent
*/
function constructVnode(vm, el, parent) {
analysisNodeAttr(vm, el)
...其他代码
}
/**
* 解析Node属性值
* @param {*} vm
* @param {*} vnode
* @returns
*/
function analysisNodeAttr(vm, node) {
if(node.nodeType != 1) return
let attrList = node.getAttributeNames();
if(attrList.includes("v-model")) {
VModel(vm, node, node.getAttribute("v-model"));
}
}
现在我们访问页面看效果
从页面上可以看到:
1.我们输入账号密码之后,控制台输出了,说明v-model添加的input事件已经失效
2.账号密码框右侧的模板发生了变化,说明新输入的值已经渲染到了页面上
本章总结:
1.收集依赖时,添加分析元素节点(nodeType == 1)的代码,并添加模板的依赖
2.创建directive文件和vmodel.js,给元素绑定input事件,并修改实例的值
3.构建虚拟dom时,如果是元素节点并且有v-model属性,给调用vmodel方法