React Native — прокрутка ListView до вложенного дочернего элемента Ref?

Я использую ListView для отображения списка comments и, возможно, subcomments, если они существуют в комментарии. Я пытаюсь перейти к определенному subcomment через его ref, но не могу заставить его работать. Для этого я использовал 3 компонента (сведены ниже):

<сильный>1. Комментарии

import React, { Component } from 'react'
import { TouchableOpacity, ListView, View, Text } from 'react-native'
import CommentRow from './commentRow'

const ds = new ListView.DataSource({ rowHasChanged: ( r1, r2 ) => r1.id !== r2.id });
const commentsDataSource = [
  {id: '1', body: 'comment 1'},{id: '2', body: 'comment 2'},{id: '3', body: 'comment 3'},{id: '4', body: 'comment 4'},{id: '5', body: 'comment 5'},{id: '6', body: 'comment 6'},{id: '7', body: 'comment 7'},{id: '8', body: 'comment 8'},{id: '9', body: 'comment 9'},{id: '10', body: 'comment 10'},
  {id: '11', body: 'comment 11'},{id: '12', body: 'comment 12', hasSubComments: true},{id: '13', body: 'comment 13'},{id: '14', body: 'comment 14'},{id: '15', body: 'comment 15'},{id: '16', body: 'comment 16'},{id: '17', body: 'comment 17'},{id: '18', body: 'comment 18'},{id: '19', body: 'comment 19'},{id: '20', body: 'comment 20'}
];

export default class Comments extends Component {
    constructor(props) {
      super(props);

      this.state = {
        dataSource: ds.cloneWithRows(commentsDataSource)
      };
    }

    scrollToSubCommentRef(ref) {
      this.rowz[ref].measure((ox, oy, width, height, px, py) => {
        const offsetY = oy;
        this.refs.mainListView.scrollTo({ y: offsetY })
      });
    }

    render() {
      return (
        <View>
            <TouchableOpacity style={{backgroundColor: 'red', padding: 50}}
                              onPress={() => this.scrollToSubCommentRef('subComment_10')}>
                <Text>Scroll to subComment_10!</Text>
            </TouchableOpacity>

            <ListView ref="mainListView"
                      renderRow={comment => <CommentRow comment={comment} />}
                      dataSource={this.state.dataSource}
                      enableEmptySections={true} />
        </View>
      )
    }
}

<сильный>2. Комментарий

import React, { Component } from 'react';
import { View } from 'react-native'
import CommentListItem from './commentListItem'

export default class CommentRow extends Component {
    render() {
      const comment = this.props.comment;

      return (
        <View key={`comment_${comment.id}`} style={{overflow: 'hidden'}}>
          <CommentListItem comment={comment} />
        </View>
      )
    }
}

<сильный>3. Элемент списка комментариев

import React, { Component } from 'react'
import { View, Text } from 'react-native'

const subComments = [
  {id: '1', body: 'subcomment 1'},{id: '2', body: 'subcomment 2'},{id: '3', body: 'subcomment 3'},{id: '4', body: 'subcomment 4'},{id: '5', body: 'subcomment 5'},{id: '6', body: 'subcomment 6'},{id: '7', body: 'subcomment 7'},{id: '8', body: 'subcomment 8'},{id: '9', body: 'subcomment 9'},{id: '10', body: 'subcomment 10'},
  {id: '11', body: 'subcomment 11'},{id: '12', body: 'subcomment 12'},{id: '13', body: 'subcomment 13'},{id: '14', body: 'subcomment 14'},{id: '15', body: 'subcomment 15'},{id: '16', body: 'subcomment 16'},{id: '17', body: 'subcomment 17'},{id: '18', body: 'subcomment 18'},{id: '19', body: 'subcomment 19'},{id: '20', body: 'subcomment 20'},
  {id: '21', body: 'subcomment 21'},{id: '22', body: 'subcomment 22'},{id: '23', body: 'subcomment 23'},{id: '24', body: 'subcomment 24'},{id: '25', body: 'subcomment 25'},{id: '26', body: 'subcomment 26'},{id: '27', body: 'subcomment 27'},{id: '28', body: 'subcomment 28'},{id: '29', body: 'subcomment 29'},{id: '30', body: 'subcomment 30'},
  {id: '31', body: 'subcomment 31'},{id: '32', body: 'subcomment 32'},{id: '33', body: 'subcomment 33'},{id: '34', body: 'subcomment 34'},{id: '35', body: 'subcomment 35'},{id: '36', body: 'subcomment 36'},{id: '37', body: 'subcomment 37'},{id: '38', body: 'subcomment 38'},{id: '39', body: 'subcomment 39'},{id: '40', body: 'subcomment 40'},
  {id: '41', body: 'subcomment 41'},{id: '42', body: 'subcomment 42'},{id: '43', body: 'subcomment 43'},{id: '44', body: 'subcomment 44'},{id: '45', body: 'subcomment 45'},{id: '46', body: 'subcomment 46'},{id: '47', body: 'subcomment 47'},{id: '48', body: 'subcomment 48'},{id: '49', body: 'subcomment 49'},{id: '50', body: 'subcomment 50'},
  {id: '51', body: 'subcomment 51'},{id: '52', body: 'subcomment 52'},{id: '53', body: 'subcomment 53'},{id: '54', body: 'subcomment 54'},{id: '55', body: 'subcomment 55'},{id: '56', body: 'subcomment 56'},{id: '57', body: 'subcomment 57'},{id: '58', body: 'subcomment 58'},{id: '59', body: 'subcomment 59'},{id: '60', body: 'subcomment 60'},
  {id: '61', body: 'subcomment 61'},{id: '62', body: 'subcomment 62'},{id: '63', body: 'subcomment 63'},{id: '64', body: 'subcomment 64'},{id: '65', body: 'subcomment 65'},{id: '66', body: 'subcomment 66'},{id: '67', body: 'subcomment 67'},{id: '68', body: 'subcomment 68'},{id: '69', body: 'subcomment 69'},{id: '70', body: 'subcomment 70'}
];

