Обработка нечистых манипуляций с DOM после изменения состояния redux

#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