Git Product home page Git Product logo

anu's People

Contributors

abnercrack avatar ambit-tsai avatar ariesly15 avatar codearvin avatar dgeibi avatar enducode avatar gandao avatar gaoxiaomumu avatar gaterking avatar gitaiqaq avatar hellosean1025 avatar hkc452 avatar jgx-jay avatar jounqin avatar jsleey avatar lizheming avatar niuzhuang avatar onlyling avatar pepperyan avatar roland-reed avatar rubylouvre avatar shaoyudong avatar shinhwe avatar taotao9125 avatar tiye avatar wenchaoss avatar whatwewant avatar yongningfu avatar yvanwangl avatar zeromake avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

anu's Issues

考虑是不是要实现getType方法

var __type = Object.prototype.toString

export function getType(obj){
    var type = __type.call(obj)
    return typeCache[type] || (typeCache[type] = (type.slice(8,-1).toLowerCase()))
}
var typeCache = {}
'Function,Date,Undefined,String,Number,Object,Null,'.replace(/\w+/,function(){
   typeCache['[object '+ name] = name.toLowerCase()
})

快速扁平化children,并为它添加上deep

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <style>
        .aaa {
            width: 200px;
            height: 200px;
            background: red;
        }

        .bbb {
            width: 200px;
            height: 200px;
            background: lawngreen;
        }
    </style>

    <!--<script type='text/javascript' src="./dist/React.js"></script>-->
    <!-- <script src="./test/react.js"></script>
    <script src="./test/react-dom.js"></script>
    <script src="./test/redux.js"></script>
    <script src="./test/react-redux.js"></script>
    <script src="./test/babel.js"></script>-->
    <script>
        window.onload = function () {
          
         
           var React = {
                createElement: function (type, configs) {
                  
                    var props = {},
                        key = null,
                        ref = null,
                        pChildren = null, //位于props中的children
                        props = {},
                        stack = [],
                        vtype = 1,
                        typeType = typeof type,

                        isEmptyProps = true
                    if (configs) {
                        for (var i in configs) {
                            var val = configs[i]
                            if (i === 'key') {
                                key = val
                            } else if (i === 'ref') {
                                ref = val
                            } else if (i === 'children') {
                                pChildren = val
                            } else {
                                isEmptyProps = false
                                props[i] = val
                            }
                        }
                    }
                
                    if (typeType === 'function') {
                        vtype = type.prototype && type.prototype.render ?
                            2 :
                            4
                    }
                    for (var i = 2, n = arguments.length; i < n; i++) {
                        stack.push(arguments[i])
                    }

                    if (!stack.length && pChildren && pChildren) {
                        stack.push(pChildren)
                    }
                   
                    props.children = flattenChildren(stack)
                    return new VNode(type, props, key, vtype)

                }
            }
            function flattenChildren(){
                 
            }
            function VNode(type, props, key, vtype) {
                this.type = type
                this.props = props
                this.vtype = vtype
                this.deep = 0
                if (key) {
                    this.key = key
                }

            }

            var a = React.createElement('div', {}, "xxx", "yyy", ["aaa", 'bbb', [React.createElement('span', {})],
                    React.createElement('p', {}), [React.createElement('b', {})]
                ],  "zzzz")

            console.log(a)
        //==========a 的结构如下:
           var a = {type: 'div', vtype: 1, props: {
                children: [
                    {type: '#text', deep: 1, text: "xxxyyyaaabbb"},{type: 'span', deep: 2, props: {}, vtype: 1},
                    {type: 'p', deep: 1, props: {}, vtype: 1},{type: 'b', deep: 2, props: {}, vtype: 1},
                    {type: '#text', deep: 0, text: "zzzz"}
                ]
            }}
        }
    </script>
</head>

<body>

    <div>这个默认会被清掉</div>
    <div id='example'></div>


</body>

</html>

真实DOM的命名空间的获取

// https://developer.mozilla.org/en-US/docs/Web/MathML/Element/math
var rmathTags = /^m/;

var namespaceMap = oneObject("svg", NAMESPACE.svg);
namespaceMap.semantics = NAMESPACE.math;
// http://demo.yanue.net/HTML5element/
"meter,menu,map,meta,mark".replace(/\w+/g, function(tag) {
    namespaceMap[tag] = null;
});
export function getNs(type) {
    if (namespaceMap[type] !== void 666) {
        return namespaceMap[type];
    } else {
        return (namespaceMap[type] = rmathTags.test(type) ? NAMESPACE.math : null);
    }
}

销毁方法的改进

disposeVnode是用于销毁虚拟DOM及其下面的所有孩子, 并将真实DOM的内部清空

调度系统

mainQueue就是传送带,currentQueue或其他queue就是托运箱,job就是要加工的材料,exec是决定如何加工。

export function drainQueue() {
    
    var queue = mainQueue.shift()
    //如果父元素拥有多个子组件,如果第一个组件在mounted/updated钩子里再次更新父元素,
    //那么mainQueue可能没有子数组了,需要置换queue为currentQueue,执行里面的mounted/updated钩子
    if(!queue &&  currentQueue.length){
        queue = currentQueue;
    }
    options.beforePatch();
    //先执行所有元素虚拟DOMrefs方法(从上到下)
    Refs.clearElementRefs();
    let needSort = [],
        unique = {},
        job, updater;

    while( job = queue.shift() ){
        updater = job.host;
        //queue可能中途加入新元素,  因此不能直接使用queue.forEach(fn)
        if (updater._disposed) {
            continue;
        }
        if (!unique[updater._mountOrder]) {
            unique[updater._mountOrder] = 1;
            needSort.push(job);
        }
        // currentQueue = queue;
           
        // var command = job.exec === updater.onUpdate ? "update" : job.exec === updater.onEnd ? "end" : "receive";
        // console.log(updater.name, command,updater._hookName );
        job.exec.call(updater,queue);
        // if(!queue.length){
        /*  while((el =  mainQueue.shift())){
                    if(el.length){
                        queue = el;
                        break;
                    }
                }
           */
        // }
    }

    if (mainQueue.length > 1) {
       mainQueue.push(currentQueue);
    }
    //再执行所有setState/forceUpdate回调,根据从下到上的顺序执行
    needSort.sort(mountSorter).forEach(function(job) {
        var updater = job.host;
        clearArray(updater._pendingCallbacks).forEach(function(fn) {
            fn.call(updater._instance);
        });
    });
    options.afterPatch();
}

React16的异步更新调研

React16通过createRoot实现全局异步,通过unstable_AsyncComponent实现局部异步

异步时间差可能大于100ms, unstable_AsyncComponent会让底下的组件更新全部变成异步

报错如下

createUncontrollable.js:33 Uncaught TypeError: Cannot read property 'propTypes' of undefined
at uncontrollable (createUncontrollable.js:33)
at Object. (Dropdown.js:417)
at webpack_require (bootstrap 31fd461a5a2ccf02c19c:585)
at fn (bootstrap 31fd461a5a2ccf02c19c:109)
at Object. (index.js:66)
at webpack_require (bootstrap 31fd461a5a2ccf02c19c:585)
at fn (bootstrap 31fd461a5a2ccf02c19c:109)
at Object. (bootstrap 31fd461a5a2ccf02c19c:631)
at webpack_require (bootstrap 31fd461a5a2ccf02c19c:585)
at bootstrap 31fd461a5a2ccf02c19c:631
React.js:1025 createClass已经废弃,请改用es6方式定义类
createClass @ React.js:1025
React.js:395 请限制使用Children.only,不要窥探虚拟DOM的内部实现,会导致升级问题
Children.(anonymous function) @ React.js:395
React.js:395 请限制使用Children.forEach,不要窥探虚拟DOM的内部实现,会导致升级问题
Children.(anonymous function) @ React.js:395
React.js:395 请限制使用Children.map,不要窥探虚拟DOM的内部实现,会导致升级问题
Children.(anonymous function) @ React.js:395
React.js:153 Uncaught Error: @root#render:You may have returned undefined, an array or some other invalid object
at checkNull (React.js:153)
at Stateless.renderComponent [as render] (React.js:2056)
at Object.mountStateless (React.js:2074)
at mountVnode (React.js:1912)
at Object.mountComponent (React.js:2034)
at mountVnode (React.js:1912)
at genVnodes (React.js:1888)
at renderByAnu (React.js:1854)
at render (React.js:1759)
at Object. (index.js:12)

updateChildren的改进