export default class CommentListItem extends Component {
  rowz = []; // to hold subComment refs for scroll access

  subCommentsList = () => {
    return subComments.map((subComment, i) => {
      return (
        <View ref={i => this.rowz["subComment_"+subComment.id] = i} key={"subComment_"+subComment.id}>
          <Text>{subComment.body}</Text>
        </View>
      );
    });
  }

  render() {
    const comment = this.props.comment;

    return (
      <View>
        <Text>{comment.body}</Text>
        {comment.hasSubComments && this.subCommentsList()}
      </View>
    )
  }
}

В родительском компоненте #1 я попытался прокрутить до subComment через его ref из subComment_10, но измерение выдает неопределенную ошибку. Я понимаю, что this.rowz не существует в #1 только в #3, где карта subComments перебирает каждый subComment и присваивает его массиву rowz (я только что понял, что по какой-то причине он не присваивает subComment_idhere массиву rowz).

Итак, как мы можем исправить проблему назначения ref на карте #3, чтобы массив rowz получил список всех подкомментариев refs, чтобы мы могли прокручивать их? И как мы можем получить TouchableOpacity с this.scrollToSubCommentRef('subComment_10') в #1, чтобы прокрутить mainListView до subComment_10?

ОБНОВЛЕНИЕ

С предоставленным решением ref успешно передается в массив rowz, но, как вы заметите, он не прокручивается до subComment_10, а прокручивается до конца comment 10. Он должен прокручиваться до верхней части subComment_10, чтобы он был самым видимым subComment при нажатии на TouchableHighlight:

введите здесь описание изображения


person Wonka    schedule 03.02.2017    source источник
comment
В этой части: subCommentsList = () =› { ref={subComment =› this.rows[subComment_+subComment.id] = subComment} можете ли вы сказать, смешиваются ли subComment в params и ref function subComment param?   -  person eden    schedule 05.02.2017
comment
Не уверен, что вы имеете в виду, но массив rows в верхней части #3 содержит список subcomments при назначении упомянутой вами функцией ref, поэтому ссылка subcomment_10 существует в массиве rows.   -  person Wonka    schedule 06.02.2017
comment
В subComments.map есть параметр с тем же именем, что и у анонимной стрелочной функции ref. Я подозревал, что это может вызвать конфликт. Но вы гарантируете, что функции работают должным образом, верно?   -  person eden    schedule 06.02.2017
comment
Как насчет массива rows = []? Где и как вы это определили? потому что this.rows - это нечто иное, чем просто rows = [] без префикса, а также его запрещенное использование в ecma 6.   -  person eden    schedule 06.02.2017
comment
Я обновил код/вопрос выше, чтобы его можно было легко воспроизвести. Я также изменил rows на rowz. Я думал, что это может конфликтовать с чем-то, но я понял, что он не назначает refs на rowz, когда я его регистрирую. У вас может быть больше опыта и вы знаете, в чем проблема :)   -  person Wonka    schedule 06.02.2017
comment
Спасибо, воспроизвел ошибку, стало понятнее. Я отправил ответ, дайте мне знать, если что-то пойдет не так   -  person eden    schedule 06.02.2017


Ответы (1)


