以下哪种策略是在 props 更改时重置组件状态的最佳方法

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)

Tun*_*unn 2

可以使用功能组件吗?可能会简化一些事情。

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 文档,但是在习惯了钩子之后,您可能再也不想使用类组件了。