我有一个复杂的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/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