如果这是一个多余的问题,请原谅我,但我在任何地方都找不到适合我的用例的确切答案。
我正在 React 中制作一个打字机效果组件,以纯粹在 React 中进行一些文本冒险,只是为了好玩。我已经设法让它以我喜欢的方式与类组件一起工作,但我更喜欢将它调整为函数组件,以此作为更好地熟悉函数组件的机会,但它似乎以奇怪的方式运行一个区间。我以前以多种不同的方式使用过函数组件,但出于某种原因,这个方法让我很难受。
这是一个jsfiddle with the class component.
class Typewriter extends React.Component {
state = {
showCursor: false,
displayMsgs: []
}
constructor(props) {
super(props)
this.typeSpeed = this.props.typeSpeed ? this.props.typeSpeed : 250
this.curChar = 0
this.msgLength = this.props.msg.length
this.chunks = [{msg: '', flags: {color: "black"}}]
this.curChunk = 0
this.inChunk = false
for(let i = 0; i < this.msgLength; i++) {
if(this.props.msg.charAt(i) === '\\') {
this.curChunk += 1
this.inChunk = !this.inChunk
let flags = { color: "black" }
if(this.inChunk) {
switch(this.props.msg.charAt(i + 1)) {
case "R":
flags = { color: "red" }
break
default:
break
}
}
this.chunks.push({msg: '', flags: flags})
i += 2
}
this.chunks[this.curChunk].msg += this.props.msg.charAt(i)
}
this.curChunk = 0
console.log(this.chunks)
}
componentDidMount() {
const displayMsgs = (new Array(this.chunks.length)).fill("")
this.setState({ displayMsgs: displayMsgs })
const cursorInterval = setInterval(() => {
this.setState({showCursor: !this.state.showCursor})
}, 250)
const interval = setInterval(() => {
const newDisplay = this.state.displayMsgs.map((el, i) => (
i === this.curChunk ? this.state.displayMsgs[this.curChunk] + this.chunks[this.curChunk].msg.charAt(this.curChar)
: el
))
this.setState({displayMsgs: newDisplay})
this.curChar += 1
if(this.curChar >= this.chunks[this.curChunk].msg.length) {
this.curChar = 0
this.curChunk += 1
console.log(this.curChunk)
}
if(this.curChunk >= this.chunks.length)
end()
}, this.typeSpeed)
const skipTyping = () => {
clearInterval(interval)
let newDisplay = []
for(let i = 0; i < this.state.displayMsgs.length; i++){
newDisplay.push(this.chunks[i].msg)
}
this.setState({ displayMsgs: newDisplay })
end()
}
const end = () => {
clearInterval(interval)
clearInterval(cursorInterval)
document.removeEventListener("keypress", skipTyping)
document.removeEventListener("click", skipTyping)
this.setState({showCursor: false})
if(this.props.callback)
this.props.callback()
}
document.addEventListener("keypress", skipTyping)
document.addEventListener("click", skipTyping)
}
render() {
return (
<div>
{ this.state.displayMsgs.map((msg, i) =>
(
<span key={i} style={{ color: this.chunks[i].flags.color }}>{msg}</span>
)
)}
{ this.state.showCursor && <span>|</span>}
</div>
)
}
}
ReactDOM.render(<Typewriter msg="Test message. As you can see it \Rworks\R smoothly." typeSpeed={50} />, document.querySelector("#app"))
它运行顺利,正如我所期望的那样,我可以轻松地在 componentDidMount 函数。此组件也适用于传递的回调,允许我在更大的应用中链接多个 Typewriter 对象。
相反,我似乎无法让钩子在这种情况下工作。我使用了 Dan Abramov 的自定义钩子 useInterval,但它的行为不稳定,而且似乎比类组件更复杂。这里我们有一个 jsfiddle with the functional component. 我只是让它以一种丑陋的方式工作,没有按键跳过输入等。
如您所见,功能组件在更新时非常不稳定;完成输入后,光标以预期的间隔/平滑度闪烁。出于这个原因,我怀疑渲染中发生了一些有趣的事情,这导致了我无法弄清楚的重大性能损失。非常感谢任何见解。
答案 0 :(得分:1)
看起来您的问题是间隔回调中的状态突变。您保存对 displayMsgs
状态的引用,对其进行变异,然后将其保存回状态。 React 没有看到新的引用,因此会在中间重新渲染上放弃,结果是“断断续续的动画”。
useInterval(() => {
if (displayMsgs && curChunk.current < chunks.current.length) {
let newDisplay = displayMsgs; // <-- reference to current state
newDisplay[curChunk.current] += chunks.current[
curChunk.current
].msg.charAt(curChar.current); // <-- Mutation!!!
console.log('newDisplay', newDisplay);
setDisplayMsgs(newDisplay); // <-- save reference back into state
curChar.current += 1;
if (curChar.current >= chunks.current[curChunk.current].msg.length) {
curChar.current = 0;
curChunk.current += 1;
console.log(curChunk.current);
}
}
}, typeSpeed);
先对状态进行浅拷贝,然后再更新。
useInterval(() => {
if (displayMsgs && curChunk.current < chunks.current.length) {
let newDisplay = displayMsgs.slice(); // <-- shallow copy array
newDisplay[curChunk.current] += chunks.current[
curChunk.current
].msg.charAt(curChar.current);
console.log('newDisplay', newDisplay);
setDisplayMsgs(newDisplay);
curChar.current += 1;
if (curChar.current >= chunks.current[curChunk.current].msg.length) {
curChar.current = 0;
curChunk.current += 1;
console.log(curChunk.current);
}
}
}, typeSpeed);