redux接続コンポーネントはどのようにして再レンダリングするかを知るのですか?


86

私はおそらく非常に明白な何かを見逃しているので、自分自身をクリアしたいと思います。

これが私の理解です。
ナイーブなreactコンポーネントには、states&がありpropsます。で更新すると、コンポーネント全体statesetState再レンダリングされます。propsほとんどが読み取り専用であり、更新しても意味がありません。

のようなものを介してreduxストアにサブスクライブするreactコンポーネントでstore.subscribe(render)は、ストアが更新されるたびに明らかに再レンダリングされます。

react-reduxにconnect()、状態ツリーの一部(コンポーネントに関係する)とactionCreatorsをコンポーネントに関して挿入するヘルパーがpropsあります。

const TodoListComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

しかし、ことを理解した上でsetState必要不可欠であるTodoListComponentReduxの状態ツリーの変更(再描画)に反応するように、私はすべてを見つけることができないstatesetStateで関連するコードTodoListコンポーネントファイルを。これは次のようになります。

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => onTodoClick(todo.id)}
      />
    )}
  </ul>
)

誰かが私が欠けているものに関して正しい方向に私を向けることができますか?

PS私はreduxパッケージにバンドルされているtodoリストの例に従っています

回答:


109

このconnect関数は、ストアにサブスクライブするラッパーコンポーネントを生成します。アクションがディスパッチされると、ラッパーコンポーネントのコールバックが通知されます。次に、mapState関数を実行し、今回の結果オブジェクトと前回の結果オブジェクトを浅く比較します(したがって、同じ値でreduxストアフィールドを書き換えた場合、再レンダリングはトリガーされません)。結果が異なる場合は、結果を小道具として「実際の」コンポーネントに渡します。

Dan Abramovは、connectat(connect.js)の非常に簡略化されたバージョンを作成しました。これは、最適化作業を示していませんが、基本的な考え方を示しています。また、関連するアイデアについて説明しているReduxのパフォーマンスに関する多数の記事へのリンクもあります。

更新

React-Redux v6.0.0は、接続されたコンポーネントがストアからデータを受信する方法にいくつかの主要な内部変更を加えました。

その一環として、connectAPIとその内部がどのように機能し、時間の経過とともにどのように変化したかを説明する投稿を書きました。

慣用的なRedux:React-Reduxの歴史と実装


ありがとう!あなたは先日@HNで私を助けてくれたのと同じ人ですか?news.ycombinator.com/itemid = 12307621便利なリンクがありますか?はじめまして:)
ジョールイス

4
うん、それは私だ:)私はReduxオンラインでの議論を探すのにあまりにも多くの時間を費やしている-Reddit、HN、SO、Medium、そして他の様々な場所。喜んでお手伝いします!(また、参考までに、DiscordのReactifluxチャットチャネルを強くお勧めします。たくさんの人がそこにぶらぶらして質問に答えています。私は通常、米国東部標準時の夜にオンラインです。招待リンクはreactiflux.comにあります。)
markerikson 2016年

これが質問に答えたと仮定して、答えを受け入れてもいいですか?:)
markerikson

オブジェクト自体を比較しているのですか、それともオブジェクトの前後のプロパティを比較しているのですか?後者の場合、最初のレベルのみをチェックしますか?それは「浅い」という意味ですか?
アンディ

はい、「浅い同等性チェック」とは、各オブジェクトの第1レベルのフィールドを比較することを意味しますprev.a === current.a && prev.b === current.b && .....。これは、データが変更されると新しい参照が発生することを前提としています。つまり、直接の変更ではなく、不変のデータ更新が必要です。
markerikson

13

私の答えは少し左翼から外れています。それは私をこの投稿に導いた問題に光を当てます。私の場合、新しい小道具を受け取ったにもかかわらず、アプリは再レンダリングされていないようでした。
Reactの開発者は、(ストア)が変更された場合、99%の確率で反応が再レンダリングされないという、よくある質問に対する回答を持っていました。しかし、他の1%については何もありません。ここでは突然変異は当てはまりませんでした。


TLDR;

componentWillReceivePropsstate新しいと同期させ続ける方法propsです。

エッジケース:一度stateアップデート、そしてアプリがない再レンダリング!


アプリがstateその要素を表示するためだけに使用している場合、props更新することはできますが、更新しstateないため、再レンダリングは行われません。

