Реф странно обнуляется в React - PullRequest
0 голосов
/ 22 апреля 2020

Я пытаюсь сделать компонент скользящим с помощью мыши. Но при отпускании мыши ссылка на один из элементов DOM становится пустой во время выполнения, и я не понимаю, почему.

Я создал Песочницу , чтобы проиллюстрировать проблему. (это не полная реализация. Как раз то, что нужно для эксперимента с проблемой).

Просто перетащите элементы и проверьте консоль. Вы увидите, что ссылка изначально установлена ​​правильно, но при отпускании мыши ссылка исчезла.

Я не понимаю, когда это происходит и почему.

Вот полный Приложение. js файл:

import React from "react";
import styled from "styled-components";

const Container = styled.div`
  width: 100%;
  overflow: hidden;
`;

const Slides = styled.div`
  position: relative;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
`;

const Slide = styled.div`
  width: ${props => props.size}%;
  cursor: pointer;
  height: 100%;
  display: inline-block;
`;

const Carrousel = ({
  children,
  columnCount = 1,
  onSlideChanged = () => {},
  active = 0
}) => {
  const [dragging, setDragging] = React.useState(false);
  const initialMouseX = React.useRef(0); // Starting drag x coordinates
  const initialLeft = React.useRef(0); // Left value when the drag started
  const currentLeft = React.useRef(-active * (100 / children.length)); // Current left value
  const sliderRef = React.createRef();
  const slidesRef = React.createRef();

  // This will directly manipulate the DOM for better performances
  const setLeft = React.useCallback(
    newLeft => {
      currentLeft.current = newLeft;
      if (!slidesRef.current) return;
      slidesRef.current.style.left = currentLeft.current + "%";
    },
    [currentLeft, slidesRef]
  );

  // Starts the drag
  const startDrag = ({ screenX }) => {
    setDragging(true);
    initialMouseX.current = screenX;
    initialLeft.current = currentLeft.current;
  };
  // We need the references to be updated before being able to listen to move and mouseup events
  React.useLayoutEffect(() => {
    if (!dragging) return;
    console.log("at last render, slidesRef was:", slidesRef.current);
    const move = event => {
      if (dragging && sliderRef.current) {
        const width = sliderRef.current.getBoundingClientRect().width;
        const newLeft =
          initialLeft.current +
          ((event.screenX - initialMouseX.current) * 100) / width;
        setLeft(newLeft);
        event.preventDefault();
      }
    };
    const stopDrag = () => {
      setDragging(false);
      console.log("when calling stopDrag, slidesRef was:", slidesRef.current);
    };
    document.addEventListener("mouseup", stopDrag);
    document.addEventListener("mousemove", move);
    return () => {
      document.removeEventListener("mousemove", move);
      document.removeEventListener("mouseup", stopDrag);
    };
  }, [initialLeft, dragging, sliderRef, setLeft, slidesRef]);

  return (
    <Container ref={sliderRef}>
      <Slides ref={slidesRef} onMouseDown={startDrag}>
        {children.map((child, index) => (
          <Slide key={index} size={100 / columnCount}>
            {child}
          </Slide>
        ))}
      </Slides>
    </Container>
  );
};

const App = () => (
  <Carrousel columnCount={2}>
    {new Array(4).fill("").map((_, i) => (
      <div key={i}>
        <span>Test{i + 1}</span>
      </div>
    ))}
  </Carrousel>
);

export default App;

Проблема в этой части:

  React.useLayoutEffect(() => {
    if (!dragging) return;
    console.log("at last render, slidesRef was:", slidesRef.current);
    const move = event => {
      if (dragging && sliderRef.current) {
        const width = sliderRef.current.getBoundingClientRect().width;
        const newLeft =
          initialLeft.current +
          ((event.screenX - initialMouseX.current) * 100) / width;
        setLeft(newLeft);
        event.preventDefault();
      }
    };
    const stopDrag = () => {
      setDragging(false);
      console.log("when calling stopDrag, slidesRef was:", slidesRef.current);
    };
    document.addEventListener("mouseup", stopDrag);
    document.addEventListener("mousemove", move);
    return () => {
      document.removeEventListener("mousemove", move);
      document.removeEventListener("mouseup", stopDrag);
    };
  }, [initialLeft, dragging, sliderRef, setLeft, slidesRef]);

При перетаскивании вы получите: at last render, slidesRef was: <div ...>, а затем при выпуске: when calling stopDrag, slidesRef was: null

...