# all files / src/ ControlledComponent.js

2.78% Statements 2/72
0% Branches 0/46
0% Functions 0/13
2.78% Lines 2/72

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202

    1×

    1×

     /**
     input, select, textarea这几个元素如果指定了value/checked的**状态属性**，就会包装成受控组件或非受控组件
     受控组件是指，用户除了为它指定**状态属性**，还为它指定了onChange/onInput/disabled等用于控制此状态属性
     变动的属性
     反之，它就是非受控组件，非受控组件会在框架内部添加一些事件，阻止**状态属性**被用户的行为改变，只能被setState改变
     */

    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 === undefined)
    }

    /**
     * 收集虚拟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
    }