vue3 --学习笔记代码

37 阅读3分钟
<div id="app">{{title}}</div>

<script>
  function h(tag, props, children) {
    return {
      tag,
      props,
      children
    }
  }
  // 填充代码
  // 1. 基本结构
  const Vue = {
    // 拓展性
    createRenderer({
      querySelector,
      insert,
      createElement,
      remove
    }) {
      // 返回渲染器
      return {
        createApp(options) {
          // 返回app对象
          return {
            mount(selector) {
              // 1. 找到宿主元素
              const parent = querySelector(selector)
              // 2. 渲染页面
              if (!options.render) {
                // 2.1 处理template: 编译
                options.render = this.compile(parent.innerHTML)
              }

              // 收集 setup 和其他选项
              if (options.setup) {
                this.setupState = options.setup()
              }
              if (options.data) {
                this.data = options.data()
              }

              // 渲染之前,处理setup和其他选项的兼容
              const proxy = new Proxy(this, {
                get(target, key) {
                  // 先从setup中取,如果取不到再从data中取
                  // 如果setup存在,且key在setup中定义过
                  if (target.setupState && key in target.setupState) {
                    // return target.setupState[key]
                    return Reflect.get(target.setupState, key)
                  } else {
                    // return target.data[key]
                    return Reflect.get(target.data, key)
                  }

                },
                set(target, key, val) {
                  if (target.setupState && key in target.setupState) {
                    return Reflect.set(target.setupState, key, val)
                  } else {
                    return Reflect.set(target.data, key, val)
                  }
                }
              })


              this.update = effect(() => {

                const vnode = options.render.call(proxy)
                // 转换vnode为dom
                // 初始化创建整棵树
                if (!this.isMounted) {

                  // 实现 createElm, 整体创建,vnode -> el
                  const el = this.createElm(vnode)
                  parent.innerHTML = ''
                  insert(el, parent)
                  // init 初始化,设置已挂载标识
                  this.isMounted = true
                } else {
                  this.patch(this._vnode, vnode)
                }
                this._vnode = vnode
              })
            },
            patch(oldNode, newNode) {
              const el = newNode.el = oldNode.el
              // 1. 更新:必须更新相同节点
              // 什么样的是相同节点
              if (oldNode.tag === newNode.tag && oldNode.key === newNode.key) {
                // update 相同节点,更新

                const oldChild = oldNode.children
                const newChild = newNode.children

                if (typeof oldChild === 'string') {
                  if (typeof newChild === 'string') {
                    // 文本更新
                    if (oldChild !== newChild) {
                      el.textContent = newChild
                    }
                  } else {
                    // 替换文本为一组子元素,清空再创建并追加
                    el.textContent = ''
                    newChild.forEach(child => insert(this.createElm(child), el))

                  }

                } else {
                  if (typeof newChild === 'string') {
                    // 替换一组子元素为文本
                    el.textContent = newChild
                  } else {
                    // updateChildren 比较两组子元素
                    this.updateChildren(el, oldChild, newChild)
                  }
                }
              } else {
                // replace 不相同节点,替换
              }
            },
            updateChildren(el, oldChild, newChild) {
              // 1.获取newCh和oldCh中较短的那一个
              const len = Math.min(oldChild.length, newChild.length)
              // 强制更新
              for (let i = 0; i < len; i++) {
                this.patch(oldChild[i], newChild[i])
              }

              // 处理剩余元素
              // 新数组元素多
              if (newChild.length > oldChild.length) {
                // 批量创建并追加
                // 截取newCh中len后面的部分
                newChild.slice(len).forEach(child => {
                  insert(this.createElm(child), el)
                })
              } else if (newChild.length < oldChild.length) {
                // 批量删除
                oldChild.slice(len).forEach(child => {
                  remove(child.el, el)
                })
              }
            },
            createElm(vnode) {
              const {
                tag,
                props,
                children
              } = vnode
              // 遍历vnode,创建整棵树
              const el = createElement(tag)
              // 递归
              // 判断children是字符串
              if (typeof children === 'string') {
                el.textContent = children
              } else {
                children.forEach(child => insert(this.createElm(child), el))
              }
              // vnode中要保存真实DOM,以备未来更新使用
              vnode.el = el
              return el
            },
            compile(template) {
              // 返回一个render函数
              // parse -> ast
              // generate: ast -> render
              return function render() {
                if (Array.isArray(this.title)) {
                  return h('h3', null, this.title.map(s => h('p', null, s)))
                } else {
                  return h('h3', null, this.title)
                }
                // const h3 = document.createElement('h3')
                // h3.textContent = this.title
                // return h3
                // 应该产生虚拟DOM
                // return h('h3', null, this.title)
                // return h('h3', null, [
                //   h('p', null, this.title),
                //   h('p', null, this.title),
                //   h('p', null, this.title)
                // ])
              }
            },

          }
        }
      }
    },

    createApp(options) {
      // 创建一个web平台特有的渲染器
      const renderer = Vue.createRenderer({
        querySelector(sel) {
          return document.querySelector(sel)
        },
        insert(el, parent) {
          parent.appendChild(el)
        },
        createElement(tag) {
          return document.createElement(tag)
        },
        remove(el, parent) {
          parent.removeChild(el)
        }
      })
      return renderer.createApp(options)
    }

  }
</script>

<script>
  // 内容拦截用户对代理对象的访问,从而在值发生变化的时候做出响应
  function reactive(obj) {
    // 返回代理的对象
    return new Proxy(obj, {
      get(target, key) {
        console.log('get key:', key)
        track(target, key)
        return Reflect.get(target, key)
      },
      set(target, key, val) {
        console.log('set key:', key)
        Reflect.set(target, key, val)

        trigger(target, key)
      }
    })
  }

  // 建立映射关系: 依赖 Dep -> 组件更新函数


  // 调用effect,首先执行fn
  // 临时存储副作用函数 effectStack
  const effectStack = []

  function effect(fn) {
    // 1. 执行一次fn
    // fn()
    const eff = function () {
      try {
        effectStack.push(eff)
        fn()
      } finally {
        effectStack.pop()
      }
    }
    // 立即调用一次
    eff()
    // 将这个函数返回出去
    return eff
  }

  // Vue3: 创建map结构 { target: {key: [update1, update2]} }
  const targetMap = {
    // 应该往里面存这样的数据,依赖关系
    // state: {
    //   'title': [update]
    // }
  }

  // 建立 target,key 和 effectStack 中存储的副作用函数之间的关系
  function track(target, key) {
    // 拿出存储副作用函数的最后一个元素
    const effect = effectStack[effectStack.length - 1]
    // 写死的话是这样的,但是不能这样写,要不然每次都新创建一个对象
    // targetMap[target] = {}
    // targetMap[target][key] = [effect]
    // 所以应该先判断target为key的对象存不存在
    let map = targetMap[target]
    if (!map) {
      // 首次get这个target【不存在就给map初始化一下】 
      map = targetMap[target] = {}
    }

    let deps = map[key]
    if (!deps) {
      deps = map[key] = []
    }
    // 映射关系建立
    if (deps.indexOf(effect) === -1) {
      deps.push(effect)
    }
  }

  function trigger(target, key) {
    const map = targetMap[target]
    if (map) {
      const deps = map[key]
      if (deps) {
        deps.forEach(dep => dep())
      }
    }
  }
</script>


<script>
  const app = Vue.createApp({
    setup() {
      const state = reactive({
        title: 'hello, vue3 YK菌'.split("")
      })

      setTimeout(() => {
        state.title = '2秒后见到新的YK菌'.split("")
      }, 2000)

      return state
    }
  })
  app.mount('#app')
</script>