如何将HTML输入值流与UP或DOWN键流以及输入类型流结合在一起?

时间:2019-04-03 16:03:18

标签: reactjs typescript rxjs

我有一个复杂的React组件,其功能类似于HTML5 input。它应该允许以下内容:

  • 允许输入部分数字,例如5.5.00.5.0是不完整的,因为我想允许两位小数。
  • 验证数字后,通过this.props将其发送给父母。
  • 支持多种类型,例如intpercentfloat,这些类型可以通过某些操作进行切换。每种类型都有其自己的验证。
  • 允许特殊的键盘操作,例如UP增大值或DOWN减小值。

我可以设置将类型流和输入流结合在一起的可观察流:

const rawInput = fromEvent<React.FormEvent<HTMLInputElement>>(this.input, "input").pipe(
    map(v => v.currentTarget.value)
);

this.typeSubject.pipe(
    switchMap(t => this.parseRawValue(rawInput, t))
).subscribe(
    v => {
        this.props.setValue(v);
        this.setState({ isValid: true });
    });

我不知道如何组合键盘流:

const pressedKey = fromEvent<React.KeyboardEvent<HTMLInputElement>>(this.input, "keydown").pipe(
    map(k => k.keyCode),
    filter(k => this.inputKeyCodes.indexOf(k) > -1)
);

鉴于下面的较大代码示例,如何合并这三个流,以便保留键入和验证功能?

我尝试了combineAllcombineLatestmergemergeAllwithLatestFrom的组合,但是如果不添加更多内容就无法创建正确的流下面的parseRawValue中的代码。

此处定义了当前组件:TransformedImagesElement/src/components/editor/inputs/NumberInput.tsx

这是带有注释的较大代码示例。主要问题在componentDidMount之内:

import * as React from 'react';
import { fromEvent, Observable, Subject, BehaviorSubject } from "rxjs";
import { map, filter, switchMap } from 'rxjs/operators';

enum NumberInputExampleType {
    int = "###",
    percent = "%"
}

interface INumberInputExampleProps {
    type: NumberInputExampleType;
    value: number | null;
    setValue(value: number): void;
    max: number;
    min: number;
}

interface INumberInputExampleState {
    type?: NumberInputExampleType | null;
    rawValue: string;
    isValid: boolean;
}

export class NumberInputExample extends React.PureComponent<INumberInputExampleProps, INumberInputExampleState> {
    state: INumberInputExampleState = {
        type: null,
        rawValue: "",
        isValid: true,
    }

    private inputKeyCodes = [
        38, // UP key
        40 // DOWN key
    ];

    // Reference to the input
    private input: HTMLInputElement | null;

    // Get type from the props, if the state is not yet set, or from state
    private get type() {
        return this.state.type || this.props.type;
    }

    // Subject for the type
    private typeSubject: Subject<NumberInputExampleType> = new BehaviorSubject(this.type);

    // This is called upon some action in the rendered elements
    private switchType(newType: NumberInputExampleType): void {
        this.typeSubject.next(newType);

        this.setState({
            type: newType,
            rawValue: ""
        });
    }

    private parseValue(): string {
        // Return the raw value if it is not valid
        if (this.state.rawValue !== "") {
            return this.state.rawValue;
        }
        else if (this.props.value !== null) {
            return this.getValue(this.props.value);
        }

        return "";
    }

    private getValue(value: number): string {
        switch (this.type) {
            case NumberInputExampleType.int:
            //return(value * this.props.max).toString() // Return the internal value, a float, converted to the presentation value, a string, using this.props.max
            case NumberInputExampleType.percent:
            // Similar logic to the above, except converting to a valid percentage
        }
    }

    componentDidMount() {
        if (this.input) {
            const rawInput = fromEvent<React.FormEvent<HTMLInputElement>>(this.input, "input").pipe(
                map(v => v.currentTarget.value)
            );

            const pressedKey = fromEvent<React.KeyboardEvent<HTMLInputElement>>(this.input, "keydown").pipe(
                map(k => k.keyCode),
                filter(k => this.inputKeyCodes.indexOf(k) > -1)
            );

            // If pressedKey is UP, increment the value. If it is DOWN, decrement the value.
            // How to combine rawInput and pressedKey while keeping the functionality of this.parseRawValue?
            //

            this.typeSubject.pipe(
                switchMap(t => this.parseRawValue(rawInput, t))
            ).subscribe(
                v => {
                    this.props.setValue(v);
                    this.setState({ isValid: true });
                });
        }
    }

    private parseRawValue(rawInput: Observable<string>, type: NumberInputExampleType): Observable<number> {
        switch (type) {
            case NumberInputExampleType.int:
                return rawInput.pipe(
                    //filter(v => ... ), // Filter invalid input, allowing only numbers or numbers-while-typing, such as '5.'
                    //map(v => ... ), // Ensure the "number" is between this.props.max and this.props.min
                    map(this.storeValueInState),
                    //map(v / this.props.max) // Finally, convert the represented value to the internal value, a float
                )
            case NumberInputExampleType.percent:
            // Similar logic to the above, except converting to a valid percentage. Filter out partial numbers
        }
    }

    private storeValueInState(value: string): string {
        this.setState({
            rawValue: value,
            isValid: false
        });

        return value;
    };

    render() {
        return <input ref={e => this.input = e} value={this.parseValue()} />;
    }
}

我希望在键入时会发生以下情况,例如NumberInputExampleType.percent

action | rawValue | state.isValid | this.props.value
-------+----------+---------------+-----------------
init   |          | true          | null
type 1 | 1        | true          | 1
type . | 1.       | false         | 1
type 0 | 1.0      | false         | 1
type w | 1.0      | false         | 1
type 1 | 1.01     | true          | 1.01
key UP | 1.02     | true          | 1.02
erase  |          | true          | null
type 9 | 9        | true          | 9
type 9 | 99       | true          | 99
type . | 99.      | false         | 99
type 9 | 99.9     | false         | 99
type 9 | 99.99    | true          | 99.99
key UP | 100      | true          | 100
key UP | 100      | true          | 100

0 个答案:

没有答案