function updateChildren(lastVnode, nextVnode, parentNode, context, mountQueue) {
    let lastChildren = lastVnode.vchildren,
        nextChildren = flattenChildren(nextVnode),//nextVnode.props.children;
        childNodes = parentNode.childNodes,
        hashcode = {},
        mountAll = mountQueue.mountAll;
    if (nextChildren.length == 0) {
        lastChildren
            .forEach(function (el) {
                var node = el._hostNode;
                if (node) {
                    removeDOMElement(node);
                }
                disposeVnode(el);
            });
        return;
    }


    lastChildren.forEach(function (el) {
        let key = el.type + (el.key || "");
        if (el._disposed) {
            return;
        }
        let list = hashcode[key];
        if (list) {
            list.push(el);
        } else {
            hashcode[key] = [el];
        }
    });
    nextChildren.forEach(function (el) {
        let key = el.type + (el.key || "");
        let list = hashcode[key];
        if (list) {
            let old = list.shift();
            if (old) {
                el.old = old;
                if (!list.length) {
                    delete hashcode[key];
                }
            }
        }
    });
    for (let i in hashcode) {
        let list = hashcode[i];
        if (Array.isArray(list)) {
            list
                .forEach(function (el) {
                    let node = el._hostNode;
                    if (node) {
                        removeDOMElement(node);
                    }
                    disposeVnode(el);
                });
        }
    }

    var beforeDOM;

    for (let i = nextChildren.length - 1; i >= 0; i--) {
        let el = nextChildren[i],
            old = el.old,
            dom,
            queue = mountAll
                ? mountQueue
                : [];
        if (old) {
            delete el.old;
            if (el === old && old._hostNode && !contextHasChange) {
                //cloneElement
                dom = old._hostNode;
                let curDOM = childNodes[i];
                if (dom !== curDOM && curDOM) {
                    parentNode.replaceChild(dom, curDOM);
                }
            } else {
                dom = updateVnode(old, el, context, queue);
                if (!dom) {
                    dom = createDOMElement({ vtype: "#comment", text: "placeholder" });
                    replaceChildDeday([old, el, context, queue], dom, parentNode);
                }
            }
        } else {
            dom = mountVnode(el, context, null, queue);
        }
        if (dom !== beforeDOM) {
            insertDOM(parentNode, dom, beforeDOM);
        }
        beforeDOM = dom;
        if (!mountAll && queue.length) {
            clearRefsAndMounts(queue);
        }
    }
}

10.3

顺整diff机制

先将孩子插入到parentNode中,然后 diffChildren, 然后 diffProps与ref, 再处理受控组件,然后生命周期钩子

调整diffProps,将属性根据元素类型 进行, svg一律使用getXXXNS进行操作, 普通元素根据固有属性进行处理,固有属性又划分出字符串属性。

在componentDidMount钩子中执行setState不会引发死循环

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <style>
      .aaa {
            width: 200px;
            height: 200px;
            background: red;
        }
       .bbb {
            width: 200px;
            height: 200px;
            background: lawngreen;
        }
    </style> 
   <!-- <script type='text/javascript' src="./dist/polyfill.js"></script>-->
<!--<script type='text/javascript' src="./dist/React.js"></script>-->
 
    <script src="./test/react.js"></script>
     <script src="./test/react-dom.js"></script>
 <script src="./dist/babel.js"></script>
    <script type='text/babel'>
       class A extends React.Component{
           constructor(props){
               super(props)
               this.state = {
                   aaa: 1
               }
           }
           componentWillMount(){
              console.log('A will mount')
               this.setState({
                   aaa: 111
               })
           }
            componentDidMount(){
              console.log('A did mount')
           }
           render(){
               return <strong ref={(a)=>{console.log('A的内部',a)}}  >A内容{this.state.aaa}<B /></strong>
           }
       }
      class B extends React.Component{
           constructor(props){
               super(props)
               this.state = {
                   aaa: 2
               }
           }
           componentWillMount(){
               console.log('B will mount')
               this.setState({
                   aaa: 222
               })
           }
          componentDidMount(){
                 console.log('B did mount')
               this.setState({
                   aaa: 2222
               })
           }
           componentWillUpdate(){
              console.log('B will change')
           }
           render(){
               return <strong>{this.state.aaa}</strong>
           }
       }
        class C extends React.Component{
           constructor(props){
               super(props)
               this.state = {
                   aaa: 3
               }
           }
           componentWillMount(){
              console.log('C will mount')
               this.setState({
                   aaa: 333
               })
           }
            componentDidMount(){
              console.log('C did mount')
           }
           render(){
               return <span>{this.state.aaa}<D /></span>
           }
       }
        class D extends React.Component{
           constructor(props){
               super(props)
               this.state = {
                   aaa: 4
               }
           }
           componentWillMount(){
              console.log('D will mount')
               this.setState({
                   aaa: 444
               })
           }
            componentDidMount(){
              console.log('D did mount')
           }
            
           render(){
               return <span>{this.state.aaa}</span>
           }
       }
  
       
       class App extends React.Component{
            click(){
                length = 7
                length2 = 5
                this.forceUpdate()
            }
            render(){
               return <div><A /><div ref={()=>{console.log('1.1')}}><div ref={()=>{console.log('1.2')}}>AC之间</div></div><C /><div ref={()=>{console.log('执行aaa')}} >xxxx</div></div>
           }
       }
      
       window.onload = function(){
           ReactDOM.render(<App />, document.getElementById('example'))
       }
       </script>
</head>

<body>

    <div>这个默认会被清掉</div>
    <div id='example'></div>


</body>

</html>

createElement应该与官方一致

function flattenChildren(stack) {
    var lastText,
        child,
        hasText = false,
        children = [];

    while (stack.length) {
        //比较巧妙地判定是否为子数组
        if ((child = stack.pop()) && child.pop) {
            if (child.toJS) {
                //兼容Immutable.js
                child = child.toJS();
            }
            for (let i = 0; i < child.length; i++) {
                stack[stack.length] = child[i];
            }
        } else {
            // eslint-disable-next-line
            var childType = typeNumber(child);

            if (childType < 3 // 0, 1, 2
            ) {
                continue;
            }

            if (childType < 6) {
                //!== 'object' 不是对象就是字符串或数字
                if (hasText) {
                    lastText = children[0] = child + lastText;
                    continue;
                }
                child = child + ""
                lastText = child
                hasText = true
            } else {
                hasText = lastText = null;
            }

            children.unshift(child);
        }
    }
    if (!children.length) {
        children = EMPTY_CHILDREN;
    }
    return children;
}

受控组件的复杂度控制

原来非受控组件的if else分支太多,会影响效率,也影响检测程序的评估分数
image

function getOptionValue(props) {
    //typeof props.value === 'undefined'
    return isDefined(props.value)
        ? props.value
        : props.children[0].text
}
function isDefined(a) {
    return !(a === null || a === undefined)
}
export function postUpdateSelectedOptions(vnode) {
    var props = vnode.props,
        multiple = !!props.multiple,
        value = isDefined(props.value)
            ? props.value
            : isDefined(props.defaultValue)
                ? props.defaultValue
                : multiple
                    ? []
                    : '',
        options = [];
    collectOptions(vnode, props, options)
    if (multiple) {
        updateOptionsMore(vnode, options, options.length, value)
    } else {
        updateOptionsOne(vnode, options, options.length, value)
    }

}

function collectOptions(vnode, props, ret) {
    var arr = props.children
    for (var i = 0, n = arr.length; i < n; i++) {
        var el = arr[i]
        if (el.type === 'option') {
            ret.push(el)
        } else if (el.type === 'optgroup') {
            collectOptions(el, el.props, ret)
        }
    }
}

function updateOptionsOne(vnode, options, n, propValue) {
    // Do not set `select.value` as exact behavior isn't consistent across all
    // browsers for all cases.
    var selectedValue = '' + propValue;
    for (let i = 0; i < n; i++) {
        let option = options[i]
        let value = getOptionValue(option.props)
        if (value === selectedValue) {
            setDomSelected(option, true)
            return
        }
    }
    if (n) {
        setDomSelected(options[0], true)
    }

}

function updateOptionsMore(vnode, options, n, propValue) {

    var selectedValue = {}
    try {
        for (let i = 0; i < propValue.length; i++) {
            selectedValue['&' + propValue[i]] = true
        }
    } catch (e) {
        /* istanbul ignore next */
        console.warn('<select multiple="true"> 的value应该对应一个字符串数组')
    }
    for (let i = 0; i < n; i++) {
        let option = options[i]
        let value = getOptionValue(option.props)
        let selected = selectedValue.hasOwnProperty('&' + value)
        setDomSelected(option, selected)

    }
}

function setDomSelected(option, selected) {
    if (option._hostNode) {
        option._hostNode.selected = selected
    }
}

//react的单向流动是由生命周期钩子的setState选择性调用(不是所有钩子都能用setState),受控组件,事务机制

function stopUserInput(e) {
    var target = e.target
    var name = e.type === 'textarea'
        ? 'innerHTML'
        : 'value'
    target[name] = target._lastValue
}

function stopUserClick(e) {
    e.preventDefault()
}

export function processFormElement(vnode, dom, props) {
    var domType = dom.type
    if (/text|password|number|date|time|color|month/.test(domType)) {
        if ('value' in props && !hasOtherControllProperty(props, textMap)) {
            console.warn(`你为${domType}元素指定了value属性,但是没有提供另外的${Object.keys(textMap)}
           等用于控制value变化的属性,那么它是一个非受控组件,用户无法通过输入改变元素的value值`)
            dom.oninput = stopUserInput
        }
    } else if (/checkbox|radio/.test(domType)) {
        if ('checked' in props && !hasOtherControllProperty(props, checkedMap)) {
            console.warn(`你为${domType}元素指定了value属性,但是没有提供另外的${Object.keys(checkedMap)}
           等用于控制value变化的属性,那么它是一个非受控组件,用户无法通过输入改变元素的value值`)
            dom.onclick = stopUserClick
        }
    } else if (/select/.test(domType)) {
        if (!('value' in props || 'defaultValue' in props)) {
            console.warn(`select元素必须指定value或defaultValue属性`)
        }
        postUpdateSelectedOptions(vnode)
    }
}

