Skip to content

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

@RubyLouvre

Description

@RubyLouvre

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions