前面两章详细描述了如何在自定义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框架已经完成,剩余的如声明周期函数比较简单,就是在相应操作的时候去调用声明周期函数,不再赘述。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。