ウェブサイト検索

javascript - Reactコンポーネントで再マウントを強制する方法は?


例えば、条件付きでレンダーされるビューコンポーネントがあるとしましょう:

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

私の入力はこのような感じです:

class MyInput extends React.Component {

    ...

    render(){
        return (
            <div>
                <input name={this.props.name} 
                    ref="input" 
                    type="text" 
                    value={this.props.value || null}
                    onBlur={this.handleBlur.bind(this)}
                    onChange={this.handleTyping.bind(this)} />
            </div>
        );
    }
}

例えば、employed が true だとします。それを false に切り替えて他のビューがレンダリングされると、unemployment-duration だけが再初期化されます。また、unemployment-reason は、条件が変わる前に値が設定されていた場合、job-title の値で事前に入力されます。

もし私が2番目のレンダリングルーチンでマークアップを次のようなものに変更したら:

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <span>Diff me!</span>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

すべてが正常に動作しているようです。どうやらReactは 'job-title' と 'unemployment-reason' の差分をうまく処理できていないようです。

私が何を間違っているのか教えてください…

回答 #1

コンポーネントのキーを変更してください。

<Component key="1" />
<Component key="2" />

コンポーネントはアンマウントされ、キーが変更されたため、コンポーネントの新しいインスタンスがマウントされます。

おそらく派生状態は必要ないと文書化されています:

キーが変わると、Reactは現在のコンポーネントを更新するのではなく、新しいコンポーネントインスタンスを作成します。キーは通常、動的リストに使用されますが、ここでも役立ちます。

回答 #2

おそらく起きていることは、React がレンダーの間に追加されるのは MyInput(unemployment-duration) だけだと考えているということです。そのため、job-title は unemployment-reason に置き換えられることがなく、これが事前定義された値が入れ替わる理由でもあります。

React が差分を行うとき、どのコンポーネントが新しいものでどのコンポーネントが古いものかを、key プロパティに基づいて判断します。コード内でそのようなキーが提供されていない場合、React は独自にキーを生成します。

あなたが提供した最後のコードスニペットが動作する理由は、React が本質的に親の div の下にあるすべての要素の階層を変更する必要があるためであり、それによりすべての子要素が再レンダリングされると考えられるからです(だから動作するのです)。もし span を上ではなく下に追加した場合、先行する要素の階層は変わらず、それらの要素は再レンダリングされず(問題はそのまま残ります)。

公式のReactドキュメントには次のように書かれています:

子要素が入れ替えられたり(検索結果のように)、リストの先頭に新しいコンポーネントが追加されたりする(ストリームのように)場合、状況はより複雑になります。このように、各子要素の識別と状態をレンダリングを跨いで維持する必要がある場合は、キーを割り当てることで各子要素を一意に識別できます。

React がキー付きの子要素を調整するとき、キーを持つ子要素が上書きされるのではなく再配置されること、または再利用されるのではなく破棄されることを保証します。

親のdivまたはすべてのMyInput要素に対して、独自のkey要素を自分で提供することで、これを修正できるはずです。

例えば:

render(){
    if (this.state.employed) {
        return (
            <div key="employed">
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div key="notEmployed">
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

または

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput key="title" ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <MyInput key="reason" ref="unemployment-reason" name="unemployment-reason" />
                <MyInput key="duration" ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

さて、React が差分を行うと、div が異なることを確認し、それに含まれるすべての子要素を再レンダリングします(最初の例)。2つ目の例では、差分は job-title と unemployment-reason で成功します。なぜなら、それらは現在異なるキーを持っているからです。

もちろん、キーがユニークであれば、どんなキーでも使うことができます。


2017年8月の更新

Reactでキーがどのように機能するかをより深く理解するために、私はReact.jsでの一意のキーの理解に関する私の回答を読むことを強くお勧めします。


2017年11月の更新

このアップデートはしばらく前に投稿されるべきでしたが、現在 ref で文字列リテラルを使用することは非推奨となっています。例えば ref="job-title" は今では ref={(el) => this.jobTitleRef=el} のようにするべきです(例として)。詳細は「this.refs を使用した際の非推奨警告」に対する私の回答をご覧ください。

関連記事