通过从类到功能组件迁移来反应状态问题

发布于 2025-01-19 03:59:39 字数 12307 浏览 0 评论 0原文

Problem

I'm converting a classful component into a functional component and state re-rendering has some issues.

Here is the original classful component:

class Skills extends React.Component {
  constructor(props) {
    super(props);
    const skills = [
      "HTML",
      "CSS",
      "SCSS",
      "Python",
      "JavaScript",
      "TypeScript",
      "Dart",
      "C++",
      "ReactJS",
      "Angular",
      "VueJS",
      "Flutter",
      "npm",
      "git",
      "pip",
      "Github",
      "Firebase",
      "Google Cloud",
    ];
    this.state = {
      skills: skills.sort(() => 0.5 - Math.random()),
      isLoaded: false,
      points: new Array(skills.length).fill([[0], [0], [-200]]),
      sphereLimit: 1,
      xRatio: Math.random() / 2,
      yRatio: Math.random() / 2,
      isMounted: true,
    };
  }

  fibSphere(samples = this.state.skills.length) {
    // https://stackoverflow.com/a/26127012/10472451
    const points = [];
    const phi = pi * (3 - sqrt(5));

    for (let i = 0; i < samples; i++) {
      const y = (i * 2) / samples - 1;
      const radius = sqrt(1 - y * y);

      const theta = phi * i;

      const x = cos(theta) * radius;
      const z = sin(theta) * radius;

      const itemLimit = this.state.sphereLimit * 0.75;

      points.push([[x * itemLimit], [y * itemLimit], [z * itemLimit]]);
    }
    this.setState({
      points: points,
      isLoaded: true,
    });
  }

  rotateSphere(samples = this.state.skills.length) {
    const newPoints = [];

    const thetaX = unit(-this.state.yRatio * 10, "deg");
    const thetaY = unit(this.state.xRatio * 10, "deg");
    const thetaZ = unit(0, "deg");

    const rotationMatrix = multiply(
      matrix([
        [1, 0, 0],
        [0, cos(thetaX), -sin(thetaX)],
        [0, sin(thetaX), cos(thetaX)],
      ]),
      matrix([
        [cos(thetaY), 0, sin(thetaY)],
        [0, 1, 0],
        [-sin(thetaY), 0, cos(thetaY)],
      ]),
      matrix([
        [cos(thetaZ), -sin(thetaZ), 0],
        [sin(thetaZ), cos(thetaZ), 0],
        [0, 0, 1],
      ])
    );

    for (let i = 0; i < samples; i++) {
      const currentPoint = this.state.points[i];
      const newPoint = multiply(rotationMatrix, currentPoint)._data;

      newPoints.push(newPoint);
    }

    if (this.state.isMounted) {
      this.setState({ points: newPoints });
      setTimeout(() => {
        this.rotateSphere();
      }, 100);
    }
  }

  handleMouseMove(e) {
    let xPosition = e.clientX;
    let yPosition = e.clientY;

    if (e.type === "touchmove") {
      xPosition = e.touches[0].pageX;
      yPosition = e.touches[0].pageY;
    }

    const spherePosition = document
      .getElementById("sphere")
      .getBoundingClientRect();

    const xDistance = xPosition - spherePosition.width / 2 - spherePosition.x;
    const yDistance = yPosition - spherePosition.height / 2 - spherePosition.y;

    const xRatio = xDistance / this.state.sphereLimit;
    const yRatio = yDistance / this.state.sphereLimit;

    this.setState({
      xRatio: xRatio,
      yRatio: yRatio,
    });
  }

  updateWindowDimensions() {
    try {
      const sphere = document.getElementById("sphere");

      if (
        this.state.sphereLimit !==
        Math.min(sphere.clientHeight, sphere.clientWidth) / 2
      ) {
        this.setState({
          sphereLimit: Math.min(sphere.clientHeight, sphere.clientWidth) / 2,
        });
        this.fibSphere();
      }
    } catch (error) {
      console.error(error);
    }
  }

  componentDidMount() {
    document.title =
      window.location.pathname === "/skills"
        ? "Josh Pollard | ⚙️"
        : document.title;

    setTimeout(() => {
      this.fibSphere();
      this.updateWindowDimensions();
      this.rotateSphere();
    }, 1500);

    window.addEventListener("resize", () => this.updateWindowDimensions());
  }

  componentWillUnmount() {
    this.setState({ isMounted: false });
    window.removeEventListener("resize", () => this.updateWindowDimensions());
  }