var textMap = {
    onChange: 1,
    onInput: 1,
    readOnly: 1,
    disabled: 1
}
var checkedMap = {
    onChange: 1,
    onClick: 1,
    readOnly: 1,
    disabled: 1
}

function hasOtherControllProperty(props, map) {
    for (var i in props) {
        if (map[i]) {
           return true
        }
    }
    return false
}

上面的processFormElement 的复杂度为8。

如果使用映射,可以大大减少复杂度

export function processFormElement(vnode, dom, props) {
    var domType = dom.type
    var duplexType = duplexMap[domType]
    if (duplexType) {
        var data = duplexData[duplexType]
        var duplexProp = data[0]
        var keys = data[1]
        var eventName = data[2]
        if (duplexProp in props && !hasOtherControllProperty(props, keys)) {
            console.warn(`你为${vnode.type}[type=${domType}]元素指定了${duplexProp}属性,但是没有提供另外的${Object.keys(keys)}
           等用于控制${duplexProp}变化的属性,那么它是一个非受控组件,用户无法通过输入改变元素的${duplexProp}值`)
            dom[eventName] = data[3]
        }
        if (duplexType === 3) {
            postUpdateSelectedOptions(vnode)
        }

    }

}

function hasOtherControllProperty(props, keys) {
    for (var key in props) {
        if (keys[key]) {
            return true
        }
    }
}
var duplexMap = {
    color: 1,
    date: 1,
    datetime: 1,
    'datetime-local': 1,
    email: 1,
    month: 1,
    number: 1,
    password: 1,
    range: 1,
    search: 1,
    tel: 1,
    text: 1,
    time: 1,
    url: 1,
    week: 1,
    textarea: 1,
    checkbox: 2,
    radio: 2,
    'select-one': 3,
    'select-multiple': 3
}


function preventUserInput(e) {
    var target = e.target
    var name = e.type === 'textarea' ?
        'innerHTML' :
        'value'
    target[name] = target._lastValue
}

function preventUserClick(e) {
    e.preventDefault()
}

function preventUserChange(e) {
    var target = e.target
    var value = target._lastValue
    var options = target.options
    if (target.multiple) {
        updateOptionsMore(options, options.length, value)
    } else {
        updateOptionsOne(options, options.length, value)
    }
}

var duplexData = {
    1: ['value', {
        onChange: 1,
        onInput: 1,
        readOnly: 1,
        disabled: 1
    }, 'oninput', preventUserInput],
    2: ['checked', {
        onChange: 1,
        onClick: 1,
        readOnly: 1,
        disabled: 1
    }, 'onclick', preventUserClick],
    3: ['value', {
        onChange: 1,
        disabled: 1
    }, 'onchange', preventUserChange]
}


export function postUpdateSelectedOptions(vnode) {
    var props = vnode.props,
        multiple = !!props.multiple,
        value = isDefined(props.value) ?
        props.value :
        isDefined(props.defaultValue) ?
        props.defaultValue :
        multiple ? [] :
        '',
        options = [];
    collectOptions(vnode, props, options)
    if (multiple) {
        updateOptionsMore(options, options.length, value)
    } else {
        updateOptionsOne(options, options.length, value)
    }

}

function isDefined(a) {
    return !(a === null || a === void 666)
}

/**
 * 收集虚拟DOM select下面的options元素,如果是真实DOM直接用select.options
 * 
 * @param {VNode} vnode 
 * @param {any} props 
 * @param {Array} ret 
 */
function collectOptions(vnode, props, ret) {
    var arr = props.children
    for (var i = 0, n = arr.length; i < n; i++) {
        var el = arr[i]
        if (el.type === 'option') {
            ret.push(el)
        } else if (el.type === 'optgroup') {
            collectOptions(el, el.props, ret)
        }
    }
}


function updateOptionsOne(options, n, propValue) {
    var selectedValue = '' + propValue;
    for (let i = 0; i < n; i++) {
        let option = options[i]
        let value = getOptionValue(option, option.props)
        if (value === selectedValue) {
            getOptionSelected(option, true)
            return
        }
    }
    if (n) {
        getOptionSelected(options[0], true)
    }
}

function updateOptionsMore(options, n, propValue) {
    var selectedValue = {}
    try {
        for (let i = 0; i < propValue.length; i++) {
            selectedValue['&' + propValue[i]] = true
        }
    } catch (e) {
        /* istanbul ignore next */
        console.warn('<select multiple="true"> 的value应该对应一个字符串数组')
    }
    for (let i = 0; i < n; i++) {
        let option = options[i]
        let value = getOptionValue(option, option.props)
        let selected = selectedValue.hasOwnProperty('&' + value)
        getOptionSelected(option, selected)
    }
}

function getOptionValue(option, props) {
    if (!props) {
        return getDOMOptionValue(option)
    }
    return props.value === undefined ? props.children[0].text : props.value
}

function getDOMOptionValue(node) {
    if (node.hasAttribute && node.hasAttribute('value')) {
        return node.getAttribute('value')
    }
    var attr = node.getAttributeNode('value')
    if (attr && attr.specified) {
        return attr.value
    }
    return node.innerHTML.trim()
}


function getOptionSelected(option, selected) {
    var dom = option._hostNode || option
    dom.selected = selected
}

image

UI测试的异步处理优化

在开发迷你react过程中,我们需要判定官方 react与我们写的迷你react的接口一致性,于是引入了UI测试。

UI测试中最著名的项目是 Selenium, 在nodejs对应的实现是webdriver.io

现在我们重写了一个karma的一个launcher, karma-webdriverio-launcher

然后基于它包装了一个karma-event-driver-ext

这个东西的试验场就是本项目的 aaa分支

大家clone下来npm install,还需要安装两个东西才能运行

一个是selenium-server-standalone-3.3.1.jar
http://selenium-release.storage.googleapis.com/3.3/selenium-server-standalone-3.3.1.jar

另一个是浏览器的driver,这里我们先用chrome,大家选择对应的操作系统版本

http://chromedriver.storage.googleapis.com/index.html?path=2.29/

下回来后放到项目的根目录:

image

然后控制台中执行这两条命令

java -jar selenium-server-standalone-3.3.1.jar

node node_modules/karma-event-driver-ext

它们各自占用一个终端

image

image

最后在第二个终端中看 到测试结果

image

image

UI测试写在test/modules下的node.spec与event.spec里(另两个是普通的单元测试)

image

现在的问题是,karma-event-driver-ext实现得不太友好,测试代码要求用户写太多Promise

import React from 'src/React'
import eventHook, {beforeHook, afterHook, runCommand} from 'karma-event-driver-ext/cjs/event-driver-hooks.js';

describe('ReactDOM.render返回根组件的实例', function () {
    this.timeout(200000);
    before(async() => {
        await beforeHook();
    });
    after(async() => {
        await afterHook(false);
    });
    it('ReactDOM.render返回根组件的实例', async() => {
        class A extends React.Component {
            constructor() {
                super()
                this.state = {
                    aaa: 111
                }
            }
            click(e) {
                console.log('这个已经触发')
                this.setState({
                    aaa: this.state.aaa + 1
                })
                console.log('点击完')
            }
            componentDidMount() {
                resolve()
            }
            componentDidUpdate() {
                console.log('updated')
                console.log(this.state.aaa)
                resolve()

            }
            render() {
                return (
                    <div
                        id="aaa"
                        onClick={this
                        .click
                        .bind(this)}>
                        {this.state.aaa}
                    </div>
                )
            }
        }
        var resolve
        var p = new Promise(function (r) {
            resolve = r
        })
        var rootInstance = ReactDOM.render(
            <A/>, document.body)
        //等待组件mount
        await p
        p = new Promise(function (r) {
            resolve = r
        })
        //确保Promise是在await之前
        await runCommand((browser) => {
            browser.click('#aaa'); 
        });
        //等待组件update
        await p
        expect(rootInstance.state.aaa).toBe(112)
        p = new Promise(function (r) {
            resolve = r
        })
        await runCommand((browser) => {
            browser.click('#aaa'); 
        });
        //等待组件update
        await p
        expect(rootInstance.state.aaa).toBe(113)

    })

})

希望的形式是这样

import React from 'src/React'
import eventHook, {beforeHook, afterHook, runCommand} from 'karma-event-driver-ext/cjs/event-driver-hooks.js';

