在Reactjs中更改scrollTop

22

我刚学习React,并想实现以下功能:

A和B都是组件,如果A滚动,则B也会滚动。

以下是我的代码:

<A onScroll="handleScroll"></A>

//what i write now
handleScroll: function(event){
    var target = event.nativeEvent.target;

    //do something to change scrollTop value
    target.scrollTop += 1;

    // it looks the same as not use react
    document.getElementById(B).scrollTop = target.scrollTop;
}

但实际上我想让我的代码像这样

//what i want
<A scrollTop={this.props.scrollSeed}></A>
<B scrollTop={this.props.scrollSeed}></B>

//...
handleScroll(){
    this.setState({scrollSeed: ++this.state.scrollSeed})
}

它类似于 input

<input value="this.props.value"/>
<input value="this.props.value"/>
<input ref='c' onChange={handleChange}>

//...
handleChange: function() {
    // enter some code in c and then render in a and b automatically
}

换句话说,我想要一些属性(比如scrollTop)能够与某个状态绑定,这样我就可以只使用setState,它们就会自动更新。与<input value={}>不同,因为<A scrollTop={}>无法正常工作。

我之前在谷歌上搜索过,但找不到答案。希望我的英语水平不会让你困惑。

4个回答

17

有许多模式可以实现这一点。这个示例是我想出来的,可以帮助您快速上手。

首先创建一个组件类,该类具有超大的元素以实现滚动效果。当拖动滚动条时,此组件调用其 handleScroll React 属性,向其父组件通知 scrollTop 的值。

var Elem = React.createClass({
    render() {
        return (
            <div ref="elem"
                onScroll={ this.onScroll }
                style={{ width: "100px", height: "100px", overflow: "scroll" }}>
                <div style={{ width: "100%", height: "200%" }}>Hello!</div>
            </div>
        );
    },
    componentDidUpdate() {
        this.refs.elem.scrollTop = this.props.scrollTop;
    },
    onScroll() {
        this.props.handleScroll( this.refs.elem.scrollTop );
    }
});

父组件,即包装器,将滚动条顶部值保留在其状态中。它的handleScroll被传递给子组件作为回调函数。对子元素的任何滚动都会触发回调、设置状态、导致重绘并更新子组件。

var Wrapper = React.createClass({
    getInitialState() {
        return {
            scrollTop: 0
        }
    },
    render() {
        return (
            <div>
                <Elem scrollTop={ this.state.scrollTop } handleScroll={ this.handleScroll } />
                <Elem scrollTop={ this.state.scrollTop } handleScroll={ this.handleScroll } />
            </div>
        );
    },
    handleScroll( scrollTop ) {
        this.setState({ scrollTop });
    }
});

假设已存在一个<div id="container"></div>,则呈现包装器。

ReactDOM.render(
    <Wrapper />,
    document.getElementById('container')
);

2
谢谢你的帮助!我发现不需要使用 ReactDOM.findDOMNode()this.refs.elem 就足够了(而且还可以节省 10% 的 CPU 使用率,^_^);在此之前,我只是尝试将我的代码放在 componentWillMount 中,但应该是 componentDidUpdate;总之,再次感谢。 - edeity
@edeity,非常正确;已修订。 - Season
this.refs已被弃用。请使用https://reactjs.org/docs/refs-and-the-dom.html#creating-refs。 - Lukas Liesis

16

2019年的答案

首先,修复:

const resetScrollEffect = ({ element }) => {
  element.current.getScrollableNode().children[0].scrollTop = 0
}

const Table = props => {
  const tableRef = useRef(null)
  useEffect(() => resetScrollEffect({ element: tableRef }), [])
  return (
    <Component>
      <FlatList
        ref={someRef}
      />
    </Component>
  )
}
第二,稍作解释: 我不知道你来这里的原因是什么,但是我已经为我的FlatList使用了"flex-direction: column-reverse"(它是一个元素列表)。我需要此属性以用于z-index的目的。然而,浏览器会将这些元素(表格、聊天等)的滚动位置设置到末尾——这可能很有用,但在我的情况下我不需要那样做。 此外,示例是使用React Hooks展示的,但你可以使用更传统的定义refs的方法。

1
我认为这是关于React Web的问题,而你的回答适用于React Native。我也想知道如何在点击时触发该效果。看起来当加载时和有任何状态或属性更改时都会调用效果。 - Benjamin Atkin
也许将 resetScrollEffect 重命名为 resetScroll,并使按钮显示 onClick={ .resetScroll({ element: tableRef }) } - Benjamin Atkin
3
当在React中使用useEffect时,将函数命名为*Effect是一种标准。但与问题相关的主要代码是:element.current.getScrollableNode().children[0].scrollTop =,因为上面的答案中的 element.current.scrollTop = 对我无效(在较早的时间内有效)。 - Jerry Green
1
谢谢,必须登录并点赞!很难找到钩子的解决方案。element.current.scrollTop 完美地工作。 - Edwardhk

7

this.refs已经被弃用,请使用reactjs.org/docs/refs-and-the-dom.html#creating-refs

import React from 'react';

class SomeComponent extends React.Component {
  constructor(props) {
    super(props);
    this.resultsDiv = React.createRef();
  }

  someFunction(){
     this.resultsDiv.current.scrollIntoView({behavior: 'smooth'});

     // alternative:
     // this.resultsDiv.current.scrollTop = 0;
  }

  render() {
    return (
      <div ref={this.resultsDiv} />
    );
  }
}

export default SomeComponent;



@jimish 尝试使用 this.resultsDiv.current.scrollIntoView({behavior: 'smooth'});。这可能由于与 CSS 布局、div 定位相关的其他问题而无法正常工作。尝试在一些你知道使用默认 CSS 定位的其他元素上使用它。 - Lukas Liesis

2
这是 Season's answer 的更新版本,包括可运行的代码片段。它使用了创建引用的推荐方法。

class Editor extends React.Component {
    constructor(props) {
        super(props);
        this.content = React.createRef();
        this.handleScroll = this.handleScroll.bind(this);
    }

    componentDidUpdate() {
        this.content.current.scrollTop = this.props.scrollTop;
    }
    
    handleScroll() {
        this.props.onScroll( this.content.current.scrollTop );
    }

    render() {
        let text = 'a\n\nb\n\nc\n\nd\n\ne\n\nf\n\ng';
        return <textarea
            ref={this.content}
            value={text}
            rows="10"
            cols="30"
            onScroll={this.handleScroll}/>;
    }
}

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {scrollTop: 0};
        this.handleScroll = this.handleScroll.bind(this);
    }

    handleScroll(scrollTop) {
        this.setState({scrollTop: scrollTop});
    }

    render() {
        return <table><tbody>
            <tr><th>Foo</th><th>Bar</th></tr>
            <tr>
                <td><Editor
                    scrollTop={this.state.scrollTop}
                    onScroll={this.handleScroll}/></td>
                <td><Editor
                    scrollTop={this.state.scrollTop}
                    onScroll={this.handleScroll}/></td>
            </tr>
        </tbody></table>;
    }
}

ReactDOM.render(
    <App/>,
    document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接