  render() {
    return (
      <motion.div
        className="skills-body"
        initial="initial"
        animate="animate"
        exit="exit"
        custom={window}
        variants={pageVariants}
        transition={pageTransition}
        onMouseMove={(e) => this.handleMouseMove(e)}
        onTouchMove={(e) => this.handleMouseMove(e)}
      >
        <div className="skills-info-container">
          <div className="skills-title">Skills</div>
          <div className="skills-description">
            I am a driven and passionate aspiring software engineer. I have
            invested a significant amount of time and effort in self-teaching,
            developing my knowledge and supporting others in the field of
            digital technology. I thrive on the challenge of finding intelligent
            solutions to complex problems and I am keen to apply and grow my
            skills in the workplace.
          </div>
        </div>
        <div className="sphere-container" id="sphere">
          {this.state.isLoaded &&
            this.state.skills.map((skill, index) => (
              <motion.div
                className="sphere-item"
                key={index}
                initial={{ opacity: 0 }}
                animate={{
                  x: this.state.points[index][0][0],
                  y: this.state.points[index][1][0] - 20,
                  z: this.state.points[index][2][0],
                  opacity: Math.max(
                    (this.state.points[index][2][0] / this.state.sphereLimit +
                      1) /
                      2,
                    0.1
                  ),
                }}
                transition={{
                  duration: 0.1,
                  ease: "linear",
                }}
              >
                {skill}
              </motion.div>
            ))}
        </div>
      </motion.div>
    );
  }
}

It's essentially a sphere of words that moves depending on mouse movement demo

Now this is as far as I have gotten with the migration to a Functional component:

