【发布时间】:2020-10-22 14:36:01
【问题描述】:
在使用 Material-UI v4.11.0 的 React v16.13.1 中,我有以下组件
const GuestSignup = (props: GuestSignupProps) => {
return <Grid container spacing={1} alignItems="flex-end">
<Grid item xs={1}>
<Tooltip title="Uncheck to cancel individual signup" arrow>
<FormControl>
<FormControlLabel
control={<Checkbox key="ck1" onChange={props.handleSignupCheckChangeGuest(props.gs.guestSignupID === global.NULL_ID ? props.gs.index : props.gs.guestSignupID)} value={props.gs.guestSignupID} checked={props.gs.selected} />}
label=""
/>
</FormControl>
</Tooltip>
</Grid>
<Grid item xs={2}>
<TextField label="First Name" value={props.gs.firstName} key={props.gs.guestSignupID.toString() + 'fn'} required onChange={props.handleFirstNameChange(props.gs.guestSignupID, props.gs.index)} />
</Grid>
<Grid item xs={2}>
<TextField label="Last Name" value={props.gs.lastName} key={props.gs.guestSignupID.toString() + 'ln'} required onChange={props.handleLastNameChange(props.gs.guestSignupID, props.gs.index)} />
</Grid>
<Grid item xs={2}>
<GuestTypes value={props.gs.guestType.guestTypeID} key="gt" gt={props.gt} handleGuestTypeChange={props.handleGuestTypeChange(props.gs.guestSignupID, props.gs.index)} />
</Grid>
<Grid item xs={2}>
<KeyboardDatePicker
autoOk
className={classes.guestDate}
disablePast
disableToolbar
variant="inline"
format="MM/dd/yyyy"
key="ad"
margin="dense"
label="Arrival"
required
value={props.gs.arrivalDate === global.EMPTY_STRING ? null : new Date(props.gs.arrivalDate)}
onChange={props.handleArrivalDateChange(props.gs.guestSignupID === global.NULL_ID ? props.gs.index : props.gs.guestSignupID, GUEST_SIGNUP)}
KeyboardButtonProps={{
'aria-label': 'change date',
}}
/>
</Grid>
<Grid item xs={2}>
<KeyboardDatePicker
autoOk
className={classes.guestDate}
disablePast
disableToolbar
variant="inline"
format="MM/dd/yyyy"
key="dd"
margin="dense"
label="Departure"
required
value={props.gs.departureDate === global.EMPTY_STRING ? null : new Date(props.gs.departureDate)}
onChange={props.handleDepartureDateChange(props.gs.guestSignupID === global.NULL_ID ? props.gs.index : props.gs.guestSignupID, GUEST_SIGNUP)}
KeyboardButtonProps={{
'aria-label': 'change date',
}}
/>
</Grid>
</Grid>
}
使用这些输入道具
interface GuestSignupProps {
id: number;
key: number;
gs: GuestSignup;
gt: Array<GuestType>;
handleArrivalDateChange(id: number, type: string): (date: Date | null) => void;
handleDepartureDateChange(id: number, type: string): (date: Date | null) => void;
handleFirstNameChange(id: number, index: number): (event: React.ChangeEvent<HTMLInputElement>) => void;
handleGuestTypeChange(id: number, index: number): (event: React.ChangeEvent<{ value: unknown }>) => void;
handleLastNameChange(id: number, index: number): (event: React.ChangeEvent<HTMLInputElement>) => void;
handleSignupCheckChangeGuest(id: number): (event: React.ChangeEvent<HTMLInputElement>) => void;
}
使用这种数据结构
export interface GuestSignup {
guestSignupID: number;
index: number;
signupID: number;
firstName: string;
lastName: string;
gender: string;
guestType: GuestType;
arrivalDate: string;
departureDate: string;
cancelDate: string;
notes: string;
cancel: boolean;
selected: boolean;
}
它作为多个实例的数组存储在状态中
const [guestSignups, setGuestSignups] = React.useState<Array<GuestSignup>>([]);
并在页面中有多个 GuestSignup 实例的情况下呈现。
{guestSignups.length > 0 &&
<React.Fragment>
{
guestSignups.map((gs: GuestSignup, index: number) => (
<GuestSignup id={gs.guestSignupID} key={gs.guestSignupID > 0 ? gs.guestSignupID : index} gs={gs} gt={guestTypes} handleArrivalDateChange={handleArrivalDateChange} handleDepartureDateChange={handleDepartureDateChange} handleFirstNameChange={handleFirstNameChange} handleGuestTypeChange={handleGuestTypeChange} handleLastNameChange={handleLastNameChange} handleSignupCheckChangeGuest={handleSignupCheckChangeGuest} />
))
}
</React.Fragment>
}
名字和姓氏都有处理程序,每次按键都会在 onChange 中调用
const handleFirstNameChange = (id: number, index: number) => (event: React.ChangeEvent<HTMLInputElement>) => {
// Used either GuestSignupID for existing guests or array index for new guests
const newGuestSignup = produce(guestSignups,
draft => {
const i = draft.findIndex(gs => { return gs.guestSignupID > 0 ? gs.guestSignupID === id : gs.index === index });
draft[i].firstName = event.target.value;
}
);
setGuestSignups([ ...newGuestSignup ]);
}
问题在于,每次击键后,名字和姓氏文本输入都会失去焦点。他们在兄弟姐妹中拥有唯一的钥匙。似乎整个访客注册集在处理程序之后重新呈现,因为如果您点击 TAB,最顶部的访客注册中的第一个复选框将获得焦点。
让文本输入元素在每次击键后保持焦点的正确方法是什么?
【问题讨论】:
-
不建议您将
key用于GuestSignup。由于key帮助 React 理解重绘,您使用它的方式可能导致这些重绘,这会使输入失去焦点(因为它们将是每次重绘的新输入)跨度> -
为什么你的
TextField元素有一个key?只有在循环渲染多个相同类型的组件时才需要键。 -
请在code sandbox 中重现您的问题。这可能是与this question 类似的根本原因,但如果没有完整的复制则无法确定。
-
由于所有元素都在 GRID 单元格中,从 React 的角度来看,它们是否被视为兄弟姐妹?它们真的必须是元素兄弟,还是 React 只考虑与渲染相关的相邻元素的兄弟? React 文档中的简单 UL 是直截了当的。上面的结构呢?试图了解什么需要keyl
-
@ChrisP 您只需要在渲染元素数组时使用键(例如,在
.map调用中渲染的元素,例如您的GuestSignup元素)。当元素被显式布局时,键不是必需的(尽管它们也不会伤害任何东西,只要键不改变给定元素)。
标签: javascript reactjs typescript material-ui