six*_*ude 8 javascript typescript reactjs
我有一个非常简单的组件,带有一个文本字段和一个按钮:
它接受一个列表作为输入,并允许用户在列表中循环。
该组件具有以下代码:
import * as React from "react";
import {Button} from "@material-ui/core";
interface Props {
names: string[]
}
interface State {
currentNameIndex: number
}
export class NameCarousel extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { currentNameIndex: 0}
}
render() {
const name = this.props.names[this.state.currentNameIndex].toUpperCase()
return (
<div>
{name}
<Button onClick={this.nextName.bind(this)}>Next</Button>
</div>
)
}
private nextName(): void {
this.setState( (state, props) => {
return {
currentNameIndex: (state.currentNameIndex + 1) % props.names.length
}
})
}
}
Run Code Online (Sandbox Code Playgroud)
这个组件很好用,除了我没有处理状态改变时的情况。 当状态改变时,我想将 重置currentNameIndex为零。
做这个的最好方式是什么?
我已经考虑的选项:
componentDidUpdate这个解决方案是ackward,因为componentDidUpdate在render之后运行,所以我需要在render方法中添加一个子句,当组件处于无效状态时“什么都不做”,如果我不小心,我可能会导致空指针异常.
我在下面包含了一个实现。
getDerivedStateFromProps该getDerivedStateFromProps方法是static和签名只给你访问到当前的状态和未来的道具。这是一个问题,因为您无法判断道具是否已更改。结果,这迫使您将道具复制到状态中,以便您可以检查它们是否相同。
我不想这样做。这个组件应该私有拥有当前选择的索引是什么。
我正在考虑这种方法,但不喜欢它如何导致父级需要了解子级的实现细节。
我花了很多时间阅读你可能不需要派生状态, 但对那里提出的解决方案很不满意。
我知道这个问题的变体已经被问过多次,但我觉得任何答案都没有权衡可能的解决方案。一些重复的例子:
解决方案使用componetDidUpdate(见上面的描述)
import * as React from "react";
import {Button} from "@material-ui/core";
interface Props {
names: string[]
}
interface State {
currentNameIndex: number
}
export class NameCarousel extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { currentNameIndex: 0}
}
render() {
if(this.state.currentNameIndex >= this.props.names.length){
return "Cannot render the component - after compoonentDidUpdate runs, everything will be fixed"
}
const name = this.props.names[this.state.currentNameIndex].toUpperCase()
return (
<div>
{name}
<Button onClick={this.nextName.bind(this)}>Next</Button>
</div>
)
}
private nextName(): void {
this.setState( (state, props) => {
return {
currentNameIndex: (state.currentNameIndex + 1) % props.names.length
}
})
}
componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>): void {
if(prevProps.names !== this.props.names){
this.setState({
currentNameIndex: 0
})
}
}
}
Run Code Online (Sandbox Code Playgroud)
解决方案使用getDerivedStateFromProps:
import * as React from "react";
import {Button} from "@material-ui/core";
interface Props {
names: string[]
}
interface State {
currentNameIndex: number
copyOfProps?: Props
}
export class NameCarousel extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { currentNameIndex: 0}
}
render() {
const name = this.props.names[this.state.currentNameIndex].toUpperCase()
return (
<div>
{name}
<Button onClick={this.nextName.bind(this)}>Next</Button>
</div>
)
}
static getDerivedStateFromProps(props: Props, state: State): Partial<State> {
if( state.copyOfProps && props.names !== state.copyOfProps.names){
return {
currentNameIndex: 0,
copyOfProps: props
}
}
return {
copyOfProps: props
}
}
private nextName(): void {
this.setState( (state, props) => {
return {
currentNameIndex: (state.currentNameIndex + 1) % props.names.length
}
})
}
}
Run Code Online (Sandbox Code Playgroud)
可以使用功能组件吗?可能会简化一些事情。
import React, { useState, useEffect } from "react";
import { Button } from "@material-ui/core";
interface Props {
names: string[];
}
export const NameCarousel: React.FC<Props> = ({ names }) => {
const [currentNameIndex, setCurrentNameIndex] = useState(0);
const name = names[currentNameIndex].toUpperCase();
useEffect(() => {
setCurrentNameIndex(0);
}, names);
const handleButtonClick = () => {
setCurrentIndex((currentNameIndex + 1) % names.length);
}
return (
<div>
{name}
<Button onClick={handleButtonClick}>Next</Button>
</div>
)
};
Run Code Online (Sandbox Code Playgroud)
useEffect类似于componentDidUpdate它将依赖项数组(状态和属性变量)作为第二个参数。当这些变量发生变化时,第一个参数中的函数就会被执行。就那么简单。您可以在函数体内进行额外的逻辑检查来设置变量(例如,setCurrentNameIndex)。
请小心,如果您对第二个参数的依赖项在函数内部发生更改,那么您将有无限的重新渲染。
查看useEffect 文档,但是在习惯了钩子之后,您可能再也不想使用类组件了。
| 归档时间: |
|
| 查看次数: |
514 次 |
| 最近记录: |