#javascript #reactjs #redux #react-redux
#javascript #reactjs #redux #реагировать-redux
Вопрос:
У меня есть интерфейс, который представляет собой список <input>
s. Нажатие клавиши enter на одном из входов должно переместить фокус на следующий вход, и если вы уже находитесь на последнем входе, следует добавить еще один и сфокусировать его. Список элементов управляется redux.
Вот код, чтобы сделать чистую часть этого, без фокусировки:
class ListItem extends Component {
render() {
return (
<li>
<input
defaultValue={this.props.value}
onKeyUp={this.onKeyUp.bind(this)}
ref={(input) => this.input = input}
/>
</li>
);
}
onKeyUp(event) {
this.props.onChange(event.target.value, event.keyCode === 13);
}
focus() {
this.input.focus();
}
}
class List extends Component {
render() {
return (
<ul>
{this.props.items.map((item, i) =>
<ListItem
key={i}
value={item}
onChange={(value, enterPressed) => this.onChange(i, value, enterPressed)}
/>
)}
</ul>
);
}
onChange(i, value, enterPressed) {
this.props.dispatch({
type: 'UPDATE_ITEM',
index: i,
value: value
});
if (enterPressed amp;amp; i === this.props.items.length - 1) {
this.props.dispatch({
type: 'ADD_ITEM'
});
}
}
}
Это кажется действительно сложным, потому что следующий элемент списка может еще не существовать, когда мы хотим его сфокусировать, поэтому нам каким-то образом нужно подождать, пока произойдет изменение состояния redux, и список повторно отправится с последним элементом.
Единственный способ, который я могу придумать для этого, — сохранить массив ссылок на каждый из элементов списка, затем при нажатии клавиши enter отправить событие redux и установить индекс сфокусированного элемента списка в состояние компонента. Затем в componentDidUpdate (который будет вызван после обновления состояния redux и дополнительного ввода в DOM), проверьте состояние, чтобы увидеть, на какой ввод мы должны сфокусироваться, и вызовите для него метод focus .
class List extends React.Component {
constructor(props) {
super(props);
this.inputs = [];
}
render() {
return (
<ul>
{this.props.items.map((item, i) =>
<ListItem
key={i}
value={item}
onChange={(value, enterPressed) => this.onChange(i, value, enterPressed)}
ref={(input) => this.inputs[i] = input}
/>
)}
</ul>
);
}
onChange(i, value, enterPressed) {
this.props.dispatch({
type: 'UPDATE_ITEM',
index: i,
value: value
});
if (enterPressed) {
this.setState({
inputToFocus: i 1
})
if (i === this.props.items.length - 1) {
this.props.dispatch({
type: 'ADD_ITEM'
});
}
}
}
componentDidUpdate() {
if (typeof this.state.inputToFocus === 'number') {
this.inputs[this.state.inputToFocus].focus();
this.setState({
inputToFocus: null
});
}
}
Это кажется действительно хрупким и уродливым. Есть ли лучший способ сделать это?
Полный пример находится по адресу https://jsfiddle.net/69z2wepo/59903 /.
Ответ №1:
Вместо того, чтобы программно фокусироваться и использовать state
в своем списке, как вы делаете выше, вы должны передать prop в ListItem , и сам элемент списка может сфокусироваться, если prop имеет значение true. Таким образом, вы также можете немного изменить свои действия, чтобы включить флаг isFocused или not, например:
{
type: 'ADD_ITEM'
isFocused: true
}
{
type: 'UPDATE_ITEM',
index: i,
value: value,
isFocused: false/true
}
Это может показаться более трудоемким, но использование реквизита, а не состояния, упростит тестирование и рассуждения.
Комментарии:
1. В каком методе должен быть сфокусирован сам ListItem? Сбрасывается ли флаг isFocused после того, как он сфокусирован?
2. это должно быть в componentDidMount , а также componentDidUpdate (они могут вызывать метод, чтобы вы не дублировали один и тот же код дважды), по сути, что-то вроде, if (this.props.isFocused) this.input.focus .
3. Вам не нужно сбрасывать флаг после того, как он сфокусирован, в основном пусть props будет источником истины, вам просто нужно убедиться, что только 1 ListItem имеет значение isFocused true .
4. Если флаг не будет сброшен, последний сфокусированный ввод будет повторно фокусироваться при каждом обновлении компонента, даже если пользователь переместил фокус в другое место. После того, как ввод сфокусирован один раз, его никогда не следует принудительно фокусировать снова, даже если компонент обновляется. Я сделал то, что вы предложили здесь: jsfiddle.net/24mwqot2/1 . Обратите внимание, как последний ввод с принудительной фокусировкой фокусируется всякий раз, когда вы вводите клавишу в любом поле.
5. позвольте мне взглянуть на ваш jsfiddle