一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情。
今天学习vue源码,跟着老师尝试手写简易的渲染的h函数和mount(没有加载组件,插槽功能)
介绍虚拟节点和h函数
h函数
在vue中,我们想要创建一个dom元素,可以用h函数,他接收三个参数,返回一个对象,也能就是虚拟dom节点
h(tag,obj,string|array)
tag表示想要创建的标签
传入div,span.....
obj是一个对象,可以传入标签的属性和方法
{
class:"scc",
OnClick:function(){}
}
第三个传入字符串或者数组
- 当传入字符串时,字符串便会当作tag的value,渲染到页面
- 当传入数组时,数组可以继续写h函数,为tag的子元素
h函数的实现
const h =(tag,props,children)=>{
return{
tag,
props,
children
}
}
就是这么简单,传入三个值,返回三个值
虚拟节点
虚拟节点是一个对象,他包含三个值,tag,属性|方法,value或者子组件
使用虚拟节点有很多好处,比如他虚拟节点都是对象,进行diff算法节省时间
js操作不需要动dom,好处有很多,欢迎大家补充,一起学习
界面渲染
当虚拟节点创建后,我们需要在dom中挂载,我们创建一个文件raderer文件,用来执行界面渲染
mount函数
当元素挂载时,我们需要他传入两个参数,第一个时挂载的虚拟节点,第二个挂载的元素
count mount = (vnode,container)=>{
// vnode是虚拟节点
// container是需要挂载的dom节点
}
创建dom节点
当我们拿到vnode时,我们需要把他转换成真是dom,用createElement
count mount = (vnode,container)=>{
const el = createElement(vnode.tag) // 我们是需要把h函数的返回,传进来的
}
添加属性和方法
这样,虚拟节点创建完了,下一步给他添加方法和属性,添加方法用addEventListener方法,添加属性用setAttribute,所以我们要先判断是属性还是方法
我们知道传入的方法都是on开头,我们就可以根据这个用startWidth方法
const mount = (vnode,container)=>{
const el = vnode.el = document.createElement(vnode.tag) //创建元素
if(vnode.props){ //因为也可能会传一个null,所以要先判断
for(const key in vnode.props){ // 传递多个属性,方法要用循环
const value = vnode.props[key]
if(key.startsWith("on")){ // 判断是否已on开头
el.addEventListener(key.slice(2).toLowerCase(),value)
} else {
el.setAttribute(key,value)
}
}
}
}
这样,我们就完成了创建dom元素并且把属性和方法给他
添加子元素或value
同样,我们先判断传来的第三个参数是字符串还是数组,如果是数组直接用textContent给节点,如果是数组,就遍历数组添加节点
if(vnode.children){ //同样先判断传进来了吗
if(typeof vnode.children === "string"){ //如果是字符串直接赋值
el.textContent = vnode.children
} else { //不是就递归创建子元素,挂载到el上面
vnode.children.forEach(item => {
mount(item,el)
});
}
}
挂载el
最后一步把el挂载到container上面(container是我们传来的节点)
container.appendChild(el)
完整代码
渲染代码
const h =(tag,props,children)=>{
return{
tag,
props,
children
}
}
const mount = (vnode,container)=>{
const el = vnode.el = document.createElement(vnode.tag)
if(vnode.props){
for(const key in vnode.props){
const value = vnode.props[key]
if(key.startsWith("on")){
el.addEventListener(key.slice(2).toLowerCase(),value)
} else {
el.setAttribute(key,value)
}
}
}
if(vnode.children){
if(typeof vnode.children === "string"){
el.textContent = vnode.children
} else {
vnode.children.forEach(item => {
mount(item,el)
});
}
}
container.appendChild(el)
}
html代码
<body>
<div id="app"></div>
<script src="./raderer.js"></script>
<script>
const vnode = h("div",{class:"scc"},[
h("span",null,"当前计数:100"),
h("button",{onClick:function(){console.log(+1);}},"+1"),
h("button",{onClick:function(){console.log(-1);}},"-1")
])
mount(vnode,document.querySelector("#app"))
</script>
</body>
界面展示
结束语
大家觉得有用记得点赞,码字不易