将焦点放在 reactjs 中显示数组的最后一个元素

Set focus on the last element of a displayed array in reactjs

我正在使用 reactjs 编写一个聊天应用程序,我正在尝试弄清楚每次将新消息添加到数组时如何将焦点更改为最新消息。 我的反应聊天 window 看起来像这样:

<ChatHeader />
<Messages datas={this.state.datas} />
<ChatFooter sendMessage={this.sendMessage} />

我的消息组件如下所示:

var Messages = React.createClass({
  render: function() {
    // Loop through the list of messages and create array of Row components
    var Rows = this.props.datas.map(function(data) {
      return (
        <Row username={data.username} message={data.message} /> 
      )
    });

    return (
        {Rows}
    );
  }
});

所以基本上每次我的主聊天 window 重新呈现时,我希望消息显示时列表一直向下滚动(显示最新的消息 - 或者 Rows 数组中的最后几件事) .我发现了一些关于转移焦点的问题,例如 ,但是当我尝试将焦点转移到消息时,它会将我带到列表的顶部。理想情况下,我想添加如下内容:

   var Rows = this.props.datas.map(function(data) {
      return (
        <Row username={data.username} message={data.message} /> 
      )
    });

//    Rows[Rows.length - 1].ref = "last";

这样我就可以专注于数组中的最后一个元素,但我一直无法找到一种方法来设置这样的引用。我见过的所有方式都显示在标签内设置引用。是否存在这样分配引用的方法?

我还考虑过给每一行一个唯一的 ID 号,并为每条新消息递增该编号,跟踪最大的数字,然后用它来引用最后一个元素,但似乎必须有更好的方法。实现此目标的最佳方法是什么?

我们将不得不利用 React 书中被认为是反模式的东西,并使用他们通常不赞成的 ReactDOM.findDOMNode

本质上,我们要做的是在 Messages class 上创建一个名为 onRowRender 的方法并将其绑定到 thisMessages 构造函数中。我们将使用 map 的可选第二个参数来跟踪生成了多少行,如果它是最后一行,我们将把 onRowRendered 方法传递给 Row 组件使用我们称之为 onRender.

的道具

在每个 Row 组件的 componentDidMount 中,我们将检查 this.props.onRender 是否存在,如果存在则表示它是最后一个行所以我们调用 this.props.onRender 并传递给它 this 这是有问题的 Row 组件。

由于我们是在组件挂载后执行所有这些操作,这意味着我们将能够使用 ReactDOM.findDOMNode 获取该渲染组件的原始 javascript DOM 元素.我们在父 Messages class.

上创建的处理程序中执行此操作

说完所有这些之后,我们得到了有问题的 DOMNode 的顶部偏移量,我们还得到了它的 parentNode 然后将 parentNode 的 topOffset 设置为等于顶部偏移量。

注意:我在示例中使用了 es6 class 语法,但它也应该适用于 React.createClass。

class Messages extends React.Component {
  constructor() {
    super();
    this.onRowRender = this.onRowRender.bind(this);
  }

  onRowRender(row) {
    var rowDOM = ReactDOM.findDOMNode(row);
    var offsets = rowDOM.getClientRects()[0];
    var parent = rowDOM.parentNode;
    parent.scrollTop = offsets.top;
  }

  render() {
    return (
      <div className='messages'>
        {this.props.datas.map((data, i) => {
          return (
            <Row
              key={data.key}
              username={data.username}
              message={data.message}
              onRender={i === this.props.datas.length - 1 ? this.onRowRender : null}
            />
          );
        })}
      </div>
    );
  }
}

class Row extends React.Component {
  componentDidMount() {
    if (this.props.onRender) this.props.onRender(this);
  }

  render() {
    return (
      <div>
        {this.props.message}&nbsp;&nbsp;&nbsp;by: {this.props.username}
      </div>
    );
  }
}

var datas = [
  {
    key: 1,
    username: 'test user #1',
    message: 'test message #1',
  }, {
    key: 2,
    username: 'test user #2',
    message: 'test message #2',
  }, {
    key: 3,
    username: 'test user #3',
    message: 'test message #3',
  }, {
    key: 4,
    username: 'test user #4',
    message: 'test message #4',
  }, {
    key: 5,
    username: 'test user #5',
    message: 'test message #5',
  },
];

ReactDOM.render(
  <Messages datas={datas} />,
  document.getElementById('root')
);

强制性 jsfiddle:https://jsfiddle.net/69z2wepo/46701/

或者,您可以通过将 Rows 的 onRender 道具更改为 onRender={this.onRowRender} 然后将 onRowRender 更改为:

onRowRender(row) {
  var rowDOM = ReactDOM.findDOMNode(row);
  var parent = rowDOM.parentNode;
  parent.scrollTop = parent.scrollHeight;
}

只要安装了新行,它就会滚动到底部。但是,按照我最初演示的方式进行操作,它将确保如果最后一个 Row 大于 Messages 组件的总高度它只会滚动到 的顶部,而不是整个列表的底部。 (我希望这是有道理的)