vue3源码解读(入门)
尤雨溪大神解读哔哩哔哩教程视频内容中的代码:(请结合视频学习,每一部分代码可在浏览器中运行调试)1、渲染函数小案例<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initia
·
视频内容中的代码:(请结合视频学习,每一部分代码可在浏览器中运行调试)
1、渲染函数小案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>渲染函数演示</title>
</head>
<style>
.mt-10{
margin-left: 10px
}
</style>
<script src="https://unpkg.com/vue@next"></script>
<body>
<div id="app"></div>
</body>
<script>
const { createApp,h } = Vue
const Stack = {
props:{
size:{
type:String,
default:''
}
},
setup(props,{ slots }){
const slot = slots.default?slots.default():[]
return ()=>h(
'div',
{ class:'stack' },
slot.map(child=>{
return h(
'div',
{ class:`mt-${props.size}` },
[ child ]
)
})
)
}
}
const App = {
components:{
Stack
},
template:`
<Stack size="10">
<div>hello</div>
<Stack size="10">
<div>hello</div>
<div>hello</div>
</Stack>
</Stack>
`
}
createApp(App).mount('#app')
</script>
</html>
2、渲染函数挂载原理
<div id="app"></div>
<style>
.blue{color: blue}
.red{color: red}
</style>
<script>
// h函数将参数整合到一个对象返回
function h(tag,props,children){
return {
tag,
props,
children
}
}
// 挂载函数对虚拟Dom进行解析
function mount(vnode,container){
const el = vnode.el = document.createElement(vnode.tag)
// 将属性添加到元素节点
if(vnode.props){
Object.keys(vnode.props).forEach(key=>{
el.setAttribute(key,vnode.props[val])
})
}
/*
*解析子节点
*当子节点为字符串时直接将子节点设置为文本节点
*当子节点为数组时,遍历子节点数组执行mount函数
*/
if(vnode.children){
if(typeof vnode.children === 'string'){
el.textContent = vnode.children
}else{
vnode.children.forEach(child=>{
mount(child,el)
})
}
}
// 将节点树插入到根节点
container.appendChild(el)
}
//虚拟dom树
const vDom = h(
'div',
{ class:'red' },
[
h(
'div',
null,
'hello'
)
]
)
// vDmo实际上经过h函数后返回一个对象树
// const vnode = {
// tag:"div",
// props:{class:'red'},
// children:[{
// tag:"div",
// props:null,
// children:[
// "hello"
// ]}
// ]
// }
mount(vDom,document.getElementById('app'))
// dom更新函数(对比虚拟dom)
function patch(n1,n2){
if(n1.tag === n2.tag){ //当标签相同时
// 对比props
const el = n2.el = n1.el //保存元素节点信息,用于更新快照
const oldProps = n1.props || {}
const newProps = n2.props || {}
for(key in newProps){
const oldValue = oldProps[key]
const newValue = newProps[key]
if(newValue !== oldValue){
el.setAttribute(key,newValue)
}
}
for(key in oldProps){
if(!(key in newProps)){
el.removeAttribute(key)
}
}
// 对比children
const oldChildren = n1.children
const newChildren = n2.children
// 新的子节点是一个字符串
if(typeof newChildren === 'string'){
if(typeof oldChildren === 'string'){
if(newChildren !== oldChildren){
el.textContent = newChildren
}
}else{
el.textContent = newChildren
}
}else{
/*
* 新的子节点是一个数组
* 旧的子节点是一个字符串
* 将元素的innerHtml置空,遍历调用mount函数解析虚拟dom
*/
if(typeof oldChildren === 'string'){
el.innerHtml = ''
newChildren.forEach(child=>{
mount(child,el)
})
}else{
/*
* 假设元素的key不改变(即顺序不变的情况下)
* 新的子节点是一个数组
* 旧的子节点是一个数组
* 当旧的子节点数组比新的子节点数组长度大,则移除多余部分的长度
* 当旧的子节点数组比新的子节点数组长度小,则遍历调用mount函数解析多的部分借点为虚拟dom
*/
const commonLength = Math.min(oldChildren.length,newChildren.length)
for(let i = 0 ; i < commonLength ; i++){
patch(oldChildren[i],newChildren[i])
}
if(oldChildren.length > newChildren.length){
oldChildren.slice(newChildren.length).forEach(child=>{
el.removeChild(child.el)
})
}
if(oldChildren.length < newChildren.length){
newChildren.slice(oldChildren.length).forEach(child=>{
mount(child,el)
})
}
}
}
}else{
// replace
// mount(n2,n1.el)
}
}
// 更新后的的虚拟dom
const vDom2 = h(
'div',
{ class:'blue' },
[
h(
'div',
null,
'changed!'
)
]
)
// 执行dom更新函数
patch(vDom,vDom2)
</script>
3、响应式原理
<script>
// 全局变量存储effect
let activeEffect
class Dep{
constructor(value){
this.subscribers = new Set()
this._value = value
}
get value(){
this.depend()
return this._value
}
set value(newValue){
this._value = newValue
this.notify()
}
// 添加依赖
depend(){
if(activeEffect){
this.subscribers.add(activeEffect)
}
}
// 通知
notify(){
this.subscribers.forEach(effect => {
effect()
})
}
}
function watchEffect(effect){
activeEffect = effect
effect()
activeEffect = null
}
const dep = new Dep('hello')
watchEffect(()=>{
console.log(dep.value)
})
dep.value = 'changed!'
</script>
4、响应式代理模式简单实现
<script>
let activeEffect
class Dep{
subscribers = new Set()
// 添加依赖
depend(){
if(activeEffect){
this.subscribers.add(activeEffect)
}
}
// 通知
notify(){
this.subscribers.forEach(effect => {
effect()
})
}
}
function watchEffect(effect){
activeEffect = effect
effect()
activeEffect = null
}
const targetMap = new WeakMap()
function getDep(target,key){
let depsMap = targetMap.get(target)
if(!depsMap){
depsMap = new Map()
targetMap.set(target,depsMap)
}
let dep = depsMap.get(key)
if(!dep){
dep = new Dep()
depsMap.set(key,dep)
}
return dep
}
// const dep = new Dep()
// 设置响应式代理
const reactiveHandlers = {
get(target,key,receiver){
const dep = getDep(target,key)
dep.depend()
// return target[key]
return Reflect.get(target,key,receiver)
},
set(target,key,value,receiver){
const dep = getDep(target,key)
const result = Reflect.set(target,key,value,receiver)
dep.notify()
return result
}
}
// 响应式代理入口函数
function reactive(raw){
return new Proxy(raw,reactiveHandlers)
}
// 初始化对象设置代理模式
const state = reactive({
count:0
})
watchEffect(()=>{
console.log(state.count)
})
state.count++
</script>
5、mini-vue项目
<div id="app"></div>
<script>
// h函数将参数整合到一个对象返回
function h(tag,props,children){
return {
tag,
props,
children
}
}
// 挂载函数对虚拟Dom进行解析
function mount(vnode,container){
const el = vnode.el = document.createElement(vnode.tag)
if(vnode.props){
Object.keys(vnode.props).forEach(val=>{
if(val.startsWith('on')){
el.addEventListener(val.slice(2).toLocaleLowerCase(),vnode.props[val])
}else{
el.setAttribute(val,vnode.props[val])
}
})
}
if(vnode.children){
if(typeof vnode.children === 'string'){
el.textContent = vnode.children
}else{
vnode.children.forEach(child=>{
mount(child,el)
})
}
}
container.appendChild(el)
}
// dom更新函数
function patch(n1,n2){
if(n1.tag === n2.tag){
// 对比props
const el = n2.el = n1.el
const oldProps = n1.props || {}
const newProps = n2.props || {}
for(key in newProps){
const oldValue = oldProps[key]
const newValue = newProps[key]
if(newValue !== oldValue){
el.setAttribute(key,newValue)
}
}
for(key in oldProps){
if(!(key in newProps)){
el.removeAttribute(key)
}
}
// 对比children
const oldChildren = n1.children
const newChildren = n2.children
// 新的子节点是一个字符串
if(typeof newChildren === 'string'){
if(typeof oldChildren === 'string'){
if(newChildren !== oldChildren){
el.textContent = newChildren
}
}else{
el.textContent = newChildren
}
}else{
/*
* 新的子节点是一个数组
* 旧的子节点是一个字符串
* 将元素的innerHtml置空,遍历调用mount函数解析虚拟dom
*/
if(typeof oldChildren === 'string'){
el.innerHtml = ''
newChildren.forEach(child=>{
mount(child,el)
})
}else{
/*
* 假设元素的key不改变(即顺序不变的情况下)
* 新的子节点是一个数组
* 旧的子节点是一个数组
* 当旧的子节点数组比新的子节点数组长度大,则移除多余部分的长度
* 当旧的子节点数组比新的子节点数组长度小,则遍历调用mount函数解析多的部分借点为虚拟dom
*/
const commonLength = Math.min(oldChildren.length,newChildren.length)
for(let i = 0 ; i < commonLength ; i++){
patch(oldChildren[i],newChildren[i])
}
if(oldChildren.length > newChildren.length){
oldChildren.slice(newChildren.length).forEach(child=>{
el.removeChild(child.el)
})
}
if(oldChildren.length < newChildren.length){
newChildren.slice(oldChildren.length).forEach(child=>{
mount(child,el)
})
}
}
}
}else{
// replace
}
}
let activeEffect
class Dep{
subscribers = new Set()
// 添加依赖
depend(){
if(activeEffect){
this.subscribers.add(activeEffect)
}
}
// 通知
notify(){
this.subscribers.forEach(effect => {
effect()
})
}
}
function watchEffect(effect){
activeEffect = effect
effect()
activeEffect = null
}
const targetMap = new WeakMap()
function getDep(target,key){
let depsMap = targetMap.get(target)
if(!depsMap){
depsMap = new Map()
targetMap.set(target,depsMap)
}
let dep = depsMap.get(key)
if(!dep){
dep = new Dep()
depsMap.set(key,dep)
}
return dep
}
// 设置响应式代理
const reactiveHandlers = {
get(target,key,receiver){
const dep = getDep(target,key)
dep.depend()
// return target[key]
return Reflect.get(target,key,receiver)
},
set(target,key,value,receiver){
const dep = getDep(target,key)
const result = Reflect.set(target,key,value,receiver)
dep.notify()
return result
}
}
// 响应式代理入口函数
function reactive(raw){
return new Proxy(raw,reactiveHandlers)
}
const App = {
data:reactive({
count:0
}),
render(){
return h(
'div',
{
onClick:()=>{
this.data.count++
}
},
String(this.data.count)
)
}
}
function mountApp(component,container){
let isMount = false
let prevVdom
watchEffect(()=>{
if(!isMount){
prevVdom = component.render()
mount(prevVdom,container)
isMount = true
}else{
const newVdom = component.render()
patch(prevVdom,newVdom)
prevVdom = newVdom
}
})
}
mountApp(App,document.getElementById('app'))
</script>
本文章代码来源于原视频中的讲解,仅供学习使用!!!
更多推荐
已为社区贡献1条内容
所有评论(0)