function Skills(props) {
  const skills = [
    "HTML",
    "CSS",
    "SCSS",
    "Python",
    "JavaScript",
    "TypeScript",
    "Dart",
    "C++",
    "ReactJS",
    "Angular",
    "VueJS",
    "Flutter",
    "npm",
    "git",
    "pip",
    "Github",
    "Firebase",
    "Google Cloud",
  ].sort(() => 0.5 - Math.random());

  const [points, setPoints] = useState(
    new Array(skills.length).fill([0, 0, -200])
  );
  const [sphereLimit, setSphereLimit] = useState(1);
  const [xRatio, setXRatio] = useState(Math.random() / 2);
  const [yRatio, setYRatio] = useState(Math.random() / 2);
  const [isLoaded, setIsLoaded] = useState(false);

  useEffect(() => {
    document.title =
      window.location.pathname === "/skills"
        ? "Josh Pollard | ⚙️"
        : document.title;

    let interval;
    setTimeout(() => {
        updateWindowDimensions();
        interval = setInterval(rotateSphere, 100);
    }, 1500);
    window.addEventListener("resize", updateWindowDimensions);

    return () => {
      clearInterval(interval);
      window.removeEventListener("resize", updateWindowDimensions);
    };
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const fibSphere = (samples = skills.length) => {
    // https://stackoverflow.com/a/26127012/10472451
    const newPoints = [];
    const phi = pi * (3 - sqrt(5));

    for (let i = 0; i < samples; i++) {
      const y = (i * 2) / samples - 1;
      const radius = sqrt(1 - y * y);

      const theta = phi * i;

      const x = cos(theta) * radius;
      const z = sin(theta) * radius;

      const itemLimit = sphereLimit * 0.75;

      newPoints.push([x * itemLimit, y * itemLimit, z * itemLimit]);
    }
    console.log(newPoints);
    setPoints(newPoints);
    setIsLoaded(true);
  };

  const rotateSphere = (samples = skills.length) => {
    const newPoints = [];

    const thetaX = unit(-yRatio * 10, "deg");
    const thetaY = unit(xRatio * 10, "deg");
    const thetaZ = unit(0, "deg");

    const rotationMatrix = multiply(
      matrix([
        [1, 0, 0],
        [0, cos(thetaX), -sin(thetaX)],
        [0, sin(thetaX), cos(thetaX)],
      ]),
      matrix([
        [cos(thetaY), 0, sin(thetaY)],
        [0, 1, 0],
        [-sin(thetaY), 0, cos(thetaY)],
      ]),
      matrix([
        [cos(thetaZ), -sin(thetaZ), 0],
        [sin(thetaZ), cos(thetaZ), 0],
        [0, 0, 1],
      ])
    );

    for (let i = 0; i < samples; i++) {
      const currentPoint = points[i];
      const newPoint = multiply(rotationMatrix, currentPoint)._data;

      newPoints.push(newPoint);
    }
    console.log(newPoints[0]);
    console.log(points[0]);
    setPoints(newPoints);
  };

  const handleMouseMove = (e) => {
    let xPosition = e.clientX;
    let yPosition = e.clientY;

    if (e.type === "touchmove") {
      xPosition = e.touches[0].pageX;
      yPosition = e.touches[0].pageY;
    }

    const spherePosition = document
      .getElementById("sphere")
      .getBoundingClientRect();

    const xDistance = xPosition - spherePosition.width / 2 - spherePosition.x;
    const yDistance = yPosition - spherePosition.height / 2 - spherePosition.y;

    const xRatio = xDistance / sphereLimit;
    const yRatio = yDistance / sphereLimit;

    setXRatio(xRatio);
    setYRatio(yRatio);
  };

  const updateWindowDimensions = () => {
    try {
      const sphere = document.getElementById("sphere");

      if (
        sphereLimit !==
        Math.min(sphere.clientHeight, sphere.clientWidth) / 2
      ) {
        setSphereLimit(Math.min(sphere.clientHeight, sphere.clientWidth) / 2);
        fibSphere();
      }
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <motion.div
      className="skills-body"
      initial="initial"
      animate="animate"
      exit="exit"
      custom={window}
      variants={pageVariants}
      transition={pageTransition}
      onMouseMove={handleMouseMove}
      onTouchMove={handleMouseMove}
    >
      <div className="skills-info-container">
        <div className="skills-title">Skills</div>
        <div className="skills-description">
          I am a driven and passionate aspiring software engineer. I have
          invested a significant amount of time and effort in self-teaching,
          developing my knowledge and supporting others in the field of digital
          technology. I thrive on the challenge of finding intelligent solutions
          to complex problems and I am keen to apply and grow my skills in the
          workplace.
        </div>
      </div>
      <div className="sphere-container" id="sphere">
        {isLoaded &&
          skills.map((skill, index) => (
            <motion.div
              className="sphere-item"
              key={index}
              initial={{ opacity: 0 }}
              animate={{
                x: points[index][0],
                y: points[index][1] - 20,
                z: points[index][2],
                opacity: Math.max(
                  (points[index][2] / sphereLimit + 1) / 2,
                  0.1
                ),
              }}
              transition={{
                duration: 0.1,
                ease: "linear",
              }}
            >
              {skill}
            </motion.div>
          ))}
      </div>
    </motion.div>
  );
}

Investigation

Now when I run this functional version it seems that for every state update the component is 'reset', instead of updating the UI, here is a codesandbox env

When highlighting one of the 'skill' words in the browser, it seems要迅速切换长度(每100毫米,与旋转球体相同的间隔)。可以通过进入开发工具并看到每个“技能”单词每100毫秒每100ms更改一次来确认这一点。

除非我错了,否则这似乎根本不对。功能组件中的技能变量是const,因此不应该改变状态更改吗?

我觉得我缺少一些很明显的东西,任何帮助!

Problem

I'm converting a classful component into a functional component and state re-rendering has some issues.

Here is the original classful component:

class Skills extends React.Component {
  constructor(props) {
    super(props);
    const skills = [
      "HTML",
      "CSS",
      "SCSS",
      "Python",
      "JavaScript",
      "TypeScript",
      "Dart",
      "C++",
      "ReactJS",
      "Angular",
      "VueJS",
      "Flutter",
      "npm",
      "git",
      "pip",
      "Github",
      "Firebase",
      "Google Cloud",
    ];
    this.state = {
      skills: skills.sort(() => 0.5 - Math.random()),
      isLoaded: false,
      points: new Array(skills.length).fill([[0], [0], [-200]]),
      sphereLimit: 1,
      xRatio: Math.random() / 2,
      yRatio: Math.random() / 2,
      isMounted: true,
    };
  }

  fibSphere(samples = this.state.skills.length) {
    // https://stackoverflow.com/a/26127012/10472451
    const points = [];
    const phi = pi * (3 - sqrt(5));

    for (let i = 0; i < samples; i++) {
      const y = (i * 2) / samples - 1;
      const radius = sqrt(1 - y * y);

      const theta = phi * i;

      const x = cos(theta) * radius;
      const z = sin(theta) * radius;

      const itemLimit = this.state.sphereLimit * 0.75;

      points.push([[x * itemLimit], [y * itemLimit], [z * itemLimit]]);
    }
    this.setState({
      points: points,
      isLoaded: true,
    });
  }

  rotateSphere(samples = this.state.skills.length) {
    const newPoints = [];

    const thetaX = unit(-this.state.yRatio * 10, "deg");
    const thetaY = unit(this.state.xRatio * 10, "deg");
    const thetaZ = unit(0, "deg");

    const rotationMatrix = multiply(
      matrix([
        [1, 0, 0],
        [0, cos(thetaX), -sin(thetaX)],
        [0, sin(thetaX), cos(thetaX)],
      ]),
      matrix([
        [cos(thetaY), 0, sin(thetaY)],
        [0, 1, 0],
        [-sin(thetaY), 0, cos(thetaY)],
      ]),
      matrix([
        [cos(thetaZ), -sin(thetaZ), 0],
        [sin(thetaZ), cos(thetaZ), 0],
        [0, 0, 1],
      ])
    );

    for (let i = 0; i < samples; i++) {
      const currentPoint = this.state.points[i];
      const newPoint = multiply(rotationMatrix, currentPoint)._data;

      newPoints.push(newPoint);
    }

    if (this.state.isMounted) {
      this.setState({ points: newPoints });
      setTimeout(() => {
        this.rotateSphere();
      }, 100);
    }
  }

  handleMouseMove(e) {
    let xPosition = e.clientX;
    let yPosition = e.clientY;

    if (e.type === "touchmove") {
      xPosition = e.touches[0].pageX;
      yPosition = e.touches[0].pageY;
    }

    const spherePosition = document
      .getElementById("sphere")
      .getBoundingClientRect();

    const xDistance = xPosition - spherePosition.width / 2 - spherePosition.x;
    const yDistance = yPosition - spherePosition.height / 2 - spherePosition.y;

    const xRatio = xDistance / this.state.sphereLimit;
    const yRatio = yDistance / this.state.sphereLimit;

    this.setState({
      xRatio: xRatio,
      yRatio: yRatio,
    });
  }

  updateWindowDimensions() {
    try {
      const sphere = document.getElementById("sphere");

      if (
        this.state.sphereLimit !==
        Math.min(sphere.clientHeight, sphere.clientWidth) / 2
      ) {
        this.setState({
          sphereLimit: Math.min(sphere.clientHeight, sphere.clientWidth) / 2,
        });
        this.fibSphere();
      }
    } catch (error) {
      console.error(error);
    }
  }

  componentDidMount() {
    document.title =
      window.location.pathname === "/skills"
        ? "Josh Pollard | ⚙️"
        : document.title;

    setTimeout(() => {
      this.fibSphere();
      this.updateWindowDimensions();
      this.rotateSphere();
    }, 1500);

    window.addEventListener("resize", () => this.updateWindowDimensions());
  }

  componentWillUnmount() {
    this.setState({ isMounted: false });
    window.removeEventListener("resize", () => this.updateWindowDimensions());
  }

  render() {
    return (
      <motion.div
        className="skills-body"
        initial="initial"
        animate="animate"
        exit="exit"
        custom={window}
        variants={pageVariants}
        transition={pageTransition}
        onMouseMove={(e) => this.handleMouseMove(e)}
        onTouchMove={(e) => this.handleMouseMove(e)}
      >
        <div className="skills-info-container">
          <div className="skills-title">Skills</div>
          <div className="skills-description">
            I am a driven and passionate aspiring software engineer. I have
            invested a significant amount of time and effort in self-teaching,
            developing my knowledge and supporting others in the field of
            digital technology. I thrive on the challenge of finding intelligent
            solutions to complex problems and I am keen to apply and grow my
            skills in the workplace.
          </div>
        </div>
        <div className="sphere-container" id="sphere">
          {this.state.isLoaded &&
            this.state.skills.map((skill, index) => (
              <motion.div
                className="sphere-item"
                key={index}
                initial={{ opacity: 0 }}
                animate={{
                  x: this.state.points[index][0][0],
                  y: this.state.points[index][1][0] - 20,
                  z: this.state.points[index][2][0],
                  opacity: Math.max(
                    (this.state.points[index][2][0] / this.state.sphereLimit +
                      1) /
                      2,
                    0.1
                  ),
                }}
                transition={{
                  duration: 0.1,
                  ease: "linear",
                }}
              >
                {skill}
              </motion.div>
            ))}
        </div>
      </motion.div>
    );
  }
}

It's essentially a sphere of words that moves depending on mouse movement demo

Now this is as far as I have gotten with the migration to a Functional component:

function Skills(props) {
  const skills = [
    "HTML",
    "CSS",
    "SCSS",
    "Python",
    "JavaScript",
    "TypeScript",
    "Dart",
    "C++",
    "ReactJS",
    "Angular",
    "VueJS",
    "Flutter",
    "npm",
    "git",
    "pip",
    "Github",
    "Firebase",
    "Google Cloud",
  ].sort(() => 0.5 - Math.random());

  const [points, setPoints] = useState(
    new Array(skills.length).fill([0, 0, -200])
  );
  const [sphereLimit, setSphereLimit] = useState(1);
  const [xRatio, setXRatio] = useState(Math.random() / 2);
  const [yRatio, setYRatio] = useState(Math.random() / 2);
  const [isLoaded, setIsLoaded] = useState(false);

  useEffect(() => {
    document.title =
      window.location.pathname === "/skills"
        ? "Josh Pollard | ⚙️"
        : document.title;

    let interval;
    setTimeout(() => {
        updateWindowDimensions();
        interval = setInterval(rotateSphere, 100);
    }, 1500);
    window.addEventListener("resize", updateWindowDimensions);

    return () => {
      clearInterval(interval);
      window.removeEventListener("resize", updateWindowDimensions);
    };
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const fibSphere = (samples = skills.length) => {
    // https://stackoverflow.com/a/26127012/10472451
    const newPoints = [];
    const phi = pi * (3 - sqrt(5));

    for (let i = 0; i < samples; i++) {
      const y = (i * 2) / samples - 1;
      const radius = sqrt(1 - y * y);

      const theta = phi * i;

      const x = cos(theta) * radius;
      const z = sin(theta) * radius;

      const itemLimit = sphereLimit * 0.75;

      newPoints.push([x * itemLimit, y * itemLimit, z * itemLimit]);
    }
    console.log(newPoints);
    setPoints(newPoints);
    setIsLoaded(true);
  };

  const rotateSphere = (samples = skills.length) => {
    const newPoints = [];

    const thetaX = unit(-yRatio * 10, "deg");
    const thetaY = unit(xRatio * 10, "deg");
    const thetaZ = unit(0, "deg");

    const rotationMatrix = multiply(
      matrix([
        [1, 0, 0],
        [0, cos(thetaX), -sin(thetaX)],
        [0, sin(thetaX), cos(thetaX)],
      ]),
      matrix([
        [cos(thetaY), 0, sin(thetaY)],
        [0, 1, 0],
        [-sin(thetaY), 0, cos(thetaY)],
      ]),
      matrix([
        [cos(thetaZ), -sin(thetaZ), 0],
        [sin(thetaZ), cos(thetaZ), 0],
        [0, 0, 1],
      ])
    );

    for (let i = 0; i < samples; i++) {
      const currentPoint = points[i];
      const newPoint = multiply(rotationMatrix, currentPoint)._data;

      newPoints.push(newPoint);
    }
    console.log(newPoints[0]);
    console.log(points[0]);
    setPoints(newPoints);
  };

  const handleMouseMove = (e) => {
    let xPosition = e.clientX;
    let yPosition = e.clientY;

    if (e.type === "touchmove") {
      xPosition = e.touches[0].pageX;
      yPosition = e.touches[0].pageY;
    }

    const spherePosition = document
      .getElementById("sphere")
      .getBoundingClientRect();

    const xDistance = xPosition - spherePosition.width / 2 - spherePosition.x;
    const yDistance = yPosition - spherePosition.height / 2 - spherePosition.y;

    const xRatio = xDistance / sphereLimit;
    const yRatio = yDistance / sphereLimit;

    setXRatio(xRatio);
    setYRatio(yRatio);
  };

  const updateWindowDimensions = () => {
    try {
      const sphere = document.getElementById("sphere");

      if (
        sphereLimit !==
        Math.min(sphere.clientHeight, sphere.clientWidth) / 2
      ) {
        setSphereLimit(Math.min(sphere.clientHeight, sphere.clientWidth) / 2);
        fibSphere();
      }
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <motion.div
      className="skills-body"
      initial="initial"
      animate="animate"
      exit="exit"
      custom={window}
      variants={pageVariants}
      transition={pageTransition}
      onMouseMove={handleMouseMove}
      onTouchMove={handleMouseMove}
    >
      <div className="skills-info-container">
        <div className="skills-title">Skills</div>
        <div className="skills-description">
          I am a driven and passionate aspiring software engineer. I have
          invested a significant amount of time and effort in self-teaching,
          developing my knowledge and supporting others in the field of digital
          technology. I thrive on the challenge of finding intelligent solutions
          to complex problems and I am keen to apply and grow my skills in the
          workplace.
        </div>
      </div>
      <div className="sphere-container" id="sphere">
        {isLoaded &&
          skills.map((skill, index) => (
            <motion.div
              className="sphere-item"
              key={index}
              initial={{ opacity: 0 }}
              animate={{
                x: points[index][0],
                y: points[index][1] - 20,
                z: points[index][2],
                opacity: Math.max(
                  (points[index][2] / sphereLimit + 1) / 2,
                  0.1
                ),
              }}
              transition={{
                duration: 0.1,
                ease: "linear",
              }}
            >
              {skill}
            </motion.div>
          ))}
      </div>
    </motion.div>
  );
}

Investigation

Now when I run this functional version it seems that for every state update the component is 'reset', instead of updating the UI, here is a codesandbox env

When highlighting one of the 'skill' words in the browser, it seems to be switching length very quickly (every 100ms, the same interval as the rotation sphere). This can be confirmed by going into dev tools and seeing that each 'skill' word changes every 100ms.

Unless I've got this wrong, this doesn't seem right at all. The skills variable in the functional component is a const so shouldn't change on state changes?

I feel like I'm missing something very obvious, any help appreciated!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

二智少女 2025-01-26 03:59:39

这是类组件的一个工作部分(功能组件):

import React, { useEffect, useRef, useState } from 'react'
import { motion } from 'framer-motion'
import { matrix, multiply, sin, cos, sqrt, pi, unit } from 'mathjs'
import './skills.scss'

const pageTransition = {
  ease: [0.94, 0.06, 0.88, 0.45],
  duration: 1,
  delay: 0.5,
}

const pageVariants = {
  initial: (window) => ({
    position: 'fixed',
    clipPath: `circle(0px at ${window.innerWidth / 2}px ${
      window.innerHeight / 2
    }px)`,
  }),
  animate: (window) => ({
    clipPath: `circle(${
      Math.max(window.innerWidth, window.innerHeight) * 4
    }px at ${window.innerWidth / 2}px ${window.innerHeight / 2}px)`,
    position: 'absolute',
  }),
  exit: {
    display: 'fixed',
  },
}

const skills = [
  'HTML',
  'CSS',
  'SCSS',
  'Python',
  'JavaScript',
  'TypeScript',
  'Dart',
  'C++',
  'ReactJS',
  'Angular',
  'VueJS',
  'Flutter',
  'npm',
  'git',
  'pip',
  'Github',
  'Firebase',
  'Google Cloud',
]

export default function Skills() {
  const sphere = useRef(undefined)

  const [isLoaded, setIsLoaded] = useState(true)
  const [points, setPoints] = useState(
    new Array(skills.length).fill([[0], [0], [-200]]),
  )
  const [sphereLimit, setSphereLimit] = useState(1)
  const [xRatio, setXRatio] = useState(Math.random() / 2)
  const [yRatio, setYRatio] = useState(Math.random() / 2)
  const [triggerBy, setTriggerBy] = useState('')

  const [sphereItem, setSphereItem] = useState([])

  useEffect(() => {
    if (triggerBy === 'fibSphere') {
      rotateSphere()
    }

    if (triggerBy === 'rotateSphere') {
      setTimeout(() => {
        rotateSphere()
      }, 100)
    }
  })

  useEffect(() => {
    fibSphere()
    updateWindowDimensions()
    //rotateSphere();
  }, [sphereItem])

  const fibSphere = () => {
    // https://stackoverflow.com/a/26127012/10472451
    let newPoints = []
    const phi = pi * (3 - sqrt(5))

    for (let i = 0; i < skills.length; i++) {
      const y = (i * 2) / skills.length - 1
      const radius = sqrt(1 - y * y)

      const theta = phi * i

      const x = cos(theta) * radius
      const z = sin(theta) * radius

      const itemLimit = sphereLimit * 0.75

      newPoints.push([[x * itemLimit], [y * itemLimit], [z * itemLimit]])
    }
    setPoints(newPoints)
    setIsLoaded(true)
    setTriggerBy('fibSphere')
  }

  const rotateSphere = () => {
    let newPoints = []

    const thetaX = unit(-1 * yRatio * 10, 'deg')
    const thetaY = unit(xRatio * 10, 'deg')
    const thetaZ = unit(0, 'deg')

    const rotationMatrix = multiply(
      matrix([
        [1, 0, 0],
        [0, cos(thetaX), -sin(thetaX)],
        [0, sin(thetaX), cos(thetaX)],
      ]),
      matrix([
        [cos(thetaY), 0, sin(thetaY)],
        [0, 1, 0],
        [-sin(thetaY), 0, cos(thetaY)],
      ]),
      matrix([
        [cos(thetaZ), -sin(thetaZ), 0],
        [sin(thetaZ), cos(thetaZ), 0],
        [0, 0, 1],
      ]),
    )

    for (let i = 0; i < skills.length; i++) {
      const currentPoint = points[i]
      const newPoint = multiply(rotationMatrix, currentPoint)._data

      newPoints.push(newPoint)
    }

    setPoints(newPoints)
    setTriggerBy('rotateSphere')
  }

  const updateWindowDimensions = () => {
    console.log('sphere', sphere)
    try {
      if (
        sphereLimit !==
        Math.min(sphere.current.clientHeight, sphere.current.clientWidth) / 2
      ) {
        setSphereLimit(
          Math.min(sphere.current.clientHeight, sphere.current.clientWidth) / 2,
        )
        fibSphere()
      }
    } catch (error) {
      console.error(error)
    }
  }

  const handleMouseMove = (e) => {
    let xPosition = e.clientX
    let yPosition = e.clientY

    if (e.type === 'touchmove') {
      xPosition = e.touches[0].pageX
      yPosition = e.touches[0].pageY
    }

    const spherePosition = document
      .getElementById('sphere')
      .getBoundingClientRect()

    const xDistance = xPosition - spherePosition.width / 2 - spherePosition.x
    const yDistance = yPosition - spherePosition.height / 2 - spherePosition.y

    const _xRatio = xDistance / sphereLimit
    const _yRatio = yDistance / sphereLimit

    setXRatio(_xRatio)
    setYRatio(_yRatio)
    setTriggerBy('ratios')
  }

  const addSphereItems = (ref) => {
    setSphereItem((prev) => [...prev, ref])
  }

  return (
    <motion.div
      className="skills-body"
      initial="initial"
      animate="animate"
      exit="exit"
      custom={window}
      variants={pageVariants}
      transition={pageTransition}
      onMouseMove={handleMouseMove}
      onTouchMove={handleMouseMove}
    >
      <div className="skills-info-container">
        <div className="skills-title">Skills</div>
        <div className="skills-description">
          I am a driven and passionate aspiring software engineer. I have
          invested a significant amount of time and effort in self-teaching,
          developing my knowledge and supporting others in the field of digital
          technology. I thrive on the challenge of finding intelligent solutions
          to complex problems and I am keen to apply and grow my skills in the
          workplace.
        </div>
      </div>
      <div className="sphere-container" id="sphere" ref={sphere}>
        {isLoaded &&
          skills.map((skill, index) => {
            return (
              <motion.div
                ref={addSphereItems}
                className="sphere-item"
                key={index}
                initial={{ opacity: 0 }}
                animate={{
                  x:
                    sphereItem && sphereItem[index]
                      ? points[index][0][0] - sphereItem[index].clientWidth / 2
                      : points[index][0][0],

                  y: points[index][1][0] - 20,

                  z: points[index][2][0],
                  opacity: Math.max(
                    (points[index][2][0] / sphereLimit + 1) / 2,
                    0.1,
                  ),
                }}
                transition={{
                  duration: 0.1,
                  ease: 'linear',
                }}
              >
                {skill}
              </motion.div>
            )
          })}
      </div>
    </motion.div>
  )
}

您似乎正在丢失 points 状态值,因为您从代码的不同部分触发 setPoints同一个周期。

功能组件的作用是在一个刷新周期内批量更新。

所以问题是函数 fibSphererotateSphere 在同一个周期中调用,第一个函数 fibSphere 虽然确实触发了 setPoints< /code> 但点的值没有改变,因为 (rotateSphere) 之后调用的函数也触发了 setPoints。这两个都是分批的。

因此,为了让您的代码正常工作,操作的顺序是,一旦 fibSphere 完成设置点,那么(并且只有那时)应该 rotateSphere 触发并更新

我引入了useEffect(没有依赖数组,即在每次更新时触发),并利用状态变量triggerBy来实际查看每次更新何时发生在点< /code> 并相应地执行操作顺序。

Here is a working piece (functional component) of your class component:

import React, { useEffect, useRef, useState } from 'react'
import { motion } from 'framer-motion'
import { matrix, multiply, sin, cos, sqrt, pi, unit } from 'mathjs'
import './skills.scss'

const pageTransition = {
  ease: [0.94, 0.06, 0.88, 0.45],
  duration: 1,
  delay: 0.5,
}

const pageVariants = {
  initial: (window) => ({
    position: 'fixed',
    clipPath: `circle(0px at ${window.innerWidth / 2}px ${
      window.innerHeight / 2
    }px)`,
  }),
  animate: (window) => ({
    clipPath: `circle(${
      Math.max(window.innerWidth, window.innerHeight) * 4
    }px at ${window.innerWidth / 2}px ${window.innerHeight / 2}px)`,
    position: 'absolute',
  }),
  exit: {
    display: 'fixed',
  },
}

const skills = [
  'HTML',
  'CSS',
  'SCSS',
  'Python',
  'JavaScript',
  'TypeScript',
  'Dart',
  'C++',
  'ReactJS',
  'Angular',
  'VueJS',
  'Flutter',
  'npm',
  'git',
  'pip',
  'Github',
  'Firebase',
  'Google Cloud',
]

export default function Skills() {
  const sphere = useRef(undefined)

  const [isLoaded, setIsLoaded] = useState(true)
  const [points, setPoints] = useState(
    new Array(skills.length).fill([[0], [0], [-200]]),
  )
  const [sphereLimit, setSphereLimit] = useState(1)
  const [xRatio, setXRatio] = useState(Math.random() / 2)
  const [yRatio, setYRatio] = useState(Math.random() / 2)
  const [triggerBy, setTriggerBy] = useState('')

  const [sphereItem, setSphereItem] = useState([])

  useEffect(() => {
    if (triggerBy === 'fibSphere') {
      rotateSphere()
    }

    if (triggerBy === 'rotateSphere') {
      setTimeout(() => {
        rotateSphere()
      }, 100)
    }
  })

  useEffect(() => {
    fibSphere()
    updateWindowDimensions()
    //rotateSphere();
  }, [sphereItem])

  const fibSphere = () => {
    // https://stackoverflow.com/a/26127012/10472451
    let newPoints = []
    const phi = pi * (3 - sqrt(5))

    for (let i = 0; i < skills.length; i++) {
      const y = (i * 2) / skills.length - 1
      const radius = sqrt(1 - y * y)

      const theta = phi * i

      const x = cos(theta) * radius
      const z = sin(theta) * radius

      const itemLimit = sphereLimit * 0.75

      newPoints.push([[x * itemLimit], [y * itemLimit], [z * itemLimit]])
    }
    setPoints(newPoints)
    setIsLoaded(true)
    setTriggerBy('fibSphere')
  }

  const rotateSphere = () => {
    let newPoints = []

    const thetaX = unit(-1 * yRatio * 10, 'deg')
    const thetaY = unit(xRatio * 10, 'deg')
    const thetaZ = unit(0, 'deg')

    const rotationMatrix = multiply(
      matrix([
        [1, 0, 0],
        [0, cos(thetaX), -sin(thetaX)],
        [0, sin(thetaX), cos(thetaX)],
      ]),
      matrix([
        [cos(thetaY), 0, sin(thetaY)],
        [0, 1, 0],
        [-sin(thetaY), 0, cos(thetaY)],
      ]),
      matrix([
        [cos(thetaZ), -sin(thetaZ), 0],
        [sin(thetaZ), cos(thetaZ), 0],
        [0, 0, 1],
      ]),
    )

    for (let i = 0; i < skills.length; i++) {
      const currentPoint = points[i]
      const newPoint = multiply(rotationMatrix, currentPoint)._data

      newPoints.push(newPoint)
    }

    setPoints(newPoints)
    setTriggerBy('rotateSphere')
  }

  const updateWindowDimensions = () => {
    console.log('sphere', sphere)
    try {
      if (
        sphereLimit !==
        Math.min(sphere.current.clientHeight, sphere.current.clientWidth) / 2
      ) {
        setSphereLimit(
          Math.min(sphere.current.clientHeight, sphere.current.clientWidth) / 2,
        )
        fibSphere()
      }
    } catch (error) {
      console.error(error)
    }
  }

  const handleMouseMove = (e) => {
    let xPosition = e.clientX
    let yPosition = e.clientY

    if (e.type === 'touchmove') {
      xPosition = e.touches[0].pageX
      yPosition = e.touches[0].pageY
    }

    const spherePosition = document
      .getElementById('sphere')
      .getBoundingClientRect()

    const xDistance = xPosition - spherePosition.width / 2 - spherePosition.x
    const yDistance = yPosition - spherePosition.height / 2 - spherePosition.y

    const _xRatio = xDistance / sphereLimit
    const _yRatio = yDistance / sphereLimit

    setXRatio(_xRatio)
    setYRatio(_yRatio)
    setTriggerBy('ratios')
  }

  const addSphereItems = (ref) => {
    setSphereItem((prev) => [...prev, ref])
  }

  return (
    <motion.div
      className="skills-body"
      initial="initial"
      animate="animate"
      exit="exit"
      custom={window}
      variants={pageVariants}
      transition={pageTransition}
      onMouseMove={handleMouseMove}
      onTouchMove={handleMouseMove}
    >
      <div className="skills-info-container">
        <div className="skills-title">Skills</div>
        <div className="skills-description">
          I am a driven and passionate aspiring software engineer. I have
          invested a significant amount of time and effort in self-teaching,
          developing my knowledge and supporting others in the field of digital
          technology. I thrive on the challenge of finding intelligent solutions
          to complex problems and I am keen to apply and grow my skills in the
          workplace.
        </div>
      </div>
      <div className="sphere-container" id="sphere" ref={sphere}>
        {isLoaded &&
          skills.map((skill, index) => {
            return (
              <motion.div
                ref={addSphereItems}
                className="sphere-item"
                key={index}
                initial={{ opacity: 0 }}
                animate={{
                  x:
                    sphereItem && sphereItem[index]
                      ? points[index][0][0] - sphereItem[index].clientWidth / 2
                      : points[index][0][0],

                  y: points[index][1][0] - 20,

                  z: points[index][2][0],
                  opacity: Math.max(
                    (points[index][2][0] / sphereLimit + 1) / 2,
                    0.1,
                  ),
                }}
                transition={{
                  duration: 0.1,
                  ease: 'linear',
                }}
              >
                {skill}
              </motion.div>
            )
          })}
      </div>
    </motion.div>
  )
}

You seemed to be loosing the points state value because you were triggering setPoints from different parts of the your code in the same cycle.

What functional component does is that it batches the updates for one refresh cycle.

So the problem was that the functions fibSphere and rotateSphere were called in the same cycle, the first function fibSphere althought did trigger setPoints but the value for points did not change, since the function called after (rotateSphere) also triggered setPoints. Both of these were batched.

So the order of operations, in order for your code to work was that once fibSphere completed setting points, then (and only then) should rotateSphere trigger and update points.

I introduced theuseEffect (without dependancy array i.e. that triggers on each update) and made use of a state variable triggerBy to actually see when each update happens on points and execute the order of operations accordingly.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文