有类到功能组件迁移的 React 状态问题
React State Issues with Classful to Functional Component Migration
问题
我正在将有类组件转换为功能组件,但状态重新渲染存在一些问题。
这是原始的有类组件:
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) {
//
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>
);
}
}
它本质上是一个根据鼠标移动而移动的单词球体demo
现在,这是我迁移到功能组件所取得的进展:
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) => {
//
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>
);
}
调查
现在,当我 运行 这个功能版本时,组件的每个状态更新似乎是 'reset',而不是更新 UI,这里是 codesandbox env
在浏览器中高亮'skill'其中一个单词时,它似乎在非常快速地切换长度(每100ms,与旋转球体相同的间隔)。这可以通过进入开发工具并查看每个 'skill' 字每 100 毫秒更改一次来确认。
除非我弄错了,否则这似乎根本不对。功能组件中的 skills 变量是 const
,所以不应该随状态变化而改变吗?
我觉得我遗漏了一些非常明显的东西,感谢任何帮助!
这是您的 class 组件的工作部分(功能组件):
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 = () => {
//
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
。
功能组件的作用是对一个刷新周期的更新进行批处理。
所以问题是函数 fibSphere
和 rotateSphere
在同一个循环中被调用,第一个函数 fibSphere
虽然触发了 setPoints
但值点没有改变,因为在 (rotateSphere
) 之后调用的函数也触发了 setPoints
。这两个都是批处理的。
因此,为了让您的代码正常工作,操作顺序是一旦 fibSphere
完成设置点,然后(并且只有那时)应该 rotateSphere
触发并更新 points
.
我引入了useEffect
(没有依赖数组,即在每次更新时触发)并使用状态变量 triggerBy
来实际查看每次更新何时发生在 points
和相应地执行操作顺序。
问题
我正在将有类组件转换为功能组件,但状态重新渲染存在一些问题。
这是原始的有类组件:
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) {
//
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>
);
}
}
它本质上是一个根据鼠标移动而移动的单词球体demo
现在,这是我迁移到功能组件所取得的进展:
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) => {
//
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>
);
}
调查
现在,当我 运行 这个功能版本时,组件的每个状态更新似乎是 'reset',而不是更新 UI,这里是 codesandbox env
在浏览器中高亮'skill'其中一个单词时,它似乎在非常快速地切换长度(每100ms,与旋转球体相同的间隔)。这可以通过进入开发工具并查看每个 'skill' 字每 100 毫秒更改一次来确认。
除非我弄错了,否则这似乎根本不对。功能组件中的 skills 变量是 const
,所以不应该随状态变化而改变吗?
我觉得我遗漏了一些非常明显的东西,感谢任何帮助!
这是您的 class 组件的工作部分(功能组件):
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 = () => {
//
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
。
功能组件的作用是对一个刷新周期的更新进行批处理。
所以问题是函数 fibSphere
和 rotateSphere
在同一个循环中被调用,第一个函数 fibSphere
虽然触发了 setPoints
但值点没有改变,因为在 (rotateSphere
) 之后调用的函数也触发了 setPoints
。这两个都是批处理的。
因此,为了让您的代码正常工作,操作顺序是一旦 fibSphere
完成设置点,然后(并且只有那时)应该 rotateSphere
触发并更新 points
.
我引入了useEffect
(没有依赖数组,即在每次更新时触发)并使用状态变量 triggerBy
来实际查看每次更新何时发生在 points
和相应地执行操作顺序。