私はstateそれがpropsreduxから受け取ったことに依存していたstore。必要なデータはまだストアにないので、必要に応じてからフェッチしましたcomponentDidMount。コンポーネントがmapStateToPropsを介して接続されているため、レデューサーがストアを更新したときに小道具を取り戻しました。しかし、ページはレンダリングされず、状態はまだ空の文字列でいっぱいでした。

この例は、ユーザーが保存されたURLから「投稿の編集」ページをロードした場合です。postIdURLからにアクセスできますが、情報がまだ入力されてstoreいないため、取得します。ページ上のアイテムは制御されたコンポーネントであるため、表示しているすべてのデータはにありstateます。

reduxを使用して、データがフェッチされ、ストアが更新され、コンポーネントがconnect編集されましたが、アプリは変更を反映していませんでした。よく見ると、props受信されましたが、アプリは更新されませんでした。state更新されませんでした。

まあ、props更新して伝播しますが、しstateません。state更新するように具体的に指示する必要があります。

でこれを行うことはできずrender()componentDidMountすでにサイクルを終了しています。

componentWillReceivePropsstate変更されたprop値に依存するプロパティを更新する場所です。

使用例:

componentWillReceiveProps(nextProps){
  if (this.props.post.category !== nextProps.post.category){
    this.setState({
      title: nextProps.post.title,
      body: nextProps.post.body,
      category: nextProps.post.category,
    })
  }      
}

他の何十もの投稿、ブログ、リポジトリが言及しなかった解決策について私を啓発したこの記事に一言コメントしなければなりません。この明らかにあいまいな問題に対する答えを見つけるのに苦労した他の人は、ここにあります:

ReactJsコンポーネントのライフサイクルメソッド—詳細

componentWillReceiveProps更新stateと同期を保つために更新する場所ですprops

一度stateの更新、その後に応じて、フィールドstate DO再レンダリング!


3
よりReact 16.3以降、あなたがすべき使用getDerivedStateFromPropsの代わりにcomponentWillReceiveProps。しかし実際には、ここで
kyw 2018

それは機能しません。状態が変更されるたびに、componentWillReceivePropsが機能するため、これを試してみても機能しません。特に、マルチレベルのネストされた小道具や複雑な小道具なので、コンポーネントにIDを割り当てて変更をトリガーし、状態を更新して新しいデータを再レンダリングする必要があります
May Weather VN

0

この回答は、Brian Vaughnの記事「YouProbably Do n't Need Derived State(2018年6月7日)」の要約です。

小道具から状態を導き出すことは、あらゆる形態のアンチパターンです。古いものcomponentWillReceivePropsと新しいものの使用を含みgetDerivedStateFromPropsます。

小道具から状態を導出する代わりに、次の解決策を検討してください。

2つのベストプラクティスの推奨事項

推奨事項1.完全に制御されたコンポーネント
function EmailInput(props) {
  return <input onChange={props.onChange} value={props.email} />;
}
推奨事項2.キーを備えた完全に制御されていないコンポーネント
// parent class
class EmailInput extends Component {
  state = { email: this.props.defaultEmail };

  handleChange = event => {
    this.setState({ email: event.target.value });
  };

  render() {
    return <input onChange={this.handleChange} value={this.state.email} />;
  }
}

// child instance
<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>

何らかの理由で推奨事項が状況に合わない場合の2つの選択肢。

代替案1:IDプロップで制御されていないコンポーネントをリセットする
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail,
    prevPropsUserID: this.props.userID
  };

  static getDerivedStateFromProps(props, state) {
    // Any time the current user changes,
    // Reset any parts of state that are tied to that user.
    // In this simple example, that's just the email.
    if (props.userID !== state.prevPropsUserID) {
      return {
        prevPropsUserID: props.userID,
        email: props.defaultEmail
      };
    }
    return null;
  }

  // ...
}
代替案2:インスタンスメソッドを使用して制御されていないコンポーネントをリセットする
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail
  };

  resetEmailForNewUser(newEmail) {
    this.setState({ email: newEmail });
  }

  // ...
}

-2

reduxが行うことだけを知っているので、ストアの状態の変更時に、コンポーネントが変更された状態に依存している場合はcomponentWillRecievePropsを呼び出し、コンポーネントを強制的に更新する必要があります。

1ストアの状態変更-2-call(componentWillRecieveProps(()=> {3-コンポーネントの状態変更}))

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.