在 React TS 中创建进度条 - 隐式获取错误具有类型 'any' 尽管类型是在接口中声明的



我正试图在React Typescript中重新创建这个Codepen。我将class重命名为className,并将css粘贴到我的App.css中。我开始在接口中创建类型,并将其传递给我的函数。但我仍然得到了例如previousTimestamp和其他人的错误:implicitly has type 'any' in some locations where its type cannot be determined.

当我在界面中说它有数字类型时,为什么它说它有任何类型?

应用程序.tsx

import React from 'react';
import './App.css';
export interface Props{
reatTotal: number;
apparentTotal: number
pixelsProgressed: number;
apparentPixelsProgressed: number;
previousTimestamp: number;
replayButton: HTMLElement;
}

function App(props: Props) {
// Internal store
let realTotal = 0;
let apparentTotal = 0;
let pixelsProgressed;
let apparentPixelsProgressed;
let previousTimestamp;
// Config
const pixelPerSecond = 50;
const depthExaggeration = 2;
const loaderHeight = 40;
// DOM elements
const stage = document.getElementById('stage');
const loader = document.getElementById('loader');
const apparentProgressText = document.getElementById('loader__text');
const replayButton = document.getElementById('control__replay');
const segments = document.querySelectorAll('.loader__segment');
const perspective = document.getElementById('perspective');
// Borrowed from https://gist.github.com/gre/1650294
function easeInOutQuad(t) {
return t < 0.5 ? 2*t*t : -1+(4-2*t)*t;
}
function reset() {
pixelsProgressed = 0;
apparentPixelsProgressed = 0;
previousTimestamp = null;

segments.forEach(el => el.style.setProperty('--backgroundTranslateX', '-100%'));
}
function play() {
reset();
window.requestAnimationFrame(step);
}
document.documentElement.style.setProperty('--loaderHeight', `${loaderHeight}px`);
segments.forEach(el => {
let { startX, endX, startZ, endZ } = el.dataset;

startZ = (startZ ?? 0) * depthExaggeration;
endZ = (endZ ?? 0) * depthExaggeration;

let width = +endX - +startX;
let translateZ = 0;
let rotateY = 0;
let transformOrigin = '50% 50%';

if (startZ === endZ) {
translateZ = -startZ;
el.dataset.apparentStart = apparentTotal;
apparentTotal += width;
el.dataset.apparentEnd = apparentTotal;
} else {
const deltaZ = +endZ - +startZ;
width = Math.abs(+endZ - +startZ);

if (deltaZ > 0) {
rotateY = 90;
transformOrigin = '0 50%';
translateZ = width - endZ;
} else {
rotateY = -90;
transformOrigin = '0 50%';
translateZ = -width - endZ;
}
}

el.dataset.start = realTotal;
realTotal += width;
el.dataset.end = realTotal

el.style.width = `${width}px`;
el.style.transform = `translateX(${startX}px) translateZ(${translateZ}px) rotateY(${rotateY}deg)`;
el.style.transformOrigin = transformOrigin;

document.querySelector('#loader').style.width = `${apparentTotal}px`;
});
function setScale() {
const stageWidth = stage.clientWidth;
const stageHeight = stage.clientHeight;
const stageAspectRatio = stageWidth / stageHeight;

const loaderOuterWidth = apparentTotal;
const loaderOuterHeight = loaderHeight * 4;
const loaderAspectRatio = loaderOuterWidth / loaderOuterHeight;

const constrain = 0.8;
if (stageAspectRatio < loaderAspectRatio)
document.documentElement.style.setProperty('--scale', (stageWidth * constrain / loaderOuterWidth));
else
document.documentElement.style.setProperty('--scale', (stageHeight * constrain / loaderOuterHeight));
}
setScale();
window.addEventListener('resize', () => setScale());
function step(timestamp) {
if (!previousTimestamp)
previousTimestamp = timestamp;

pixelsProgressed += (timestamp - previousTimestamp) / 1000 * pixelPerSecond;
const pixelsProgressedEased = easeInOutQuad(pixelsProgressed / realTotal) * realTotal;
previousTimestamp = timestamp;

segments.forEach(el => {
const { start, end, apparentStart } = el.dataset;

if (pixelsProgressedEased < +end && pixelsProgressedEased > +start) {
const width = +end - +start;
el.style.setProperty('--backgroundTranslateX', `${-width + pixelsProgressedEased - +start}px`);

if (apparentStart)
apparentPixelsProgressed = +apparentStart + (pixelsProgressedEased - +start);

} else if (pixelsProgressedEased >= +start) {
el.style.setProperty('--backgroundTranslateX', '0px');
}
});

if (apparentPixelsProgressed > 0.995 * apparentTotal) {
apparentPixelsProgressed = apparentTotal;
apparentProgressText.innerText = 'Done!';
replayButton.innerText = 'Replay';
replayButton.disabled = false;
} else {
apparentProgressText.innerText = `Loading… ${Math.round(apparentPixelsProgressed / apparentTotal * 100)}%`;
replayButton.innerText = 'Playing…';
replayButton.disabled = true;
}
if (pixelsProgressed < realTotal)
window.requestAnimationFrame(step);
}
play();
replayButton.addEventListener('click', () => play());
stage.addEventListener('click', () => {
stage.classList.toggle('is-isometric');
perspective.checked = stage.classList.contains('is-isometric');
});
perspective.addEventListener('input', () => {
if (perspective.checked)
stage.classList.add('is-isometric');
else
stage.classList.remove('is-isometric');
});
return (
<div id="container">
<div id="stage">
<div id="loader">
<div id="loader__text">
Loading&hellip;0%
</div>
<div className="loader__segment loader__segment--start" data-start-x="0" data-end-x="150"></div>
<div className="loader__segment" data-start-x="150" data-end-x="150" data-start-z="0" data-end-z="25"></div>
<div className="loader__segment" data-start-x="150" data-end-x="165" data-start-z="25" data-end-z="25"></div>
<div className="loader__segment" data-start-x="165" data-end-x="165" data-start-z="25" data-end-z="0"></div>
<div className="loader__segment" data-start-x="165" data-end-x="175"></div>
<div className="loader__segment" data-start-x="175" data-end-x="175" data-start-z="0" data-end-z="50"></div>
<div className="loader__segment" data-start-x="175" data-end-x="190" data-start-z="50" data-end-z="50"></div>
<div className="loader__segment" data-start-x="190" data-end-x="190" data-start-z="50" data-end-z="-20"></div>
<div className="loader__segment" data-start-x="190" data-end-x="225" data-start-z="-20" data-end-z="-20"></div>
<div className="loader__segment" data-start-x="225" data-end-x="225" data-start-z="-20" data-end-z="10"></div>
<div className="loader__segment" data-start-x="225" data-end-x="260" data-start-z="10" data-end-z="10"></div>
<div className="loader__segment" data-start-x="260" data-end-x="260" data-start-z="10" data-end-z="0"></div>
<div className="loader__segment loader__segment--end" data-start-x="260" data-end-x="300"></div>
</div>
</div>
<div id="footer">
<div id="footer__controls">
<button type="button" id="control__replay">
Replay
</button>
<label className="toggle">
<input className="toggle__native" type="checkbox" id="perspective"/>
<div className="toggle__control"></div>
3D lock
</label>
</div>
</div>
</div>

);
}
export default App;

