J'essaie d'implémenter une vue de liste dans React. Ce que j'essaie de faire est de stocker les informations d'en-tête de liste, d'enregistrer les composants et d'enregistrer l'événement de défilement . Chaque fois que l'utilisateur fait défiler la fenêtre, j'aimerais -calculer les données offsetTop
.
Le problème maintenant, c’est que j’ai trouvé que la console n’imprimait que la valeur initiale (la valeur est fixe et jamais modifiée) Les données offsetTop
ne changent jamais dans la fonction onscroll
.
Quelqu'un suggère-t-il comment obtenir la dernière variable offsetTop
à partir de l'objet _instances
?
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>
);
}
}
Le code source entier est sur Github, vous pouvez le cloner et le compiler à partir d’ici:
Vous pouvez être encouragé à utiliser la méthode Element.getBoundingClientRect () pour obtenir le décalage supérieur de votre élément. Cette méthode fournit les valeurs de décalage complètes (gauche, haut, droite, bas, largeur, hauteur) de votre élément dans la fenêtre.
Lisez le message de John Resig décrivant l’utilité de cette méthode.
La réponse d'Eugene utilise la fonction correcte pour obtenir les données, mais pour la postérité, j'aimerais préciser comment l'utiliser dans React v0.14 + (selon cette réponse ):
import ReactDOM from 'react-dom';
//...
componentDidMount() {
var rect = ReactDOM.findDOMNode(this)
.getBoundingClientRect()
}
Cela fonctionne parfaitement pour moi et j'utilise les données pour faire défiler vers le haut le nouveau composant qui vient d'être monté.
import ReactDOM from 'react-dom';
//...
componentDidMount() {
var n = ReactDOM.findDOMNode(this);
console.log(n.offsetTop);
}
Vous pouvez simplement récupérer l'offsetTop du nœud.
Une meilleure solution avec ref pour éviter findDOMNode qui soit découragée.
...
onScroll() {
let offsetTop = this.instance.getBoundingClientRect().top;
}
...
render() {
...
<Component ref={(el) => this.instance = el } />
...
Si vous utilisez React 16.3 et versions ultérieures, vous pouvez rapidement créer une référence dans le constructeur, puis l'associer au composant que vous souhaitez utiliser, comme indiqué ci-dessous.
...
constructor(props){
...
//create a ref
this.someRefName = React.createRef();
}
onScroll(){
let offsetTop = this.someRefName.current.offsetTop;
}
render(){
...
<Component ref={this.someRefName} />
}
Je me rends bien compte que l'auteur pose une question sur un composant basé sur les classes, mais je pense qu'il est utile de mentionner que, à compter de React 16.8.0 (6 février 2019) vous pouvez tirer parti des points d'ancrage dans les fonctions Composants.
Exemple de code:
import { useRef } from 'react'
function Component() {
const inputRef = useRef()
return (
<input ref={inputRef} />
<div
onScroll={() => {
const { offsetTop } = inputRef.current
...
}}
>
)
}