Как объединить поток ввода значений HTML с потоком клавиш ВВЕРХ или ВНИЗ, а также поток ввода типа? - PullRequest
1 голос
/ 03 апреля 2019

У меня есть сложный компонент React, который работает аналогично HTML5 input. Это должно позволять следующее:

  • Разрешить ввод частичного номера, например 5., 5.0 или 0.. 5.0 является частичным, потому что я хочу разрешить два десятичных знака.
  • Когда номер подтвержден, отправьте его родителю через this.props.
  • Поддержка нескольких типов, таких как int, percent, float, переключаемых через некоторые действия. Каждый тип имеет свою валидацию.
  • Разрешить специальные действия клавиатуры, такие как 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)
);

Учитывая приведенный ниже пример кода большего размера, как мне объединить эти три потока, чтобы сохранить функциональность набора и проверки?

Я пробовал комбинации combineAll, combineLatest, merge, mergeAll и withLatestFrom, но не смог создать правильный поток, не добавив значительно больше кода в parseRawValue ниже.

Здесь определяется текущий компонент: TransformedImagesElement / src / components / editor / input / 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...