App.css

* {
box-sizing: border-box;
}
html,
body {
height: 100%;
color: #333;
font-family: Arial, sans-serif;
}
a {
color: #249353;
}
:root {
--rotateX: 0;
--rotateY: 0;
--scale: 1;
--translateY: 0;
--loaderHeight: 0; /* Set by JS */
--loaderColor: #3AD57C;
}
#container {
display: flex;
flex-direction: column;
height: 100%;
}
#stage {
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
background-color: #eee;
cursor: pointer;
user-select: none;

&:hover,
&.is-isometric {
--rotateX: -30deg;
--rotateY: -45deg;
--translateY: 35%;
}
}
#loader {
height: var(--loaderHeight);
perspective: 100000px;
transform-style: preserve-3d;
transform:
scale(var(--scale))
rotateX(var(--rotateX))
rotateY(var(--rotateY))
translateY(var(--translateY));
transition: all 0.5s ease-in-out;
}
#loader__text {
position: absolute;
top: 0;
left: 0;
transform: translateY(-115%);
}
.loader__segment {
position: absolute;
top: 0;
left: 0;
border-top: 1px solid #333;
border-bottom: 1px solid #333;
height: var(--loaderHeight);
background-color: white;
overflow: hidden;

--backgroundTranslateX: -100%;
&.loader__segment--start {
border-left: 1px solid #333;
}
&.loader__segment--end {
border-right: 1px solid #333;
}

&::before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: var(--loaderColor);
content: '';
transform: translateX(var(--backgroundTranslateX));
}
}
#footer {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #fff;
font-size: 1rem;
}
#footer__controls {
display: flex;
align-items: center;
width: 100%;

& > * {
flex: 1 1 100%;
}
}
#footer__credit {
padding: 0 1em;
font-size: .75em;
}
button {
border: none;
padding: 1em;
background-color: var(--loaderColor);
color: rgba(0,0,0,0.85);
cursor: pointer;
font-size: 1rem;
width: 8em;

&[disabled] {
cursor: wait;
color: rgba(0,0,0,0.25);
background-image: linear-gradient(90deg, rgba(0,0,0,.15) 50%, rgba(0,0,0,0) 50%);
background-size: 200% 200%;
animation-name: buttonProgress;
animation-duration: 1500ms;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}
}
.toggle {
display: flex;
justify-content: center;
padding: 1em;
color: #555;
cursor: pointer;

.toggle__native {
opacity: 0;
width: 0;
height: 0;
}

.toggle__control {
width: 2em;
height: 1em;
margin-right: .5em;
position: relative;
background-color: #ddd;
border-radius: 0.5em;

&::before {
width: calc(1em - 2px);
height: calc(1em - 2px);
position: absolute;
top: 1px;
left: 1px;
border-radius: 50%;
background-color: #fff;
content: '';
transition: left .25s ease-in-out;
}
}

.toggle__native:checked + .toggle__control {
background-color: var(--loaderColor);
&::before {
left: calc(100% - 1em + 1px);
}
}
}
@keyframes buttonProgress {
0% { background-position: 100% 50%; }
55% { background-position: 0 50%; }
100% { background-position: 0 50%; }
}
@media all and (max-width: 480px) {
#footer {
flex-direction: column;
}

#footer__credit {
padding: 1em;
}
}

您只为props声明了类型,而不是本地变量。

export interface Props{
previousTimestamp: number;
}
function App(props: Props) {
props.previousTimestamp // this is typed (number)
let previousTimestamp; // not this
}

若要键入局部变量,您需要在声明期间键入。

let previousTimestamp: number;

调试

要调试这些东西,请将鼠标悬停在变量上,看看TS认为您的类型是什么。如果你把鼠标悬停在previousTimestamp的任何位置,你都会看到它是any

如果您使用的是VSCode,我想提到您可以启用的inlays hints,它将不同粒度的键入信息注入到编辑器中。这可能是压倒性的/令人讨厌的,但也有助于让你了解TS推断的类型。

最新更新