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

受控组件的复杂度控制 #10

Closed
RubyLouvre opened this issue Jun 1, 2017 · 2 comments
Closed

受控组件的复杂度控制 #10

RubyLouvre opened this issue Jun 1, 2017 · 2 comments

Comments

@RubyLouvre
Copy link
Owner

RubyLouvre commented Jun 1, 2017

原来非受控组件的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

@RubyLouvre
Copy link
Owner Author

image
image
上面是react,下面是我的anu
性能更好,且内存消耗更低

@RubyLouvre
Copy link
Owner Author

image

BetaSu added a commit to BetaSu/anu that referenced this issue Nov 9, 2020
BetaSu added a commit to BetaSu/anu that referenced this issue Nov 9, 2020
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