30-mini-vue 更新 element 的 props

更新 element 的 props

  1. 目标

    • 假如我们有一个 div,上面有一个属性 foo: 'foo', 我们把这个属性改为 foo: 'new-foo' ------> 修改
    • 属性由 foo: 'foo' 变为 foo: null || undefined ------> 删除这个属性
    • 属性 bar: 'bar' 变为这个属性不存在
  2. 初始化文件

    js 复制代码
    import { h, ref } from "../../lib/guide-mini-vue.esm.js";
    
    export const App = {
      name: "App",
      render() {
        return h("div",
          {
              ...this.props
          },
          [
            h('p',{},'标签'),
            h('button', {
              onClick: this.onChangePropsDemo1
            },
              "btn: props修改了"
            ),
            h('button', {
              onClick: this.onChangePropsDemo2
            },
              "btn2: props改变为null或undefined"
            ),
            h('button', {
              onClick: this.onChangePropsDemo3
            },
              "btn3: props被删除了"
            ),
          ]
        )
      },
      setup() {
        const props = ref({
          foo: "foo",
          bar: "bar"
        });
        // 修改
        const onChangePropsDemo1 = () => {
          props.value.foo = 'new-foo'
        }
        // 置空
        const onChangePropsDemo2 = () => {
          props.value.foo = undefined
        }
        // 删除
        const onChangePropsDemo3 = () => {
          props.value = {
            foo: "foo"
          }
        }
        return {
          props,
          onChangePropsDemo1,
          onChangePropsDemo2,
          onChangePropsDemo3
        }
      },
    }
  3. 实现点击 btn: props 修改了 ,到这里实现了点击按钮修改属性

js 复制代码
// App.ts
const onChangePropsDemo1 = () => {
  props.value.foo = 'new-foo'
}
js 复制代码
// renderer.ts
function patchElement(n1, n2, container) {  // ✅ 
  // 1. 拿到新旧虚拟dom的 props 进行比对
  let oldProps = n1.props || {}
  let newProps = n2.props || {}
  let el = n2.el = n1.el
  if(oldProps !== newProps) {
    // 2. 如果不相等,就进行修改
    patchProps(el, oldProps, newProps)
  }
}
function patchProps(el, oldProps, newProps) { // ✅
  // 遍历新 props
  for (let key in newProps) {
    // 拿到 新props 与旧props 的值进行对比
    let newVal = newProps[key]
    let oldVal = oldProps[key]
    // 如果不相等,进一步进行修改
    if(newVal !== oldVal) {
      hostPatchPros(el, key, oldVal, newVal)
    }
  }
}
  function mountElement(n2, container, parentComponent) {
  const { type, props, children, shapeFlag } = n2
  const el = n2.el = hostCreateElement(type)
  if (shapeFlag & shapeFlags.TEXT_CHILDREN) {
    el.textContent = children
  } else if (shapeFlag & shapeFlags.ARRAY_CHILDREN) {
    mountChildren(children, el, parentComponent)
  }
  for (let key in props) {
    let value = props[key]
    hostPatchPros(el, key, null, value)  // ✅ 这里增加了一个 旧虚拟dom的 props 值的参数
  }
  hostInsert(el, container)
}
// runtime-dom/index.ts
export function patchProps(el, key, preVal, nextVal) { // ✅ 这里增加了一个 旧虚拟dom的 props 值的参数
  const isOn = (key) => /^on[A-Z]/.test(key)
  if (isOn(key)) {
    const event = key.slice(2).toLocaleLowerCase() 
    el.addEventListener(event, nextVal, false) // ✅
  } else {
    el.setAttribute(key, nextVal)
  }
}
  1. 下面实现点击 btn2: props改变为null或undefined
js 复制代码
// App.js
const onChangePropsDemo2 = () => {
  props.value.foo = undefined
}
js 复制代码
export function patchProps(el, key, preVal, nextVal) {
  const isOn = (key) => /^on[A-Z]/.test(key)
  if (isOn(key)) {
    const event = key.slice(2).toLocaleLowerCase()
    el.addEventListener(event, nextVal, false)
  } else {
    if(nextVal === undefined || nextVal === null) { // ✅ 当赋值为 null 或者 undefined 时,就进行直接删除属性
      el.removeAttribute(key)
    } else {
      el.setAttribute(key, nextVal)
    }
  }
}
  1. 下面实现点击 btn3: props被删除了 就进行属性删除
js 复制代码
// 删除
const onChangePropsDemo3 = () => {
  props.value = {
    foo: "foo"
  }
}
js 复制代码
function patchProps(el, oldProps, newProps) {
  for (let key in newProps) {
    let newVal = newProps[key]
    let oldVal = oldProps[key]
    if(newVal !== oldVal) {
      hostPatchPros(el, key, oldVal, newVal)
    }
  }
  for(let key in oldProps) { // ✅ 当就属性的 key 在新属性里面不存在,说明被删除了,直接走上面封装的删除的逻辑,进行删除
    if(!(key in newProps)) {
      hostPatchPros(el, key, oldProps[key], null)  
    }
  }
}
  1. 查看可优化的点
js 复制代码
// shared/index.ts
export const EMPTY_OBJECT = {}
// renderer.ts
function patchElement(n1, n2, container) {

  let oldProps = n1.props || EMPTY_OBJECT // ✅
  let newProps = n2.props || EMPTY_OBJECT // ✅

  let el = n2.el = n1.el
  patchProps(el, oldProps, newProps)
}
function patchProps(el, oldProps, newProps) {
  if (oldProps !== newProps) { // ✅ 判断不相等才往下走
    for (let key in newProps) {
      let newVal = newProps[key]
      let oldVal = oldProps[key]
      if (newVal !== oldVal) {
        hostPatchPros(el, key, oldVal, newVal)
      }
    }
    if(oldProps !== EMPTY_OBJECT) { // ✅ 只有不为空才能往下走,优化性能
      for (let key in oldProps) {
        if (!(key in newProps)) {
          hostPatchPros(el, key, oldProps[key], null)
        }
      }
    }
  }
}
相关推荐
魁首16 小时前
OpenAI Codex 深入剖析:下一代 AI 编程助手的架构与原理
前端·openai·ai编程
火星数据-Tina16 小时前
如何构建一个支持多终端同步的体育比分网站?
大数据·前端·数据库·websocket
IT_陈寒16 小时前
React 19 实战:5个新特性让你的开发效率提升50%!
前端·人工智能·后端
GuMoYu16 小时前
el-date-picker限制选择范围
前端·javascript·vue.js
a31582380616 小时前
Android 大图显示策略优化显示(二)
android·java·开发语言·javascript·kotlin·glide·图片加载
计算机毕设VX:Fegn089516 小时前
计算机毕业设计|基于springboot + vue律师咨询系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
冴羽17 小时前
JavaScript Date 语法要过时了!以后用这个替代!
前端·javascript·node.js
加油乐17 小时前
react使用Ant Design
前端·react.js·ant design