describe('ReactDOM.render返回根组件的实例', function () {
    this.timeout(200000);
    before(async() => {
        await beforeHook();
    });
    after(async() => {
        await afterHook(false);
    });
    it('ReactDOM.render返回根组件的实例', async() => {
        class A extends React.Component {
            constructor() {
                super()
                this.state = {
                    aaa: 111
                }
            }
            click(e) {
                console.log('这个已经触发')
                this.setState({
                    aaa: this.state.aaa + 1
                })
                console.log('点击完')
            }
            componentDidMount() {
                resolve()
            }
            componentDidUpdate() {
                console.log('updated')
                console.log(this.state.aaa)
                resolve()

            }
            render() {
                return (
                    <div
                        id="aaa"
                        onClick={this
                        .click
                        .bind(this)}>
                        {this.state.aaa}
                    </div>
                )
            }
        }
        var resolve, rootInstance
        await runCommand((brower, r) => {
               resolve = r
               rootInstance = ReactDOM.render(
             <A/>, document.body)
      })
 
        //确保Promise是在await之前
        await runCommand((browser, r) => {
           resolve = r
            browser.click('#aaa'); 
        });
    
        expect(rootInstance.state.aaa).toBe(112)
       
        await runCommand((browser, r) => {
             resolve = r
            browser.click('#aaa'); 
        });
        expect(rootInstance.state.aaa).toBe(113)

    })
})

看起来清爽多了

React16的componentDidCatch

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
  <title>树组件</title>

  <script type='text/javascript' src="./dist/React.js"></script>
  <script type='text/javascript' src="./lib/ReactTestUtils.js"></script>
  <script type='text/javascript' src="./lib/babel.js"></script>
    
</head>

<body>

    <div>测试</div>
    <div id='example'></div>
    <script type='text/babel'>
      var container = document.getElementById("example")
      var div = container
     // var PropTypes = React.PropTypes
      if(!window.ReactDOM){
        window.ReactDOM = window.React
       
      }
      var PropTypes = React.PropTypes
      var expect = function(a) {
          return {
              toBe: function(b) {
                  console.log(a, "\nvs\n",b, a === b)
              }
          }
      }
      var app;
      var count = 0;

      class App extends React.Component {
          render() {
              if (this.props.stage === 1) {
                  return <div><UnunmountableComponent /></div>;
              } else {
                  return null;
              }
          }
      }

      class UnunmountableComponent extends React.Component {
          componentWillUnmount() {
              app.setState({});
              count++;
              throw Error("always fails");
          }

          render() {
              return <div>Hello {this.props.name}</div>;
          }
      }

      var container = document.createElement("div");

      var setRef = ref => {
          if (ref) {
              app = ref;
          }
      };

   //   expect(function() {
     try{
          ReactDOM.render(<App ref={setRef} stage={1} />, container);
          ReactDOM.render(<App ref={setRef} stage={2} />, container);
    //  }).toThrow();
     }catch(e){
       console.log(e)
     }
      expect(count).toBe(1);
    </script>
    <pre>
     
    </pre>
</body>

</html>

image

anu 最新版本 componentdidcatch 有问题

使用的是anu最新版本,从github直接下的.

import React from 'react';
import ReactDOM from 'react-dom';
// import PropTypes from 'props-type';

const Hello = ({ name }) => <h1>Hello {name}!</h1>;

class ShowMyError extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: false };
  }

  componentDidCatch(error, info) {
    this.setState({ error, info });
  }

  render() {
    if (this.state.error) {
      return (
        <div>
          <h1>
            Error AGAIN: {this.state.error.toString()}
          </h1>
          {this.state.info &&
            this.state.info.componentStack.split("\n").map(i => {
              return (
                <div key={i}>
                  {i}
                </div>
              );
            })}
        </div>
      );
    }
    return this.props.children;
  }
}

class Broken extends React.Component {
  constructor(props) {
    super(props);
    this.state = { throw: false, count: 0 };
  }

  render() {
    if (this.state.throw) {
      throw new Error("YOLO");
    }

    return (
      <div>
        <button
          onClick={e => {
            this.setState({ throw: true });
          }}
        >
          button will render error.
        </button>

        <button onClick={e => {
          this.setState(({ count }) => ({
            count: count + 1
          }));
        }}>button will not throw</button>

        <div>
          {"All good here. Count: "}{this.state.count}
        </div>
      </div>
    );
  }
}

class App extends React.Component {
  render() {
    const styles = {
      fontFamily: "sans-serif",
      textAlign: "center"
    };
    return (
      <div style={styles}>
        <Hello name="ShowMyError" />
        <h2>
          Start clicking to see some {"\u2728"}magic{"\u2728"}
        </h2>
        <ShowMyError>
          <Broken />
        </ShowMyError>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));

以上的例子在Anu中的截图是:
2017-12-14 11 07 34
2017-12-14 11 07 40

下面是在react 16.2.0中的截图

2017-12-14 11 09 33

9.3章

toVnode里面添加

  var instance = new Type(props, context)
                    //必须在这里添加vnode,因为willComponent里可能进行setState操作
 instance.vnode = vnode 

Component.call(instance, props, context) //重点!!

React.js里面添加

    transaction.isInTransation = true
    var root = toVnode(vnode, {})
    transaction.isInTransation = false

Component.js的updateComponentProxy