Хорошо, я запустил ваш отредактированный код и понял, чего вам не хватает. Массив refz создается локально в классе CommentListItem, поэтому вы не можете получить к нему доступ из родительских классов. Однако, поскольку вы будете выполнять всю навигацию из родительского класса, передавая массив реквизита на самый нижний уровень и заполняя его, будет лучший подход. Таким образом, вы не получите сообщение об ошибке this.rowz is undefined и запустите свой код, как ожидалось.

export default class Comments extends Component {
constructor(props) {
  super(props);
  this.rowz = []
  this.state = {
    dataSource: ds.cloneWithRows(commentsDataSource)
  };
}

scrollToSubCommentRef(ref) {
  this.rowz[ref].measure((ox, oy, width, height, px, py) => {
    const offsetY = oy;
    this.refs.mainListView.scrollTo({ y: offsetY })
  });
}

render() {
  return (
    <View>
        <TouchableOpacity style={{backgroundColor: 'red', padding: 50}}
                          onPress={() => this.scrollToSubCommentRef('subComment_10')}>
            <Text>Scroll to subComment_10!</Text>
        </TouchableOpacity>

        <ListView ref="mainListView"
                  renderRow={comment => <CommentRow refArr={this.rowz} comment={comment} />}
                  dataSource={this.state.dataSource}
                  enableEmptySections={true} />
    </View>
  )
}
}

Здесь, в классе комментариев, мы передаем массив (this.rowz), который мы создали в конструкторе, в класс CommentsRow ~

<CommentRow refArr={this.rowz} comment={comment} />

В классе CommentRow мы просто передадим то, что у нас было из родительского класса,

export default class CommentRow extends Component {
    render() {
      const comment = this.props.comment;

      return (
        <View key={`comment_${comment.id}`} style={{overflow: 'hidden'}}>
          <CommentListItem refArr={this.props.refArr} comment={comment} />
        </View>
      )
    }
}

Прямо здесь:

<CommentListItem refArr={this.props.refArr} comment={comment} />

И, наконец, в классе CommentListItem, чтобы заполнить наш массив, мы можем просто вызвать this.props.refArr.push()

export default class CommentListItem extends Component {
  rowz = []; // to hold subComment refs for scroll access

  subCommentsList = () => {
    return subComments.map((subComment, i) => {
      return (
        <View ref={i => this.props.refArr["subComment_"+subComment.id] = i} key={"subComment_"+subComment.id}>
          <Text>{subComment.body}</Text>
        </View>
      );
    });
  }

  render() {
    const comment = this.props.comment;

    return (
      <View>
        <Text>{comment.body}</Text>
        {comment.hasSubComments && this.subCommentsList()}
      </View>
    )
  }
}

Как вы можете видеть яснее здесь:

<View ref={i => this.props.refArr["subComment_"+subComment.id] = i} key={"subComment_"+subComment.id}>

Он просто работает и плавно прокручивается при нажатии touchable. Я пропустил части импорта во фрагментах выше.

person eden    schedule 06.02.2017
comment
Спасибо за решение, это на полпути (массив rowz теперь имеет refs), но позиция прокрутки неверна. Я добавил обновление к вопросу с изображением, показывающим оставшуюся проблему с положением прокрутки. - person Wonka; 06.02.2017
comment
Можете ли вы записать, что вы измеряете для каждого подкомментария? Также попробуйте прокрутить, скажем, sub_comment20, какова дельта расстояния, соответствует ли она высоте строки? Я считаю, что вы можете понять это, протестировав функцию scrollToSubCommentRef. - person eden; 06.02.2017
comment
Я считаю, что это ваша функция и измерения, которые терпят неудачу. Это может помочь: stackoverflow.com/questions/34750424/ - person eden; 06.02.2017
comment
Дело в том, что функция и измерения работают нормально, когда comment имеет ref в массиве, прокручивается до правильного comment, но это subComment, до которого он не прокручивается правильно, поэтому я создал этот вопрос. - person Wonka; 07.02.2017
comment
Я постараюсь освободить место и завтра посмотрю еще раз. - person eden; 07.02.2017
comment
Я понял :) В функции измерения мне нужно было вычислить offsetY родительского comment и добавить это значение к offsetY subComment, поэтому для прокрутки до правильной позиции определенно нужна сумма обоих. Большое спасибо за вашу помощь @Enie Jakiro! - person Wonka; 07.02.2017
comment
Рад, что ты это понял :) - person eden; 07.02.2017
comment
Не мутируйте реквизит!! Лучше передать функцию в качестве реквизита для изменения состояния или даже просто свойства родителя. Более чистый и предсказуемый. то есть для ребенка: this.props.addChild(childId) и для родителя в addChild: this.childrefs.push(childId) - person ThaJay; 28.02.2018