更新 element 的 props
-
目标
- 假如我们有一个 div,上面有一个属性 foo: 'foo', 我们把这个属性改为 foo: 'new-foo' ------> 修改
- 属性由 foo: 'foo' 变为 foo: null || undefined ------> 删除这个属性
- 属性 bar: 'bar' 变为这个属性不存在
-
初始化文件
jsimport { 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 } }, } -
实现点击 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)
}
}
- 下面实现点击 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)
}
}
}
- 下面实现点击 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)
}
}
}
- 查看可优化的点
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)
}
}
}
}
}