我收到打字稿错误。在这里,我正在共享代码,但出现错误
代码:1 DateField.tsx
handleDate(value: string){
const inputField = this.refs.input;
const caretStart = inputField.selectionStart ;
const caretEnd = inputField.selectionEnd;
错误:1
任何
类型“ ReactInstance”上不存在属性“ selectionStart”。
属性'selectionStart'在类型'Component
代码:2 DateField.tsx
completeField(value:any, fieldIndex:any) {
return _.padStart(
value,
FIELDS[fieldIndex].label.length,
FIELDS[fieldIndex].pad
);
}
错误:2
(财产)垫子:号码
无法将类型'number'的参数分配给类型'string |的参数。 undefined'.ts(2345)
代码:3 DropDown.tsx
componentDidUpdate() {
if (this.refs.list) {
const option = this.refs["option-" + this.props.value];
this.refs.list._scrollTop =
option.offsetTop - (this.props.height - option.offsetHeight) / 2;
}
}
错误:3
任何
类型“ ReactInstance”上不存在属性“ _scrollTop”。
属性'_scrollTop'在'Component
代码:4 DropDown.tsx
render() {
const { value, isOpen } = this.state;
const current = _.find(this.props.options, (option) => {
// HACK because select returns string
return (
option.value === value || option.value.toString() === value.toString()
);
});
错误:4
const值:字符串
由于类型'number'和'string'没有重叠,因此此条件将始终返回'false'。ts(2367)
DateField.tsx
import _ from "lodash";
import moment from "moment";
import React, { Component } from "react";
import "./DateField.scss";
const SEPARATOR = "-";
const POSSIBLE_SEPARATORS = ["-", "/", " ", String.fromCharCode(13)];
const FIELDS = [
{
label: "DD",
pad: 0
},
{
label: "MM",
pad: 0
},
{
label: "YYYY",
pad: moment().year()
}
];
const HINT = FIELDS.map((field) => field.label).join(SEPARATOR);
const MAX_LENGTH = HINT.length;
type OnChangePropType = {
day: number;
month: number;
year: number;
value: any;
resolvedDate: any;
};
interface IProps {
value: any;
onChange: (param: OnChangePropType) => void;
}
interface IState {
value: any;
errors: Array<Object>;
hint: string;
}
export class DateField extends Component<IProps, IState> {
static defaultProps = {
onChange: () => {}
};
constructor(props: IProps) {
super(props);
this.state = {
value: props.value || "",
hint: HINT,
errors: []
};
}
/**
* Find difference between words (i.e. insertion or edit point)
* @param {*} value
* @param {*} lastValue
*/
findDifference(value:any, lastValue:any) {
for (let i = 0; i < value.length && i < lastValue.length; i++) {
if (value[i] !== lastValue[i]) {
return i;
}
}
return value.length - 1;
}
findClosestSeparatorFieldIndex({ value, editIndex }:{ value:any, editIndex:any }) {
const partialValue = value.substr(0, editIndex + 1);
let numSeparators = partialValue.match(new RegExp(SEPARATOR, "g"));
if (numSeparators) {
// FIELD index from zero (['DD', 'MM', 'YYYY'])
return numSeparators.length - 1;
}
return null;
}
componentDidUpdate(prevProp:IProps) {
const { value } = this.props;
if(prevProp.value !== value && value) {
this.handleDate(value);
}
}
handleDate(value: string){
const inputField = this.refs.input;
const caretStart = inputField.selectionStart ;
const caretEnd = inputField.selectionEnd;
// e.preventDefault();
let { hint } = this.state;
// let value = val;
console.log("value", value);
const errors = [];
// swap all possible separators for correct one
value = value.replace(
new RegExp(`[${POSSIBLE_SEPARATORS.join("")}]`, "g"),
SEPARATOR
);
// remove non-valid chars (not sep or digit)
value = value.replace(new RegExp(`[^${SEPARATOR}0-9]`, ""), "");
let editIndex = this.findDifference(value, this.state.value);
let fieldToCompleteIndex = null;
// find attempts at splitting
if (value.charAt(editIndex) === SEPARATOR) {
// const allSeparators = new RegExp(SEPARATOR, 'g').exec(value);
// console.log('all', allSeparators);
// const closestSeparator = _.find(allSeparators, (match, i) => {
// return editIndex < match.index ? i : false;
// });
fieldToCompleteIndex = this.findClosestSeparatorFieldIndex({
value,
editIndex
});
// console.log(fieldToCompleteIndex);
// if (editIndex >
// if (editIndex < 2) {
// completeComponent = 'day';
// }
// console.log(editIndex, 'YES');
}
// fix value by removing non-digits
value = value.replace(/[^0-9]/g, "");
const maxLength = HINT.replace(SEPARATOR, "").length;
// size limit
if (value.length > maxLength) {
value = value.substr(0, maxLength);
}
// split into fields
let day = value.substr(0, 2);
let month = value.substr(2, 2);
let year = value.substr(4, 4);
// const resolvedDate = this.resolveDate({ day, month, year })
// console.log(resolvedDate);
if (fieldToCompleteIndex === 0) {
day = this.completeField(day, fieldToCompleteIndex);
}
if (fieldToCompleteIndex === 1) {
month = this.completeField(month, fieldToCompleteIndex);
}
if (fieldToCompleteIndex === 2) {
year = this.completeField(year, fieldToCompleteIndex);
}
// editIndex++;
let resolvedDate = null;
if (day && month && year) {
resolvedDate = moment([year, +month -1 , day]);
if (!resolvedDate.isValid()) {
errors.push("Invalid");
// console.log(resolvedDate);
}
}
value =
day +
(month || fieldToCompleteIndex === 0 ? SEPARATOR + month : "") +
(year || fieldToCompleteIndex === 1 ? SEPARATOR + year : "");
// edit hint to remove replaced chars
hint = HINT.substr(value.length);
this.setState({ value, hint, errors });
this.props.onChange({
day: parseInt(day, 10),
month: parseInt(month, 10) - 1,
year: parseInt(year, 10),
value,
resolvedDate
});
// console.log(
// "caretStart",
// caretStart,
// "caretEnd",
// caretEnd,
// "editIndex",
// editIndex
// );
}
change(e: React.ChangeEvent<HTMLInputElement>) {
this.handleDate(e.target.value.toString());
// requestAnimationFrame(() => {
// inputField.selectionStart = editIndex;
// inputField.selectionEnd = editIndex;
// });
}
// resolveDate({ day, month, year }) {
// const today = moment();
// day = parseInt(day) || 1;
// month = parseInt(month) || 0;
// year = parseInt(year) || today.year();
// let resolvedDate = moment([year, month, day]);
// console.log(resolvedDate);
// // if (parseInt(day) > ) {
// // day = 1;
// // }
// if (!month || parseInt(month) === 0) {
// month = today.month();
// }
// return {
// day,
// month,
// year,
// };
// }
/**
* Find difference between words (i.e. insertion or edit point)
* @param {*} length
* @param {*} lastValue
*/
completeField(value:any, fieldIndex:any) {
return _.padStart(
value,
FIELDS[fieldIndex].label.length,
FIELDS[fieldIndex].pad
);
}
render() {
const { value, hint, errors } = this.state;
return (
<div>
<div className="field">
<div className="field-hint">
<span className="hint-filled">{value}</span>
{hint}
</div>
<input
className="field-input"
onChange={(e) => this.change(e)}
value={value}
ref="input"
/>
</div>
<div className="field-errors">
{errors.map((error: React.ReactNode, i: number) => (
<div className="field-errors-item" key={i}>
{error}
</div>
))}
</div>
</div>
);
}
}
DropDOwn.tsx
import _ from "lodash";
import React, { Component } from "react";
import DropdownArrow from "../../assets/dropdown-arrow.svg";
import "./Dropdown.scss";
type OptionsPropType = {
value: number;
label: string;
};
interface IProps {
value: string;
height: number;
options: Array<OptionsPropType>;
onChange: (value: string) => void;
}
interface IState {
isOpen: boolean;
value: string;
}
export class Dropdown extends Component<IProps, IState> {
listRef = null;
constructor(props:IProps) {
super(props);
this.state = {
isOpen: false,
value: props.value
};
}
change(value:string) {
this.setState({ value });
this.props.onChange(value);
this.close();
}
onOutsideClicked = (e:any) => {
// only outside clicks allowed
// if (this.refs.container && this.refs.container.contains(e.target)) {
// return;
// }
// document.removeEventListener("mousedown", this.onOutsideClicked);
this.close();
};
toggle() {
const isOpen = !this.state.isOpen;
this.setState({ isOpen });
if (isOpen) {
document.addEventListener("mousedown", this.onOutsideClicked);
}
}
close() {
this.setState({ isOpen: false });
}
componentWillReceiveProps(props) {
if (props.value !== this.state.value) {
this.setState({ value: props.value });
}
}
componentDidUpdate() {
if (this.refs.list) {
const option = this.refs["option-" + this.props.value];
this.refs.list._scrollTop =
option.offsetTop - (this.props.height - option.offsetHeight) / 2;
}
}
render() {
const { value, isOpen } = this.state;
const current = _.find(this.props.options, (option) => {
// HACK because select returns string
return (
option.value === value || option.value.toString() === value.toString()
);
});
const list = _.map(this.props.options, (option) => (
<div
ref={`option-${option.value}`}
className={`dropdown-option ${
value === option.value ? "dropdown-option--selected" : ""
}`}
onClick={() => this.change(option.value)}
key={option.value}
>
{option.label}
</div>
));
return (
<div
className={`dropdown ${isOpen ? "dropdown--open" : ""}`}
onClick={() => this.toggle()}
ref="container"
>
<div className="dropdown-label">{current.label}</div>
<div className="dropdown-arrow">
<img src={DropdownArrow} />
</div>
{/* <select onChange={(e) => this.change(e.target.value)} defaultValue={value}>{list}</select> */}
{isOpen ? (
<div
className="dropdown-list"
style={{ maxHeight: this.props.height + "px" }}
ref="list"
>
{list}
</div>
) : null}
</div>
);
}
}
Widget.tsx
import React, { Component } from "react";
import ReactDOM from "react-dom";
import moment from "moment";
import { find as _find } from "lodash";
import { Dropdown } from "./dropdown/Dropdown";
import { DateField } from "./date-field/DateField";
import { MonthNavigator } from "./month-navigator/MonthNavigator";
import "./Widget.scss";
const MIN_YEAR = 1900;
const MAX_YEAR = moment().year() + 5;
type DatePropType = {
day: number;
month: number;
year: number;
};
type FromPropType = {
day: number;
month: number;
year: number;
};
type UntilPropType = {
day: number;
month: number;
year: number;
};
type FromToUntilPropType = {
from: FromPropType;
until: UntilPropType;
};
interface IProps {
disabledRanges: Array<FromToUntilPropType>;
date:Date
}
interface IState {
date: Date;
day: number;
month: number;
year: number;
selectedDay: number;
selectedMonth: number;
selectedYear: number;
dropdownHeight: number;
selectedDate: string;
}
export class Widget extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
const date = props.date || moment();
this.state = {
date,
day: date.date(),
month: date.month(),
year: date.year(),
selectedDay: 10,
selectedMonth: 2,
selectedYear: 2019,
dropdownHeight: 200,
selectedDate: '',
};
}
// createDay({ dayNumber, startDay }) {
// const { day, month, year, date, selectedDay } = this.state;
// let state = '';
// if (month === date.month() && year === date.year()) {
// state = dayNumber === selectedDay
// ? 'Widget-day--selected'
// : (dayNumber === day
// ? 'Widget-day--current'
// : ''
// );
// }
// const style = {};
// if (startDay) {
// style = { ...style, gridColumnStart: startDay};
// startDay = 0;
// }
// return (
// <div className={`Widget-day ${state}`} onClick={() => this.changeDay({ day: dayNumber)}
// style={style}
// key={dayNumber}>
// {dayNumber}
// </div>
// );
// }
/**
*
* @param {object} param
* @param {string} param.type Can be: 'previous' | 'next' | `null` where `null` is current
*/
createDays() {
const daysInWeek = 7;
const {
day,
month,
year,
date,
selectedDay,
selectedMonth,
selectedYear
} = this.state;
const list = [];
const daysInCurrentMonth = moment([year, month, day]).daysInMonth();
const daysInPreviousMonth = moment([year, month, day])
.subtract(1, "month")
.daysInMonth();
let startDay = moment([year, month, 1]).day();
const numPreviousDaysShown = Math.max(startDay - 1, 0);
let numNextDaysShown =
daysInWeek - ((daysInCurrentMonth + numPreviousDaysShown) % daysInWeek);
if (numNextDaysShown === daysInWeek) {
numNextDaysShown = 0;
}
let from = 0 - numPreviousDaysShown;
let until = daysInCurrentMonth + numNextDaysShown;
const classNamePrefix = "widget-day--";
for (let i = from; i < until; i++) {
let dayNumber = i + 1;
const classNames = [];
const style = {};
let isDisabled = false;
let type = "";
let referenceDate = moment([year, month, 1]);
if (dayNumber <= 0) {
dayNumber = daysInPreviousMonth + i + 1;
type = "previous";
referenceDate.subtract(1, "month");
} else if (dayNumber > daysInCurrentMonth) {
dayNumber = i - daysInCurrentMonth + 1;
type = "next";
referenceDate.add(1, "month");
} else {
referenceDate.date(dayNumber);
}
// if (startDay) {
// style = { ...style, gridColumnStart: startDay};
// startDay = 0;
// }
if (type) {
classNames.push(`${classNamePrefix}${type}`);
}
if (
referenceDate.year() === selectedYear &&
referenceDate.month() === selectedMonth &&
dayNumber === selectedDay
) {
classNames.push(`${classNamePrefix}selected`);
} else if (
dayNumber === day &&
month === date.month() &&
year === date.year()
) {
classNames.push(`${classNamePrefix}current`);
}
if (
this.checkIsInDisabledRange({
day: dayNumber,
month: referenceDate.month(),
year: referenceDate.year()
})
) {
isDisabled = true;
classNames.push(`${classNamePrefix}disabled`);
}
const clickAction = !isDisabled
? () =>
this.changeDay({
day: dayNumber,
month: referenceDate.month(),
year: referenceDate.year()
})
: () => {};
list.push(
<div
className={`widget-day ${classNames.join(" ")}`}
onClick={clickAction}
style={style}
key={i}
>
{dayNumber}
</div>
);
}
return list;
}
/**
* Disabled ranges = array of from, until
*
* @param {number} day
* @param {number} month
* @param {number} year
*/
checkIsInDisabledRange({ day, month, year }: DatePropType) {
const current = moment([year, month, day]);
return (
_find(this.props.disabledRanges, (range) => {
const from = moment([
range.from.year,
range.from.month,
range.from.day
]);
const until = moment([
range.until.year,
range.until.month,
range.until.day
]);
return current.isSameOrAfter(from) && current.isSameOrBefore(until);
}) != null
);
}
createDayLabels() {
const list = [];
for (let i = 1; i < 8; i++) {
const label = moment().day(i).format("ddd");
list.push(
<div className="widget-label" key={i}>
{label}
</div>
);
}
return list;
}
createMonthSelector(current:any) {
const list = [];
for (let i = 0; i < 12; i++) {
list.push({ value: i, label: moment().month(i).format("MMMM") });
}
return (
<Dropdown
onChange={(value) => this.changeMonth(value)}
value={current}
options={list}
height={this.state.dropdownHeight}
/>
);
}
createYearSelector(current:any) {
const list = [];
for (let i = MIN_YEAR; i < MAX_YEAR; i++) {
list.push({ value: i, label: moment().year(i).format("YYYY") });
}
return (
<Dropdown
onChange={(value) => this.changeYear(value)}
value={current}
options={list}
height={this.state.dropdownHeight}
/>
);
}
changeDay({ day, month, year }:{ day:any, month:any, year:any }) {
console.log(
day,
month,
year,
moment([year, month, day]).format("DD MM YYYY")
);
this.setState({selectedDate: moment([year, month, day]).format("DDMMYYYY")});
// let { month, year } = this.state;
// let reference = moment([year, month, 1]);
// const day = value + 1;
// if (value < 0) {
// reference.subtract(1, 'month');
// reference.date(reference.daysInMonth() + day);
// }
// else {
// reference.date(day);
// }
// console.log(reference.date(), month, year);
// moving to previous or next
this.setState({
selectedDay: day,
selectedMonth: month,
selectedYear: year,
month,
year
});
}
changeMonth(value:any ) {
this.setState({ month: value });
}
changeYear(value:any) {
this.setState({ year: value });
}
previousMonth() {
let { day, month, year } = this.state;
const previousMonth = moment([year, month, day]).add(-1, "month");
this.setState({
day: previousMonth.date(),
month: previousMonth.month(),
year: previousMonth.year()
});
}
nextMonth() {
let { day, month, year } = this.state;
const nextMonth = moment([year, month, day]).add(1, "month");
this.setState({
day: nextMonth.date(),
month: nextMonth.month(),
year: nextMonth.year()
});
}
componentDidUpdate() {
if (this.state.dropdownHeight !== this.refs.body.clientHeight) {
this.setState({ dropdownHeight: this.refs.body.clientHeight });
}
}
componentDidMount() {
this.setState({ dropdownHeight: this.refs.body.clientHeight });
}
// onChangeField({ value, day, month, year }) {
// console.log('change', value, day, month, year);
// if (day && month && year) {
// this.setState({ selectedDay: day, selectedMonth: month, selectedYear: year });
// }
// }
onChangeField({ day, month, year, resolvedDate }) {
if (
resolvedDate &&
resolvedDate.isValid() &&
resolvedDate.year() >= MIN_YEAR &&
resolvedDate.year() <= MAX_YEAR
) {
this.changeDay({ day, month, year });
}
}
render() {
const { month, year, selectedDate } = this.state;
return (
<div className="date-picker">
<DateField onChange={(value) => this.onChangeField(value)} value={selectedDate} />
<div className="widget">
<div className="header">
<MonthNavigator
onClick={() => this.previousMonth()}
direction="left"
/>
<div className="header-month">
{this.createMonthSelector(month)}
</div>
<div className="header-year">{this.createYearSelector(year)}</div>
<MonthNavigator
onClick={() => this.nextMonth()}
direction="right"
/>
</div>
<div className="widget-body" ref="body">
{this.createDayLabels()}
{this.createDays()}
</div>
</div>
</div>
);
}
}
答案 0 :(得分:1)
您正在使用legacy string refs。您应该更新为较新的引用语法之一,explained here。
在DateField
中,您有对输入元素的引用。
在构造函数中,调用createRef
创建一个空的引用,并使用泛型告诉它我们将引用指向哪个DOM元素。
this.inputRef = createRef<HTMLInputElement>();
Typescript将给您一个错误“类型'DateField'上不存在属性'inputRef'”。因此,您需要声明该属性存在。将其添加到班级顶部。实际上,如果我们在这里拥有通用名称,那么我们就不需要在createRef
中再说一次。
private readonly inputRef: React.RefObject<HTMLInputElement>;
要使用ref,请访问.current
属性。我们使用?.
运算符,因为如果尚未设置ref,则当前属性可能不存在。
handleDate(value: string) {
const caretStart = this.inputRef.current?.selectionStart;
const caretEnd = this.inputRef.current?.selectionEnd;
...
要将引用附加到input
元素,请在render()
函数中执行此操作
<input
....
ref={this.inputRef}
/>
还有很多其他问题。我修复了部分here,但不是全部。