我有一个非常简单的组件,带有一个文本字段和一个按钮:
它接受一个列表作为输入并允许用户在列表中循环。
该组件具有以下代码:
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
}
})
}
}
这个组件很好用,除了我没有处理状态改变时的情况。 当状态改变时,我想将 重置currentNameIndex
为零。
做这个的最好方式是什么?
我已经考虑的选项:
使用 componentDidUpdate
这个解决方案是ackward,因为componentDidUpdate
在render之后运行,所以我需要在render方法中添加一个子句,当组件处于无效状态时“什么都不做”,如果我不小心,我可能会导致空指针异常.
我在下面包含了一个实现。
使用 getDerivedStateFromProps
该getDerivedStateFromProps
方法是static
和签名只给你访问到当前的状态和未来的props。这是一个问题,因为您无法判断props是否已更改。结果,这迫使您将props复制到状态中,以便您可以检查它们是否相同。
使组件“完全受控”
我不想这样做。这个组件应该私有拥有当前选择的索引是什么。
使组件“完全不受钥匙控制”
我正在考虑这种方法,但不喜欢它如何导致父级需要了解子级的实现细节。
杂项
我花了很多时间阅读你可能不需要派生状态, 但对那里提出的解决方案很不满意。
我知道这个问题的变体已经被问过多次,但我觉得任何答案都没有权衡可能的解决方案。一些重复的例子:
附录
解决方案使用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
})
}
}
}
解决方案使用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
}
})
}
}