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

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

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>

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

Logo

前往低代码交流专区

更多推荐