vue3源码解读(入门)

时间:2025-04-07 16:30:08

尤雨溪大神解读哔哩哔哩教程

视频内容中的代码:(请结合视频学习,每一部分代码可在浏览器中运行调试)

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="/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 = ?():[]
      return ()=>h(
        'div',
        { class:'stack' },
        (child=>{
          return h(
            'div',
            { class:`mt-${}` },
            [ 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 =  = ()
    // 将属性添加到元素节点
    if(){
      ().forEach(key=>{
        (key,[val])
      })
    }
    /*
    *解析子节点
    *当子节点为字符串时直接将子节点设置为文本节点
    *当子节点为数组时,遍历子节点数组执行mount函数
    */
    if(){
      if(typeof  === 'string'){
         = 
      }else{
        (child=>{
          mount(child,el)
        })
      }
    }
    // 将节点树插入到根节点
    (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,('app'))

  // dom更新函数(对比虚拟dom)
  function patch(n1,n2){
    if( === ){ //当标签相同时
      // 对比props
      const el =  =  //保存元素节点信息,用于更新快照
      const oldProps =  || {}
      const newProps =  || {}
      for(key in newProps){
        const oldValue = oldProps[key]
        const newValue = newProps[key]
        if(newValue !== oldValue){
          (key,newValue)
        }
      }
      for(key in oldProps){
        if(!(key in newProps)){
          (key)
        }
      }

      // 对比children
      const oldChildren = 
      const newChildren = 
      // 新的子节点是一个字符串
      if(typeof newChildren === 'string'){
        if(typeof oldChildren === 'string'){
          if(newChildren !== oldChildren){
             = newChildren
          }
        }else{
           = newChildren
        }
      }else{
        /*
          * 新的子节点是一个数组
          * 旧的子节点是一个字符串
          * 将元素的innerHtml置空,遍历调用mount函数解析虚拟dom
        */
        if(typeof oldChildren === 'string'){
           = ''
          (child=>{
            mount(child,el)
          })
        }else{
          /*
            * 假设元素的key不改变(即顺序不变的情况下)
            * 新的子节点是一个数组
            * 旧的子节点是一个数组
            * 当旧的子节点数组比新的子节点数组长度大,则移除多余部分的长度
            * 当旧的子节点数组比新的子节点数组长度小,则遍历调用mount函数解析多的部分借点为虚拟dom
          */  
          const commonLength = (,)
          for(let i = 0 ; i < commonLength ; i++){
            patch(oldChildren[i],newChildren[i])
          }
          if( > ){
            ().forEach(child=>{
              ()
            })
          }
          if( < ){
            ().forEach(child=>{
              mount(child,el)
            })
          }
        }
      }
    }else{
      // replace
      // mount(n2,)
    }
  }

  // 更新后的的虚拟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){
       = new Set()
      this._value = value
    }
    get value(){
      ()
      return this._value
    }

    set value(newValue){
      this._value = newValue
      ()
    }
    // 添加依赖
    depend(){
      if(activeEffect){
        (activeEffect)
      }
    }

    // 通知
    notify(){
      (effect => {
        effect()
      })
    }
  }

  function watchEffect(effect){
    activeEffect = effect
    effect()
    activeEffect = null
  }

  const dep = new Dep('hello')

  watchEffect(()=>{
    ()
  })  

   = 'changed!'
</script>

4、响应式代理模式简单实现

<script>
  let activeEffect
  class Dep{
    subscribers = new Set()
    // 添加依赖
    depend(){
      if(activeEffect){
        (activeEffect)
      }
    }

    // 通知
    notify(){
      (effect => {
        effect()
      })
    }
  }

  function watchEffect(effect){
    activeEffect = effect
    effect()
    activeEffect = null
  }

  const targetMap = new WeakMap()

  function getDep(target,key){
    let depsMap = (target)
    if(!depsMap){
      depsMap = new Map()
      (target,depsMap)
    }
    let dep = (key)
    if(!dep){
      dep = new Dep()
      (key,dep)
    }
    return dep
  }

  // const dep = new Dep()
   // 设置响应式代理
  const reactiveHandlers = {
    get(target,key,receiver){
      const dep = getDep(target,key)
      ()
      // return target[key]
      return (target,key,receiver)
    },

    set(target,key,value,receiver){
      const dep = getDep(target,key)
      const result = (target,key,value,receiver)
      ()
      return result
    }
  }

  // 响应式代理入口函数
  function reactive(raw){
    return new Proxy(raw,reactiveHandlers)
  }

  // 初始化对象设置代理模式
  const state = reactive({
    count:0
  })

  watchEffect(()=>{
    ()
  })

  ++
</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 =  = ()
    if(){
      ().forEach(val=>{
        if(('on')){
          ((2).toLocaleLowerCase(),[val])
        }else{
          (val,[val])
        }
      })
    }

    if(){
      if(typeof  === 'string'){
         = 
      }else{
        (child=>{
          mount(child,el)
        })
      }
    }
    (el)
  }

  // dom更新函数
  function patch(n1,n2){
    if( === ){
      // 对比props
      const el =  = 
      const oldProps =  || {}
      const newProps =  || {}
      for(key in newProps){
        const oldValue = oldProps[key]
        const newValue = newProps[key]
        if(newValue !== oldValue){
          (key,newValue)
        }
      }
      for(key in oldProps){
        if(!(key in newProps)){
          (key)
        }
      }

      // 对比children
      const oldChildren = 
      const newChildren = 
      // 新的子节点是一个字符串
      if(typeof newChildren === 'string'){
        if(typeof oldChildren === 'string'){
          if(newChildren !== oldChildren){
             = newChildren
          }
        }else{
           = newChildren
        }
      }else{
        /*
          * 新的子节点是一个数组
          * 旧的子节点是一个字符串
          * 将元素的innerHtml置空,遍历调用mount函数解析虚拟dom
        */
        if(typeof oldChildren === 'string'){
           = ''
          (child=>{
            mount(child,el)
          })
        }else{
          /*
            * 假设元素的key不改变(即顺序不变的情况下)
            * 新的子节点是一个数组
            * 旧的子节点是一个数组
            * 当旧的子节点数组比新的子节点数组长度大,则移除多余部分的长度
            * 当旧的子节点数组比新的子节点数组长度小,则遍历调用mount函数解析多的部分借点为虚拟dom
          */  
          const commonLength = (,)
          for(let i = 0 ; i < commonLength ; i++){
            patch(oldChildren[i],newChildren[i])
          }
          if( > ){
            ().forEach(child=>{
              ()
            })
          }
          if( < ){
            ().forEach(child=>{
              mount(child,el)
            })
          }
        }
      }
    }else{
      // replace
    }
  }

  let activeEffect
  
  class Dep{
    subscribers = new Set()
    // 添加依赖
    depend(){
      if(activeEffect){
        (activeEffect)
      }
    }

    // 通知
    notify(){
      (effect => {
        effect()
      })
    }
  }

  function watchEffect(effect){
    activeEffect = effect
    effect()
    activeEffect = null
  }

  const targetMap = new WeakMap()

  function getDep(target,key){
    let depsMap = (target)
    if(!depsMap){
      depsMap = new Map()
      (target,depsMap)
    }
    let dep = (key)
    if(!dep){
      dep = new Dep()
      (key,dep)
    }
    return dep
  }
  
  // 设置响应式代理
  const reactiveHandlers = {
    get(target,key,receiver){
      const dep = getDep(target,key)
      ()
      // return target[key]
      return (target,key,receiver)
    },

    set(target,key,value,receiver){
      const dep = getDep(target,key)
      const result = (target,key,value,receiver)
      ()
      return result
    }
  }

  // 响应式代理入口函数
  function reactive(raw){
    return new Proxy(raw,reactiveHandlers)
  }

  const App = {
    data:reactive({
      count:0
    }),
    render(){
      return h(
        'div',
        {
          onClick:()=>{
            ++
          }
        },
        String()
      )
    }
  }

  function mountApp(component,container){
    let isMount = false
    let prevVdom
    watchEffect(()=>{
      if(!isMount){
        prevVdom = ()
        mount(prevVdom,container)
        isMount = true
      }else{
        const newVdom = ()
        patch(prevVdom,newVdom)
        prevVdom = newVdom
      }
    })
  }

  mountApp(App,('app'))
</script>

本文章代码来源于原视频中的讲解,仅供学习使用!!!