    if (!instance.vnode.dom) {
        var parentNode = instance.container
        instance.state = this.state //将merged state赋给它
        toDOM(instance.vnode, instance.context, parentNode)
    } else {

完整实现react的非受控组件需要监控用户对defaultValue/defaultChecked的操作

受控组件与非受控组件的实现 官方一直晦涩

受控组件是指表单元素指定了value/check,同时也指定了onChange/onInput/disabled/readOnly等阻止或让用户改变vaue/check的属性或事件。如果用户没有指定这些方法呢,框架会加上一些onClick, onInput, onChange事件,让你无法通过手动方式改变value/check

这个我老早就实现了。

非受控组件是指表单元素指定了defaultValue/defaultChecked,以后你每次

ReactDOM.render(<textarea defaultValue="monkey" />, container);

可以发现textarea.value都发生变化,你也可以通过键盘输入改这个值,框架不会阻止你改动它的。

但是如果你手动改了它,以后再

ReactDOM.render(<textarea defaultValue="xxx" />, container);

发现textarea的value不再是xxx了,它是你改后的值,换言之,它更看重你的修改,而不是代码实现。

我们可以得出这样的结论
受控组件是更看重于 JSX中的编程事实
非受控组件是看重于 用户的操作效果

如果你想让它还是以代码方式更新视图,你需要

ReactDOM.render(<textarea defaultValue={null} />, container);

null与undefined都行

这时估计内部的标记会被清除掉。

我们可以通过下面的测试来检测你是否完美实现React的非受控组件行为

             var emptyFunction = function(){};
            var renderTextarea = function(component, container) {
                if (!container) {
                    container = document.createElement("div");
                }
                const node = ReactDOM.render(component, container);

                // Fixing jsdom's quirky behavior -- in reality, the parser should strip
                // off the leading newline but we need to do it by hand here.
                node.defaultValue = node.innerHTML.replace(/^\n/, "");
                return node;
            };
            const container = document.createElement("div");
            const node = renderTextarea(<textarea defaultValue="giraffe" />, container);
    
           expect(node.value).toBe("giraffe");// 1 
           expect(node.defaultValue).toEqual("giraffe"); // 2
           expect(node.innerHTML).toEqual("giraffe"); // 3

            // Changing `defaultValue` should do nothing.
            renderTextarea(<textarea defaultValue="gorilla" />, container);
            expect(node.value).toEqual("giraffe"); // 4
            expect(node.defaultValue).toEqual("gorilla"); // 5
            expect(node.innerHTML).toEqual("gorilla"); // 6
            node.value = "cat"; 
    
            renderTextarea(<textarea defaultValue="monkey" />, container);
            expect(node.value).toEqual("cat"); // 7
            expect(node.defaultValue).toEqual("monkey"); // 8

下面的anu的实现

大致是在diffProps中的isSpecialAttr添加两个值

var isSpecialAttr = {
    style: 1,
    defaultValue: 1,
    defaultChecked: 1,
    children: 1,
    innerHTML: 1,
    dangerouslySetInnerHTML: 1
};

让它们进入特殊的钩子中,由于它们的行为相同,让它共同使用一个钩子

var rform = /textarea|input|select/i;
function uncontrolled(dom, name, val) {
    if (rform.test(dom.nodeName)) {
        controlledHook.stopObserve(dom);
        dom[name] = val;
        controlledHook.observe(dom, name);
    } else {
        dom.setAttribute(name, val);
    }
}

export var actionStrategy = {
    innerHTML: noop,
    defaultValue: uncontrolled,
    defaultChecked: uncontrolled,
   //...
}

controlledHook对象在compat文件被加载的情况下会被重写,因此要export出来
这为了监听用户的操作,也就是知道用户是否对dom.defaultValue/defaultChecked进行操作,那么在高级浏览器下,我们可以通过setter,getter知晓。

controlledHook.observe = function(dom, name) {
    try {
        var controllProp = name === "defaultValue" ? "value" : "checked";
        if (!dom._hack) {
            dom._hack = true;
            Object.defineProperty(dom, name, {
                set: function(value) {
                    if (name === "defaultValue") {
                        dom.innerHTML = value;
                    }
                    if (dom._observing) {
                        //变动value/defaultXXX/innerHTML三方
                        if (dom._userSet) {
                            return;
                        }
                        dom.__default = dom[controllProp] = value;
                    } else {
                        dom._userSet = true;
                        if (value == null) {
                            dom._userSet = false;
                        } //value/check不能变,只变defaultXXX
                        dom.__default = value;
                    }
                },
                get: function() {
                    return dom.__default;
                }
            });
        }
    } catch (e) {}
    dom._observing = true;
};
controlledHook.stopObserve = function(dom) {
    dom._observing = false;
};

在旧式IE下我们可以通过onpropertychange得知用户对这个元素的任何元素的修改。

它是放在compat文件中,那么在高级版本不用打包它,有效减少体积

var observeControlledProp = function(e) {
        var dom = e.srcElement,
            prop = e.propertyName;
        if (prop === "defaultValue" || prop === "defaultChecked") {
            var controllProp = prop === "defaultValue" ? "value": "checked";
            var value = dom[prop];
            if (dom._observing) {
                if (dom._userSet) {
                    dom[controllProp] = dom.__default;
                    return;
                }
                dom.__default = dom[controllProp] = value; //同步value/checked
            } else {
                dom._userSet = true;
                if (value == null) {
                    dom._userSet = false;
                }
            }
        }
    };
    controlledHook.observe = function(dom) {
        dom.attachEvent("onpropertychange", observeControlledProp);
        dom._observing = true;
    };
    controlledHook.stopObserve = function(dom) {
        dom._observing = false;
    };

9.1 事件系统

for (var i = paths.length; i--;) { //从上到下
          var path = paths[i]
          var fn = path.props[captured]
          if (typeof fn === 'function') {
              event.currentTarget = path.dom
              fn.call(path.dom, event)
              if (event._stopPropagation) {
                  break
              }
          }
      }

      for (var i = 0, n = paths.length; i < n; i++) { //从下到上
          var path = paths[i]
          var fn = path.props[bubble]
          if (typeof fn === 'function') {
              event.currentTarget = path.dom
              fn.call(path.dom, event)
              if (event._stopPropagation) {
                  break
              }

          }
      }

event应该改成e

got "ReferenceError: window is not defined" when importing ReactDOMServer in node.

var ReactDOMServer = require('anujs/dist/ReactDOMServer');

ReferenceError: window is not defined
    at .../node_modules/anujs/dist/ReactDOMServer.js:197:1
    at .../node_modules/anujs/dist/ReactDOMServer.js:2:82
    at Object.<anonymous> (.../node_modules/anujs/dist/ReactDOMServer.js:5:2)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Module.require (module.js:579:17)
    at require (internal/module.js:11:18)

line 197 in ReactDOMServer.js:

var pendingRefs = [];
window.pendingRefs = pendingRefs;

anu版本1.1.4 context无效的bug

import React from 'anujs';
import ReactDOM from 'anujs';
import PropTypes from 'anujs';

class Parent extends React.Component {
  render() {
    return (<div className="parent"><Son /></div>)
  }
}
class Son extends React.Component {
  render() {
    return (<div className="son">{this.context.value}</div>)
  }
}
class App extends React.Component {
  getChildContext() {
    return {
      value: 666
    };
  }
  render() {
    return (<div className='app'><Parent /></div>)
  }
};
ReactDOM.render(
  <App/>,
  document.getElementById('root')
)

在上述代码中,无法获取到{this.context.value}

代码出处:https://rubylouvre.github.io/anu/context.html

React.Children.map的key生成策略

它会对原来的虚拟DOM进行克隆,添加与原来不一样的key

   map(children, callback, context) {
        var ret = [];
        _flattenChildren(children, "").forEach(function(old, index) {
            let el = callback.call(context, old, index);
            if (el === null) {
                return;
            }
            if (el.vtype) {
                //如果返回的el等于old,还需要使用原来的key, _prefix
                let curKey = el && el.key !== null ? el.key : null;
                let oldKey = old && old.key !== null ? old.key : null;
                let oldFix = old && old._prefix,
                    key;
                if (oldKey && curKey) {
                    key = oldFix + "$" + oldKey;
                    if (oldKey !== curKey) {
                        key = curKey + "/" + key;
                    } 
                } else {
                    key = curKey || oldKey;
                    if (key) {
                        if (oldFix) {
                            key = oldFix + "$" + key;
                        }
                    } else {
                        key = oldFix || "." + index;
                    }
                }
                key = key.replace(/\d+\$/, "$");
                ret.push(cloneElement(el, { key }));
            } else if (el.type) {
                ret.push(Object.assign({}, el));
            } else {
                ret.push(el);
            }
        });
        return ret;
    },

scheduler的优化

确保里面为空

if(scheduler.count && !scheduler.setTimeoutID){
    scheduler.setTimeoutID = setTimeout(function(){
        console.log(scheduler.count, 'mountComponent')
       scheduler.run()
        scheduler.setTimeoutID = null
    })
}

React16的children操作

Children内部改用operateChildren实现转换

import { operateChildren } from "./createElement";
import { cloneElement } from "./cloneElement";
import { extend } from "./util";

export const Children = {
    only(children) {
        //only方法接受的参数只能是一个对象,不能是多个对象(数组)。
        if (children && children.vtype) {
            return children;
        }
        throw new Error("expect only one child");
    },
    count(children) {
        if (children == null) {
            return 0;
        }
        var index = 0;
        operateChildren(children, false, function() {
            index++;
        });
        return index;
    },
    map(children, callback, context) {
        if (children == null) {
            return children;
        }
        var index = 0;
        return operateChildren(children, true, function(ret, old, keeper) {
            if (old == null || old === false || old === true) {
                old = null;
            } else if (!old._prefix) {
                old._prefix = "." + keeper.unidimensionalIndex;
                keeper.unidimensionalIndex++;
            }
            let outerIndex = index;
            let el = callback.call(context, old, index++);
            if (el == null) {
                return;
            }
            if (el.vtype) {
                //如果返回的el等于old,还需要使用原来的key, _prefix
                var key = computeKey(old, el, outerIndex);
                ret.push(cloneElement(el, { key }));
            } else if (el.type) {
                ret.push(extend({}, el));
            } else {
                ret.push(el);
            }
        });
    },
    forEach(children, callback, context) {
        if (children != null) {
            var index = 0;
            operateChildren(children, false, function(array, el) {
                if (el == null || el === false || el === true) {
                    el = null;
                }
                callback.call(context, el, index++);
            });
        }
    },
    toArray: function(children) {
        if (children == null) {
            return [];
        }
        return Children.map(children, function(el) {
            return el;
        });
    }
};
var rthimNumer = /\d+\$/;
function computeKey(old, el, index) {
    let curKey = el && el.key != null ? escapeKey(el.key) : null;
    let oldKey = old && old.key != null ? escapeKey(old.key) : null;
    let oldFix = old && old._prefix,
        key;
    if (oldKey && curKey) {
        key = oldFix + "$" + oldKey;
        if (oldKey !== curKey) {
            key = curKey + "/" + key;
        }
    } else {
        key = curKey || oldKey;
        if (key) {
            if (oldFix) {
                key = oldFix + "$" + key;
            }
        } else {
            key = oldFix || "." + index;
        }
    }
    return key.replace(rthimNumer, "$");
}
function escapeKey(key) {
    return String(key).replace(/[=:]/g, escaperFn);
}
var escaperLookup = {
    "=": "=0",
    ":": "=2"
};
function escaperFn(match) {
    return escaperLookup[match];
}

大菜是operateChildren,用于替代旧的_flattenChildren,更加语义化与更具通用性

export function operateChildren(children, isMap, callback) {
    let ret = [],
        keeper = {
            unidimensionalIndex: 0
        },
        child,
        iteractorFn,
        temp = Array.isArray(children) ? children.slice(0) : [children];
    while (temp.length) {
        if ((child = temp.shift()) && (child.shift || (iteractorFn = getIteractor(child)))) {
            //比较巧妙地判定是否为子数组
            if (iteractorFn) {
                //兼容Immutable.js, Map, Set
                child = callIteractor(iteractorFn, child);
                iteractorFn = false;
                temp.unshift.apply(temp, child);
                continue;
            }
            if (isMap) {
                if (!child._prefix) {
                    child._prefix = "." + keeper.unidimensionalIndex;
                    keeper.unidimensionalIndex++; //维护第一层元素的索引值
                }
                for (let i = 0; i < child.length; i++) {
                    if (child[i]) {
                        child[i]._prefix = child._prefix + ":" + i;
                    }
                }
            }
            temp.unshift.apply(temp, child);
        } else {
            if (typeNumber(child) === 8  && !child.type) {
                throw Error("invalid type");
            }
            callback(ret, child, keeper);
        }
    }
    return ret;
}

支持React16的组件返回数组或简单类型的行为

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <script type='text/javascript' src="./test/react.development.js"></script>
  <script type='text/javascript' src="./test/react-dom.development.js"></script>
  <script type='text/javascript' src="./lib/babel.js"></script>

</head>

<body>
  <div>开发者工具</div>
  <pre>
  </pre>
  <div id='example'></div>
  <script type='text/babel'>
   
     
      var container = document.getElementById("example")
      var div = container
     // var PropTypes = React.PropTypes
      if(!window.ReactDOM){
        window.ReactDOM = window.React
       
      }
      var PropTypes = React.PropTypes
      var expect = function(a) {
          return {
              toBe: function(b) {
                  console.log(a, "\nvs\n",b, a === b)
              }
          }
      }
      class Child extends React.Component{
        constructor(props){
          super(props)
          this.state = {
            aaa: 111
          }
        }
        componentWillMount(){
          console.log("child will mount")
        }
        componentWillUpdate(){
          console.log("child will update")
        }
        componentDidUpdate(){
          console.log("child did update")
        }
        componentDidMount(){
          console.log("child did mount")
        }
        componentWillUnmount(){
          console.log("child will unmount")
        }
        render(){
            return <strong>Child</strong>
        }
      }
      class Radio extends React.Component{
        constructor(props){
          super(props)
          this.state = {
            aaa: 111
          }
        }
        componentWillMount(){
          console.log("radio will mount")
        }
        componentWillUpdate(){
          console.log("radio will update")
        }
        componentDidUpdate(){
          console.log("radio did update")
        }
        componentDidMount(){
          console.log("radio did mount")
        }
        render(){
            return <input type="radio" />
        }
      }
      class App extends React.Component{
        constructor(props){
          super(props)
          this.state = {
            aaa: 111
          }
        }
        componentWillMount(){
          console.log("app will mount")
        }
        componentWillUpdate(){
          console.log("app will update")
        }
        componentDidUpdate(){
          console.log("app did update")
        }
        componentDidMount(){
          console.log("app did mount, setState")
          this.setState({
            aaa: 333
          })
        }
        render(){
            return this.state.aaa === 333? [<Radio/>,<Radio/>,<Radio/>] :[<Child />,<Child/>]
        }
      }
      var s = ReactDOM.render(<App /> ,div)
    </script>

</body>

</html>

image

元素虚拟DOM也有updater

组件虚拟DOM会产生 CompositeUpdater
元素虚拟DOM会产生 DOMUpdater

它们至少用于控制ref的执行

1.1.2的updateChildren

1.1.2的节点排序算法,通过一次循环,同时构建三个辅助对象,actions, removeHits, fuzzyHits
第二次循环,实现所有节点排序,第三次循环,实现多余节点的移除。

function diffChildren(lastVnode, nextVnode, parentNode, context, mountQueue) {
    let lastChildren = lastVnode.vchildren,
        nextChildren = flattenChildren(nextVnode),
        nextLength = nextChildren.length,
        lastLength = lastChildren.length;
    //如果旧数组长度为零
    if (nextLength && !lastLength) {
        nextChildren.forEach(function(vnode){
            let curNode = mountVnode(null, vnode, lastVnode, context, mountQueue);
            parentNode.appendChild(curNode);
        });
        return;
    }
    let maxLength = Math.max(nextLength, lastLength),
        insertPoint = parentNode.firstChild,
        removeHits = {},
        fuzzyHits = {},
        actions = [],
        i = 0,
        hit,
        dom,
        oldDom,
        nextChild,
        lastChild;
    //第一次循环,构建移动指令(actions)与移除名单(removeHits)与命中名单(fuzzyHits)
    if(nextLength){
        actions.length = nextLength;
        while (i < maxLength) {
            nextChild = nextChildren[i];
            lastChild = lastChildren[i];
            if (nextChild && lastChild && isSameNode(lastChild, nextChild)) {
            //  如果能直接找到,命名90%的情况
                actions[i] = {
                    last: lastChild,
                    next: nextChild,
                    directive: "update"
                };
                removeHits[i] = true;
            } else {
                if (nextChild) {
                    hit = nextChild.type + (nextChild.key || "");
                    if (fuzzyHits[hit] && fuzzyHits[hit].length) {
                        var oldChild = fuzzyHits[hit].shift();
                        // 将旧的节点移动新节点的位置,向后移动
                        actions[i] = {
                            last: oldChild,
                            next: nextChild,
                            directive: "moveAfter"
                        };
                        removeHits[oldChild._i] = true;
                    }
                }
                if (lastChild) {
                //如果不相同,储存它们的key
                    lastChild._i = i;
                    hit = lastChild.type + (lastChild.key || "");
                    let hits = fuzzyHits[hit];
                    if (hits) {
                        hits.push(lastChild);
                    } else {
                        fuzzyHits[hit] = [lastChild];
                    }
                }
            }
            i++;
        }
    }
    for (let j = 0, n = actions.length; j < n; j++) {
        let action = actions[j];
        if (!action) {
            let curChild = nextChildren[j];
            hit = curChild.type + (curChild.key || "");
            if (fuzzyHits[hit] && fuzzyHits[hit].length) {
                oldChild = fuzzyHits[hit].shift();
                oldDom = oldChild._hostNode;
                parentNode.insertBefore(oldDom, insertPoint);
                dom = updateVnode(oldChild, curChild, lastVnode, context, mountQueue);
                removeHits[oldChild._i] = true;
            } else {
                //如果找不到对应的旧节点,创建一个新节点放在这里
                dom = mountVnode(null, curChild, lastVnode, context, mountQueue);
                parentNode.insertBefore(dom, insertPoint);
            }
        } else {
            oldDom = action.last._hostNode;
            if(action.action === "moveAfter"){
                parentNode.insertBefore(oldDom, insertPoint); 
            }
            dom = updateVnode(
                action.last,
                action.next,
                lastVnode,
                context,
                mountQueue
            );
        }
        insertPoint = dom.nextSibling;
    }
    //移除
    lastChildren.forEach(function(el, i) {
        if (!removeHits[i]) {
            var node = el._hostNode;
            if (node) {
                removeDOMElement(node);
            }
            disposeVnode(el);
        }
    });
}

function isSameNode(a, b) {
    if (a.type === b.type && a.key === b.key) {
        return true;
    }
}

IESetTimeout

import { options, typeNumber } from "./util";
import { modern, document } from "./browser";

if (0 === [1, 2].splice(0).length) {
console.warn("请引入polyfill进行修复"); // eslint-disable-line
}

export var scheduler = {
list: [],
add: function (el) {
this.count = this.list.push(el);
},
addAndRun: function (fn) {
this.add(fn);
defer(function () {
scheduler.run();
}, 0);
},
run: function () {
if (this.count === 0) return;
this.count = 0;
this.list.splice(0).forEach(function (instance) {
if (typeNumber(instance) === 5) {
instance(); //处理ref方法
return;
}
if (instance._pendingCallbacks.length) {
//处理componentWillMount产生的回调
instance._pendingCallbacks.splice(0).forEach(function (fn) {
fn.call(instance);
});
}
if (instance.componentDidMount) {
instance._updating = true;
instance.componentDidMount();
instance.componentDidMount = instance._updating = false;
instance._hasDidMount = true;
//处理componentDidMount里调用 setState产生重绘
if (instance._pendingStates.length && !instance._disableSetState) {
options.refreshComponent(instance);
}
}
});
}
};
var defer = modern ? setTimeout : IESetTimeout
function IESetTimeout(cb) {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "data:text/javascript,";
script.onreadystatechange = function () {
canceller();
cb()
};
var canceller = function () {
script.onreadystatechange = null;
document.body.removeChild(script);
};
document.body.appendChild(script);
}

新的diff机制

##虚拟DOM的_owner属性的设置问题

它必须在组件render时,通过CurrenOwner.cur中获取

有了_owner,才能正确将ref的名字与值放到对应的组件实例的refs对象上。

##组件的初始阶段的构建问题

组件的componentDidMount回调,必须等到它下面的所有子组组件都执行了componentDidMount回调才执行自己的。并且DidMount回调,必须等到其refs对象构建好才执行。

为了达到这个目标,在ReactDOM.render阶段,有一个mountQueue数组,它有一个mountAll=true的属性,它会一路传递下去。收集内部的组件实例。最后执行clearRefsAndMounts。

在更新过程,如果两个虚拟DOM 的类型不一致,也会产生一个mountQueue数组,再进行比较,最后执行clearRefsAndMounts。

##两处阻止组件更新的时机

componentWillMount与componentWillRecieveProps会阻止组件进行更新,通过_dirty 这个标识实现

if (!this._dirty && (this._dirty = true)) {
      defer(() => {
        this._dirty = false
        options.refreshComponent(this);
      }, 16)
    }

##父组件在更新过程中,子组件在componentWillRecieveProps中执行父组件的方法间接或直接执行父组件的setState方法,这时父组件会在子组件update之后,立即再次同步更新自身。

通过_updating与CurrentOwner.updating 实现。

##组件的componentWillComponent与componentDidComponent钩子都有setState,并且setState都有回调,亦即所谓的pendingCallback,它们会呆在componentDidUpdate执行(有待优化)。

##虚拟DOM 不再清空_hostNode属性。

##去掉_hostParent属性

##所有虚拟DOM 都有_hostNode属性,组件不再通过instanceMap来寻找自己的DOM

function sameVnode(a, b) {
  return a.type === b.type && a.key === b.key
}
function patchVnode(lastVnode, nextVnode, args) {
  return updateVnode(lastVnode, nextVnode, lastVnode._hostNode, args[0], args[1])
}
function updateChildren(lastVnode, nextVnode, parentNode, parentContext, mountQueue) {
  let newCh = nextVnode.props.children
  let oldCh = lastVnode.props.children
  let oldStartIdx = 0;
  let newStartIdx = 0;
  let oldEndIdx = oldCh.length - 1;
  let oldStartVnode = oldCh[0];
  let oldEndVnode = oldCh[oldEndIdx];
  let newEndIdx = newCh.length - 1;
  let newStartVnode = newCh[0];
  let newEndVnode = newCh[newEndIdx];
  let oldKeyMap, idxInOld, dom, ref, elmToMove;

  var args = [parentContext, mountQueue]
  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {

    if (sameVnode(oldStartVnode, newStartVnode)) {
      dom = patchVnode(oldStartVnode, newStartVnode, args);
      ref = childNodes[newStartIdx]
      if (dom !== ref) {
        parentNode.replaceChild(dom, ref)
        oldStartVnode._hostNode = dom
      }
      oldStartVnode = oldCh[++oldStartIdx];
      newStartVnode = newCh[++newStartIdx];
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
      dom = patchVnode(oldEndVnode, newEndVnode, args);
      ref = childNodes[newEndIdx]
      if (dom !== ref) {
        parentNode.replaceChild(dom, ref)
        oldEndVnode._hostNode = dom
        // insertDOM(parentNode, dom, ref)
      }
      oldEndVnode = oldCh[--oldEndIdx];
      newEndVnode = newCh[--newEndIdx];
    } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
      //如果新的最后一个等于旧的第一个
      dom = patchVnode(oldStartVnode, newEndVnode, args);
      parentNode.insertBefore(dom, oldStartVnode._hostNode.nextSibling)
      //  api.insertBefore(parentNode, oldStartVnode._hostNode as Node,
      // api.nextSibling(oldEndVnode._hostNode as Node));
      oldStartVnode = oldCh[++oldStartIdx];
      newEndVnode = newCh[--newEndIdx];
    } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
      dom = patchVnode(oldEndVnode, newStartVnode, args);
      parentNode.insertBefore(oldEndVnode._hostNode, oldStartVnode._hostNode);
      oldEndVnode = oldCh[--oldEndIdx];
      newStartVnode = newCh[++newStartIdx];
    } else {
      if (oldKeyMap === undefined) {
        oldKeyMap = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
      }
      idxInOld = oldKeyMap[newStartVnode.key];
      if (isUndef(idxInOld)) { // New element
        dom = mountVnode(newStartVnode, parentContext, null, mountQueue)
        parentNode.insertBefore(dom, oldStartVnode._hostNode);
        newStartVnode = newCh[++newStartIdx];
      } else {
        elmToMove = oldCh[idxInOld];
        if (elmToMove.type !== newStartVnode.type) {
          dom = mountVnode(newStartVnode, parentContext, null, mountQueue)
          parentNode.insertBefore(parentNode, dom, oldStartVnode._hostNode);
        } else {
          patchVnode(elmToMove, newStartVnode, mountQueue);
          oldCh[idxInOld] = undefined;
          parentNode.insertBefore(parentNode, (elmToMove._hostNode), oldStartVnode._hostNode);
        }
        newStartVnode = newCh[++newStartIdx];
      }
    }
  }
  if (oldStartIdx > oldEndIdx) {
    let before = newCh[newEndIdx + 1] == null
      ? null
      : newCh[newEndIdx + 1]._hostNode;
    addVnodes(parentNode, before, newCh, newStartIdx, newEndIdx, mountQueue);
  } else if (newStartIdx > newEndIdx) {
    removeVnodes(parentNode, oldCh, oldStartIdx, oldEndIdx);
  }
}

