乐趣区

关于javascript:如何构建自定义React基础虚拟Dom框架三

后面两章详细描述了如何在自定义 react 框架中实现虚构 Dom 渲染和比照更新,本章将持续增加 ref 属性和 key 属性。

ref 属性

通过 ref 属性能够获取原生 Dom 对象或者组件实例。

批改入口文件,增加 ref 测试组件:

class DemoRef extends MyReact.Component {handle() {
        let value = this.input.value
        console.log(value)
    }
    render() {
        return (
            <div>
                <input type="text" ref={input => (this.input = input)} />
                <button onClick={this.handle.bind(this)}> 按钮 </button>
            </div>
        )
    }
}

MyReact.render(<DemoRef></DemoRef>, root)

原生 Dom

在 createDOMElemen 的时候,判断 props 上是否存在 ref 属性,如果存在,则调用 ref 属性对应的函数,并将原生 Dom 元素传递进去。

类组件实例

在 mountComponent 办法中,判断实例的 props 属性上是否存在 ref 属性,如果存在,调用 ref 属性对应的函数,并将类组件实例当作参数传递。

key 属性

ref 属性次要是为了优化列表的比照更新,通过复用达到最小化操作 Dom 的目标。

批改入口文件,增加测试 Demp:

import * as MyReact from './MyReact'

class DemoRef extends MyReact.Component {handle() {
        let value = this.input.value
        console.log(value)
    }
    render() {
        return (
            <div>
                <input type="text" ref={input => {this.input = input}
                } />
                <button onClick={this.handle.bind(this)}> 按钮 </button>
            </div>
        )
    }
}

// MyReact.render(<DemoRef></DemoRef>, root)
class KeyDemo extends MyReact.Component {constructor(props) {super(props)
        this.state = {
            persons: [
                {
                    id: 1,
                    name: "张三"
                },
                {
                    id: 2,
                    name: "李四"
                },
                {
                    id: 3,
                    name: "王五"
                },
                {
                    id: 4,
                    name: "赵六"
                }
            ]
        }
        this.handleClick = this.handleClick.bind(this)
    }
    handleClick() {const newState = JSON.parse(JSON.stringify(this.state))
        // newState.persons.push(newState.persons.shift())
        // newState.persons.splice(1, 0, { id: 100, name: "李逵"})
        newState.persons.pop()
        this.setState(newState)
    }
    render() {
        return (
            <div>
                <ul>
                    {this.state.persons.map(person => (<li key={person.id}>
                            {person.name}
                            <DemoRef />
                        </li>
                    ))}
                </ul>
                <button onClick={this.handleClick}> 按钮 </button>
            </div>
        )
    }
}

MyReact.render(<KeyDemo />, root)

在原有的 diff 算法中,会间接循环遍历虚构 Dom 的所有子节点进行 diff,当退出 key 属性后,须要批改此处代码。

首先须要循环遍历旧虚构 Dom 的所有子节点,找出所有蕴含 key 属性的节点。

let keyedElements = {}
for (let i = 0, len = oldDom.childNodes.length; i < len; i++) {let domElement = oldDom.childNodes[i]
    if (domElement.nodeType === 1) {let key = domElement.getAttribute("key")
         if (key) {keyedElements[key] = domElement
         }
    }
}
let hasNoKey = Object.keys(keyedElements).length === 0

而后在更新后的虚构 Dom 中遍历查找所有和旧节点 key 雷同的子节点,如果有,以后子节点不须要操作,否则是新增的子节点。

if (hasNoKey) {
    // 比照子节点
    virtualDom.children.forEach((child, i) => {diff(child, oldDom, oldDom.childNodes[i])
    })
} else {
    // 2. 循环 virtualDom 的子元素 获取子元素的 key 属性
    virtualDom.children.forEach((child, i) => {
        let key = child.props.key
        if (key) {let domElement = keyedElements[key]
            if (domElement) {
                // 3. 看看以后地位的元素是不是咱们冀望的元素
                if (oldDom.childNodes[i] && oldDom.childNodes[i] !== domElement) {oldDom.insertBefore(domElement, oldDom.childNodes[i])
                }
            } else {
                // 新增元素
                mountElement(child, oldDom, oldDom.childNodes[i])
            }
        }
    })
}

至此,自定义简略的 react 框架曾经实现,残余的如申明周期函数比较简单,就是在相应操作的时候去调用申明周期函数,不再赘述。

退出移动版