Reactでリストビューを実装しようとしています。私が達成しようとしているのは、リストヘッダー情報を保存し、コンポーネントを登録し、スクロールイベントを登録することです。ユーザーがウィンドウをスクロールするたびに、保存されているdivを取り出してoffsetTop
データを再計算します。
問題は、コンソールが初期値を出力するだけだということです(値は固定されており、変更されません)offsetTop
データはonscroll
関数で変更されません。
_instances
オブジェクトから最新のoffsetTop
を取得する方法を提案する人はいますか?
import React, { Component } from 'react';
import ListHeader from './lib/ListHeader';
import ListItems from './lib/ListItems';
const styles = {
'height': '400px',
'overflowY': 'auto',
'outline': '1px dashed red',
'width': '40%'
};
class HeaderPosInfo {
constructor(headerObj, originalPosition, originalHeight) {
this.headerObj = headerObj;
this.originalPosition = originalPosition;
this.originalHeight = originalHeight;
}
}
export default class ReactListView extends Component {
static defaultProps = {
events: ['scroll', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll', 'resize', 'touchmove', 'touchend'],
_instances:[],
_positionMap: new Set(),
_topPos:'',
_topWrapper:''
}
static propTypes = {
data: React.PropTypes.array.isRequired,
headerAttName: React.PropTypes.string.isRequired,
itemsAttName: React.PropTypes.string.isRequired,
events: React.PropTypes.array,
_instances: React.PropTypes.array,
_positionMap: React.PropTypes.object,
_topPos: React.PropTypes.string,
_topWrapper: React.PropTypes.object
};
state = {
events: this.props.events,
_instances: this.props._instances,
_positionMap: this.props._positionMap,
_topPos: this.props._topPos
}
componentDidMount() {
this.initStickyHeaders();
}
componentWillUnmount() {
}
componentDidUpdate() {
}
refsToArray(ctx, prefix){
let results = [];
for (let i=0;;i++){
let ref = ctx.refs[prefix + '-' + String(i)];
if (ref) results.Push(ref);
else return results;
}
}
initHeaderPositions() {
// Retrieve all instance of headers and store position info
this.props._instances.forEach((k)=>{
this.props._positionMap.add(new HeaderPosInfo(
k,
k.refs.header.getDOMNode().offsetTop,
k.refs.header.getDOMNode().offsetHeight
));
});
let it = this.props._positionMap.values();
let first = it.next();
this.props._topPos = first.value.originalPosition;
this.props._topWrapper = first.value.headerObj;
}
initStickyHeaders () {
this.props._instances = this.refsToArray(this, 'ListHeader');
this.initHeaderPositions();
// Register events listeners with the listview div
this.props.events.forEach(type => {
if (window.addEventListener) {
React.findDOMNode(this.refs.listview).addEventListener(type, this.onScroll.bind(this), false);
} else {
React.findDOMNode(this.refs.listview).attachEvent('on' + type, this.onScroll.bind(this), false);
}
});
}
onScroll() {
// update current header positions and apply fixed positions to the top one
console.log(1);
let offsetTop = React.findDOMNode(this.props._instances[0].refs.header).offsetTop;
}
render() {
const { data, headerAttName, itemsAttName } = this.props;
let _refi = 0;
let makeRef = () => {
return 'ListHeader-' + (_refi++);
};
return (
<div ref="listview" style={styles}>
{
Object.keys(data).map(k => {
const header = data[k][headerAttName];
const items = data[k][itemsAttName];
return (
<ul key={k}>
<ListHeader ref={makeRef()} header={header} />
<ListItems items={items} />
</ul>
);
})
}
</div>
);
}
}
ソースコード全体はGithubにあります。ここからクローンを作成してコンパイルできます。
Element.getBoundingClientRect() メソッドを使用して、要素の上部オフセットを取得することをお勧めします。このメソッドは、ビューポート内の要素の完全なオフセット値(左、上、右、下、幅、高さ)を提供します。
John Resigの投稿 をチェックして、この方法の有用性を説明してください。
Eugeneの答えは正しい関数を使用してデータを取得しますが、後世のために、React v0.14 +( this answer ):
import ReactDOM from 'react-dom';
//...
componentDidMount() {
var rect = ReactDOM.findDOMNode(this)
.getBoundingClientRect()
}
私のために完璧に働いており、私はデータを使用して、マウントしたばかりの新しいコンポーネントの一番上までスクロールしています。
import ReactDOM from 'react-dom';
//...
componentDidMount() {
var n = ReactDOM.findDOMNode(this);
console.log(n.offsetTop);
}
NodeからoffsetTopを取得するだけです。
推奨されていないfindDOMNodeを回避するrefを使用したより良いソリューション。
...
onScroll() {
let offsetTop = this.instance.getBoundingClientRect().top;
}
...
render() {
...
<Component ref={(el) => this.instance = el } />
...
React 16.3以降を使用している場合のより簡単な方法は、コンストラクターでrefを作成し、次に示すように、使用するコンポーネントにそれを添付することです。
...
constructor(props){
...
//create a ref
this.someRefName = React.createRef();
}
onScroll(){
let offsetTop = this.someRefName.current.offsetTop;
}
render(){
...
<Component ref={this.someRefName} />
}
私は著者がクラスベースのコンポーネントに関連して質問をすることを理解していますが、 React 16.8.0(February 6、2019) でフックを利用できることに言及する価値があると思います関数ベースのコンポーネント。
サンプルコード:
import { useRef } from 'react'
function Component() {
const inputRef = useRef()
return (
<input ref={inputRef} />
<div
onScroll={() => {
const { offsetTop } = inputRef.current
...
}}
>
)
}