9.2

diffProps 方法不能修改props属性,因为它可能已经冻结了

 var builtIdProperties = /^(?:className|id|title|htmlFor)$/
  export function diffProps(dom, instance, props, nextProps) {

      if (props === nextProps) {
          return
      }

      for (let name in nextProps) {
          if (name === 'children') {
              continue
          }
          var val = nextProps[name]
          if (name === 'ref') {
              if (props[name] !== val) {
                  instance && patchRef(instance, val, dom)
              }
              continue
          }
          if (name === 'style') {
              patchStyle(dom, props[style], val)
              continue
          }
          if (name === 'dangerouslySetInnerHTML') {
              var oldhtml = props[name] && props[name]._html
              instance && (instance._hasSetInnerHTML = true)
              if (val && val._html !== oldhtml) {
                  dom.innerHTML = val._html
              }
          }
          if (isEvent(name)) {
              if (!props[name]) { //添加全局监听事件
                  var eventName = getBrowserName(name)
                  addGlobalEventListener(eventName)
              }
              if (inMobile && eventName === 'click') {
                  elem.addEventListener('click', clickHack)

              }
              var events = (dom.__events || (dom.__events = {}))
                  //   events[name] = props[name] = val
              events[name] = val
              continue
          }

          if (val !== props[name]) {
              //移除属性
              if (val === false || val === void 666 || val === null) {
                  dom.removeAttribute(name)
                      // delete props[name]
              } else { //添加新属性
                  if (builtIdProperties.test(name)) {
                      dom[name] = val + ''
                  } else {
                      dom.setAttribute(name, val + '')
                  }

                  //  props[name] = val // 不能改旧的props
              }
          }
      }
      for (let name in props) {
          if (!(name in nextProps)) {
              if (isEvent(name)) { //移除事件
                  var events = dom.__events || {}
                  delete events[name]
              } else { //移除属性
                  if (builtIdProperties.test(name)) {
                      dom[name] = ''
                  } else {
                      dom.removeAttribute(name)
                  }
              }
              // delete props[name]
          }
      }
  }

