后面两章详细描述了如何在自定义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框架曾经实现,残余的如申明周期函数比较简单,就是在相应操作的时候去调用申明周期函数,不再赘述。