テキストフィールドとボタンを備えた非常にシンプルなコンポーネントがあります。
入力としてリストを受け取り、ユーザーがリストを循環できるようにします。
コンポーネントには次のコードがあります。
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
このソリューションは、componentDidUpdate
レンダリング後に実行されるため、不適切です。コンポーネントが無効な状態のときにrenderメソッドに句「何もしない」を追加する必要があります。注意しないと、null-pointer-exceptionが発生する可能性があります。 。
これの実装を以下に含めました。
使用する 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
})
}
}
}
使用するソリューション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
}
})
}
}
next
をコンポーネントに渡します。ボタンが表示名、およびそのonClickのは、ちょうどサイクリングを扱う親に位置し、次の関数呼び出し
currentIndex
ます。names
親で更新されたときはリセットされるcurrentIndex