createClass

 var ReactComponent = React.Component;

  var _assign = Object.assign;

  var _invariant = function _invariant(condition, format, a, b, c, d, e, f) {
    if (!condition) {
      var error;
      if (format === undefined) {
        error = new Error(
          "Minified exception occurred; use the non-minified dev environment " +
            "for the full error message and additional helpful warnings."
        );
      } else {
        var args = [a, b, c, d, e, f];
        var argIndex = 0;
        error = new Error(
          format.replace(/%s/g, function() {
            return args[argIndex++];
          })
        );
        error.name = "Invariant Violation";
      }

      error.framesToPop = 1; // we don't care about invariant's own frame
      throw error;
    }
  };

  var MIXINS_KEY = "mixins";

  /**
 * Policies that describe methods in `ReactClassInterface`.
 */

  var injectedMixins = [];

  var ReactClassInterface = {
    mixins: "DEFINE_MANY",
    statics: "DEFINE_MANY",
    propTypes: "DEFINE_MANY",
    contextTypes: "DEFINE_MANY",
    childContextTypes: "DEFINE_MANY",
    getDefaultProps: "DEFINE_MANY_MERGED",
    getInitialState: "DEFINE_MANY_MERGED",
    getChildContext: "DEFINE_MANY_MERGED",
    render: "DEFINE_ONCE",
    componentWillMount: "DEFINE_MANY",
    componentDidMount: "DEFINE_MANY",
    componentWillReceiveProps: "DEFINE_MANY",
    shouldComponentUpdate: "DEFINE_ONCE",
    componentWillUpdate: "DEFINE_MANY",
    componentDidUpdate: "DEFINE_MANY",
    componentWillUnmount: "DEFINE_MANY",
    updateComponent: "OVERRIDE_BASE"
  };

  /**
 * Mapping from class specification keys to special processing functions.
 *
 * Although these are declared like instance properties in the specification
 * when defining classes using `React.createClass`, they are actually static
 * and are accessible on the constructor instead of the prototype. Despite
 * being static, they must be defined outside of the "statics" key under
 * which all other static methods are defined.
 */
  var RESERVED_SPEC_KEYS = {
    displayName: function displayName(Constructor, _displayName) {
      return (Constructor.displayName = _displayName);
    },
    mixins: function mixins(Constructor, _mixins) {
      if (_mixins) {
        for (var i = 0; i < _mixins.length; i++) {
          mixSpecIntoComponent(Constructor, _mixins[i]);
        }
      }
    },
    childContextTypes: function childContextTypes(
      Constructor,
      _childContextTypes
    ) {
      return (Constructor.childContextTypes = _assign(
        {},
        Constructor.childContextTypes,
        _childContextTypes
      ));
    },
    contextTypes: function contextTypes(Constructor, _contextTypes) {
      return (Constructor.contextTypes = _assign(
        {},
        Constructor.contextTypes,
        _contextTypes
      ));
    },
    /**
   * Special case getDefaultProps which should move into statics but requires
   * automatic merging.
   */
    getDefaultProps: function getDefaultProps(Constructor, _getDefaultProps) {
      if (Constructor.getDefaultProps) {
        Constructor.getDefaultProps = createMergedResultFunction(
          Constructor.getDefaultProps,
          _getDefaultProps
        );
      } else {
        Constructor.getDefaultProps = _getDefaultProps;
      }
    },

    propTypes: function propTypes(Constructor, _propTypes) {
      Constructor.propTypes = _assign({}, Constructor.propTypes, _propTypes);
    },
    statics: function statics(Constructor, _statics) {
      mixStaticSpecIntoComponent(Constructor, _statics);
    },
    autobind: function autobind() {}
  };

  function validateMethodOverride(isAlreadyDefined, name) {
    var specPolicy = ReactClassInterface.hasOwnProperty(name)
      ? ReactClassInterface[name]
      : null;

    // Disallow overriding of base class methods unless explicitly allowed.
    if (ReactClassMixin.hasOwnProperty(name)) {
      _invariant(
        specPolicy === "OVERRIDE_BASE",
        "ReactClassInterface: You are attempting to override " +
          "`%s` from your class specification. Ensure that your method names " +
          "do not overlap with React methods.",
        name
      );
    }

    // Disallow defining methods more than once unless explicitly allowed.
    if (isAlreadyDefined) {
      _invariant(
        specPolicy === "DEFINE_MANY" || specPolicy === "DEFINE_MANY_MERGED",
        "ReactClassInterface: You are attempting to define " +
          "`%s` on your component more than once. This conflict may be due " +
          "to a mixin.",
        name
      );
    }
  }

  /**
 * Mixin helper which handles policy validation and reserved
 * specification keys when building React classes.
 */
  function mixSpecIntoComponent(Constructor, spec) {
    if (!spec) {
      return;
    }

    _invariant(
      typeof spec !== "function",
      "ReactClass: You're attempting to " +
        "use a component class or function as a mixin. Instead, just use a " +
        "regular object."
    );

    var proto = Constructor.prototype;
    var autoBindPairs = proto.__reactAutoBindPairs;

    // By handling mixins before any other properties, we ensure the same
    // chaining order is applied to methods with DEFINE_MANY policy, whether
    // mixins are listed before or after these methods in the spec.
    if (spec.hasOwnProperty(MIXINS_KEY)) {
      RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);
    }

    for (var name in spec) {
      if (!spec.hasOwnProperty(name)) {
        continue;
      }

      if (name === MIXINS_KEY) {
        // We have already handled mixins in a special case above.
        continue;
      }

      var property = spec[name];
      var isAlreadyDefined = proto.hasOwnProperty(name);
      validateMethodOverride(isAlreadyDefined, name);
      if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
        RESERVED_SPEC_KEYS[name](Constructor, property);
      } else {
        // Setup methods on prototype:
        // The following member methods should not be automatically bound:
        // 1. Expected ReactClass methods (in the "interface").
        // 2. Overridden methods (that were mixed in).
        var isReactClassMethod = ReactClassInterface.hasOwnProperty(name);
        var isFunction = typeof property === "function";
        var shouldAutoBind =
          isFunction &&
          !isReactClassMethod &&
          !isAlreadyDefined &&
          spec.autobind !== false;

        if (shouldAutoBind) {
          autoBindPairs.push(name, property);
          proto[name] = property;
        } else {
          if (isAlreadyDefined) {
            var specPolicy = ReactClassInterface[name];

            // These cases should already be caught by validateMethodOverride.
            _invariant(
              isReactClassMethod &&
                (specPolicy === "DEFINE_MANY_MERGED" ||
                  specPolicy === "DEFINE_MANY"),
              "ReactClass: Unexpected spec policy %s for key %s " +
                "when mixing in component specs.",
              specPolicy,
              name
            );

            // For methods which are defined more than once, call the existing
            // methods before calling the new property, merging if appropriate.
            if (specPolicy === "DEFINE_MANY_MERGED") {
              console.log(name, "createMergedResultFunction");

              proto[name] = createMergedResultFunction(proto[name], property);
            } else if (specPolicy === "DEFINE_MANY") {
              proto[name] = createChainedFunction(proto[name], property);
            }
          } else {
            proto[name] = property;
            {
              // Add verbose displayName to the function, which helps when looking
              // at profiling tools.
              if (typeof property === "function" && spec.displayName) {
                proto[name].displayName = spec.displayName + "_" + name;
              }
            }
          }
        }
      }
    }
  }

  function mixStaticSpecIntoComponent(Constructor, statics) {
    if (!statics) {
      return;
    }
    for (var name in statics) {
      var property = statics[name];
      if (!statics.hasOwnProperty(name)) {
        continue;
      }

      var isReserved = name in RESERVED_SPEC_KEYS;
      _invariant(
        !isReserved,
        "ReactClass: You are attempting to define a reserved " +
          'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' +
          "as an instance property instead; it will still be accessible on the " +
          "constructor.",
        name
      );

      var isInherited = name in Constructor;
      _invariant(
        !isInherited,
        "ReactClass: You are attempting to define " +
          "`%s` on your component more than once. This conflict may be " +
          "due to a mixin.",
        name
      );
      Constructor[name] = property;
    }
  }

  function mergeIntoWithNoDuplicateKeys(one, two) {
    _invariant(
      one &&
        two &&
        (typeof one === "undefined" ? "undefined" : _typeof(one)) ===
          "object" &&
        (typeof two === "undefined" ? "undefined" : _typeof(two)) === "object",
      "mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects."
    );

    for (var key in two) {
      if (two.hasOwnProperty(key)) {
        one[key] = two[key];
      }
    }
    return one;
  }

  function createMergedResultFunction(one, two) {
    return function mergedResult() {
      var a = one.apply(this, arguments);
      var b = two.apply(this, arguments);
      if (a == null) {
        return b;
      } else if (b == null) {
        return a;
      }
      var c = {};
      mergeIntoWithNoDuplicateKeys(c, a);
      mergeIntoWithNoDuplicateKeys(c, b);
      return c;
    };
  }

  function createChainedFunction(one, two) {
    return function chainedFunction() {
      one.apply(this, arguments);
      two.apply(this, arguments);
    };
  }

  function bindAutoBindMethod(component, method) {
    var boundMethod = method.bind(component);
    return boundMethod;
  }

  function bindAutoBindMethods(component) {
    var pairs = component.__reactAutoBindPairs;
    for (var i = 0; i < pairs.length; i += 2) {
      var autoBindKey = pairs[i];
      var method = pairs[i + 1];
      component[autoBindKey] = bindAutoBindMethod(component, method);
    }
  }

  var ReactClassMixin = {
    /**
   * TODO: This will be deprecated because state should always keep a consistent
   * type signature and the only use case for this, is to avoid that.
   */
    replaceState: function replaceState(newState, callback) {
      // this.updater.enqueueReplaceState(this, newState, callback);
    },

    /**
   * Checks whether or not this composite component is mounted.
   * @return {boolean} True if mounted, false otherwise.
   * @protected
   * @final
   */
    isMounted: function isMounted() {
      return !!this._rendered && this._rendered._hostNode;
    }
  };

  var ReactClassComponent = function ReactClassComponent() {};
  _assign(
    ReactClassComponent.prototype,
    ReactComponent.prototype,
    ReactClassMixin
  );

  /**
 * Creates a composite component class given a class specification.
 * See https://facebook.github.io/react/docs/top-level-api.html#react.createclass
 *
 * @param {object} spec Class specification (which must define `render`).
 * @return {function} Component constructor function.
 * @public
 */
  function newCtor(className, ReactComponent, bindAutoBindMethods) {
    var curry = Function(
      "ReactComponent",
      "bindAutoBindMethods",
      `return function ${className}(props, context) {
    ReactComponent.call(this, props, context);
    var initialState = this.getInitialState ? this.getInitialState() : {};

    this.state = initialState;
    // This constructor gets overridden by mocks. The argument is used
    // by mocks to assert on what gets mounted.

    // Wire up auto-binding
    if (this.__reactAutoBindPairs.length) {
      bindAutoBindMethods(this);
    }
  };`
    );
    return curry(ReactComponent, bindAutoBindMethods);
  }

  function createClass(spec) {
    // To keep our warnings more understandable, we'll use a little hack here to
    // ensure that Constructor.name !== 'Constructor'. This makes sure we don't
    // unnecessarily identify a class without displayName as 'Constructor'.
    var Constructor = newCtor(
      spec.displayName || "Component",
      ReactComponent,
      bindAutoBindMethods
    );

    Constructor.prototype = new ReactClassComponent();
    Constructor.prototype.constructor = Constructor;
    Constructor.prototype.__reactAutoBindPairs = [];

    injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor));

    // mixSpecIntoComponent(Constructor, IsMountedPreMixin);
    mixSpecIntoComponent(Constructor, spec);
    // mixSpecIntoComponent(Constructor, IsMountedPostMixin);

    // Initialize the defaultProps property after all mixins have been merged.
    if (Constructor.getDefaultProps) {
      Constructor.defaultProps = Constructor.getDefaultProps();
    }

    _invariant(
      Constructor.prototype.render,
      "createClass(...): Class specification must implement a `render` method."
    );

    // Reduce time spent doing lookups by setting these on the prototype.
    for (var methodName in ReactClassInterface) {
      if (!Constructor.prototype[methodName]) {
        Constructor.prototype[methodName] = null;
      }
    }

    return Constructor;
  }
  React.createClass = createClass;

__events赋值问题

dom.__events 是什么时候赋值的, 很奇怪

经过了这个方法 action = strategyCache[which] = getPropAction(dom, name, isSVG);

dom的events属性突然就有值了, 但这里面并没有看到给dom新增__events属性啊

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.