首页 > 解决方案 > 如何将 HTML 输入值流与 UP 或 DOWN 键流以及输入类型流结合起来?

问题描述

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

我可以设置一个结合了类型流和输入流的可观察流:

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)
);

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

我尝试了combineAllcombineLatestmergemergeAll和的组合withLatestFrom,但如果不在下面添加大量代码,就无法创建正确的流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

标签: reactjstypescriptrxjs

解决方案


推荐阅读