Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React16.2源码解析-Reconciliation #16

Open
UNDERCOVERj opened this issue Jun 7, 2018 · 0 comments
Open

React16.2源码解析-Reconciliation #16

UNDERCOVERj opened this issue Jun 7, 2018 · 0 comments

Comments

@UNDERCOVERj
Copy link
Owner

UNDERCOVERj commented Jun 7, 2018

在下一个 stateprops 更新时,render() 函数将会返回一个不同的 React 元素树。接下来 React 将会找出如何高效地更新 UI 来匹配最近时刻的 React 元素树。

可以通过指定 key 属性,来指示哪些元素可以保持稳定。 props.key 也对应 fiber.key

render () {
    return (
        <div>
            {this.state.arr.map((item, idx) => {
                return <p key={item.name}>{item.name}</p>
            })}
        </div>
    )
}

一致性比较的几个算法

image

一致性比较的使用

当为了创建/重用 childFiber 而执行 beginWork ,并在根据 workInProgress.tag 来更新 Component 时,会用到 reconcileChildren 来对所有新旧子 fiber 进行一致性比较

reconcileChildren 需要比较是否是第一次生成子 fiber ,是则 currentChild = null; 否则比较后更新后 fiber。如下代码所示,通过 workInProgress.alternate 来判断是否是第一次创建子 fibermountChildFibersreconcileChildFibers 执行函数相同,只是参数不同

var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);

function reconcileChildrenAtExpirationTime(current, workInProgress, nextChildren) {
    var current = workInProgress.alternate; // 第一次创建子fiber为null
    if (current === null) {
        workInProgress.child = mountChildFibers(workInProgress, null, nextChildren);
    } else {
        workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren);
    }
}

举例分析

class App extends Component {
    constructor (props) {
        super(props);
        this.state = {
            arr: [
                {name: '1'},
                {name: '2'}
            ]
        }
    }
    handleClick = (e) => {
        let arr = this.state.arr
        this.setState({
            arr: [{name: '4'}, arr[1], {name: '3'}, arr[0]]
        })
    }
    render() {
        return (
            <div className="App">
                <button onClick={this.handleClick} className="aaa">click</button>
                <div>
                    {this.state.arr.map((item, idx) => {
                        return <p key={item.name}>{item.name}</p>
                    })}
                </div>
            </div>
        );
    }
}

上述代码,会渲染带有 key的子节点

作用:在更新时, React 会用这个 key 去比较原来的树的子节点和之后的树的子节点

在点击 button 之后,会执行事件回调函数 handleClick ,在此函数中有 setState 操作,会将更新队列挂载到 instance 上,而后在 updateClassInstance 时,将改变 instance.state

var newState = processUpdateQueue();
instance.state = newState;

改变 instance.state主要是为了在 render 方法中用 this.state 来取得。

reconcileChildFibers 中重要的 newChild 参数

  • updateClassComponent 中, newChildrender 方法返回后的 element

var newChild = instance.render();

  • updateHostComponent 中,newChildprops.children(如果 props.children 不是文本字符串)

var newChild = nextProps.children;

接下来,就拿出代码来深度剖析,reconcileChildFibers 是决定用那种方式对 newChildoldChild 进行 Reconciliation

function reconcileChildFibers(returnFiber, currentFirstChild, newChild, expirationTime) { // currentFirstChild指第一个子fiber
    if (typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null) {
        newChild = newChild.props.children;
    }
    
    // Handle object types
    var isObject = typeof newChild === 'object' && newChild !== null;
    
    if (isObject) {
        switch (newChild.$$typeof) {
            case REACT_ELEMENT_TYPE:
                return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, expirationTime)); // 创建child fiber
    
            case REACT_CALL_TYPE:
                return placeSingleChild(reconcileSingleCall(returnFiber, currentFirstChild, newChild, expirationTime));
            case REACT_RETURN_TYPE:
                return placeSingleChild(reconcileSingleReturn(returnFiber, currentFirstChild, newChild, expirationTime));
            case REACT_PORTAL_TYPE:
                return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, expirationTime));
        }
    }
    
    if (typeof newChild === 'string' || typeof newChild === 'number') {
        return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, '' + newChild, expirationTime));
    }
    
    if (isArray$1(newChild)) { // 如果新的child是数组,这里的newChild是nextProps.children
        return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, expirationTime);
    }
    
    if (getIteratorFn(newChild)) {
        return reconcileChildrenIterator(returnFiber, currentFirstChild, newChild, expirationTime);
    }
}
    1. reconcileSingleElement

image

如果是第一次生成 childFiber ,则直接根据元素类型( element.type )来创建 childFiber

如果是更新,则按序比较新旧孩子 fiberkey ,即 childFiber-childFiber.sibling-childFiber.sibling.sibling....。如果有多个孩子 fiber ,将 key 不一样的 fiber 标记为删除,key 一样的则重用。

fiber.effectTag = Deletion;
returnFiber.firstEffect = returnFiber.lastEffect = fiber;
    1. reconcileChildrenArray

新旧 children 对比,对于 key 相同的 fiber ,采取 updateSlot 更新措施(如果 key 相同,则更新 fiber 并返回,否则返回 null)。

主要工作是将 childsibling 连起来,最终返回第一个 child

遍历新 children

image


image


image


image

var existingChildren = mapRemainingChildren(returnFiber, oldFiber);

mapRemainingChildren 是为了将旧的 childrenkey 为键存入 map

var _newFiber2 = updateFromMap(existingChildren, returnFiber, newIdx, newChildren[newIdx], expirationTime);

updateFromMap 为了返回创建或者重用的 fiber

lastPlacedIndex = placeChild(_newFiber2, lastPlacedIndex, newIdx);

placeChild 为新的 newFiber 添加 index 索引,并置 effectTagPlacement 。如果 newFiber 是新创建的,或者 newFiber 是重用的但是位置发生了改变,这两种情况都会置 effectTagPlacement,否则不设置 effectTag

由此我们就能看到 Reconciliation 的作用了

作用

  1. 重用或者创建新的子 fiber
  2. 将新的所有子 fibersibling 连接起来
  3. 对于需要移动的孩子元素,设置其对应 fibereffectTag
@UNDERCOVERj UNDERCOVERj changed the title React16.2最新源码-Reconciliation React16.2源码解析-Reconciliation Jun 9, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant