v-model的简单实现

<!DOCTYPE html>
<html>
<head>
  <title>Mini Vue</title>
</head>
<body>
  <div id="app">
    <input v-model="message" />
    <p>{{ message }}</p>
  </div>

  <script>
    // 1. 响应式系统(上面的代码)
    function reactive(target) {
      return new Proxy(target, {
        get(obj, key) {
          track(obj, key)
          return obj[key]
        },
        set(obj, key, value) {
          obj[key] = value
          trigger(obj, key)
          return true
        }
      })
    }

    let activeEffect = null
    const targetMap = new WeakMap()

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

    function track(obj, key) {
      if (!activeEffect) return
      let depsMap = targetMap.get(obj)
      if (!depsMap) targetMap.set(obj, (depsMap = new Map()))
      let dep = depsMap.get(key)
      if (!dep) depsMap.set(key, (dep = new Set()))
      dep.add(activeEffect)
    }

    function trigger(obj, key) {
      const depsMap = targetMap.get(obj)
      if (!depsMap) return
      const dep = depsMap.get(key)
      if (dep) dep.forEach(effect => effect())
    }

    // 2. 极简编译:查找 v-model 和 {{ }}
    function compile(el, data) {
      // 查找所有子元素
      const nodes = el.querySelectorAll('*')

      // 处理 v-model
      el.querySelectorAll('[v-model]').forEach(input => {
        const key = input.getAttribute('v-model')
        
        // 初始化 value
        input.value = data[key]

        // 双向绑定
        input.addEventListener('input', (e) => {
          data[key] = e.target.value
        })

        // 数据变化时更新 input
        watchEffect(() => {
          input.value = data[key]
        })
      })

      // 处理 {{ message }}
      Array.from(el.childNodes).forEach(node => {
        if (node.nodeType === 3) { // 文本节点
          const text = node.textContent
          const match = text.match(/\{\{(.*)\}\}/)
          if (match) {
            const key = match[1].trim()
            // 初次渲染
            node.textContent = node.textContent.replace(match[0], data[key])
            // 响应式更新
            watchEffect(() => {
              node.textContent = node.textContent.replace(data[key], data[key])
            })
          }
        }
      })

      // 处理子元素中的 {{ }}
      nodes.forEach(node => {
        Array.from(node.childNodes).forEach(child => {
          if (child.nodeType === 3) {
            const text = child.textContent
            const match = text.match(/\{\{(.*)\}\}/)
            if (match) {
              const key = match[1].trim()
              const getValue = () => data[key]
              watchEffect(() => {
                child.textContent = text.replace(/\{\{.*\}\}/, getValue())
              })
            }
          }
        })
      })
    }

    // 3. 启动
    const obj = { message: 'Hello, Mini Vue!' }
    const data = reactive(obj)

    const app = document.getElementById('app')
    compile(app, data)
  </script>
</body>
</html>
posted @ 2025-09-10 11:03  躺尸的大笨鸟  阅读(6)  评论(0)    收藏  举报