2016-11-22 7 views
8

編集:私はこのことを明確にするためにこの質問を書き直しました。これまでの回答に感謝してくれた人々に感謝します。Reactの入れ子状態を管理するための良いアプローチ

複雑なネストされた状態をReactで管理する方法を理解し、コンテンツが変更されていないコンポーネントに対してrender()が呼び出される回数を制限しようとしています。背景として

私はこのようなオブジェクトの「著者」及び「資料」の両方で状態を有すると仮定:

{ 
    'authors' : { 
    234 : { 
     'name' : 'Alice Ames', 
     'bio' : 'Alice is the author of over ...', 
     'profile_pic' : 'http://....' 
    }, 
    794 : { 
     'name' : 'Bob Blake', 
     'bio' : 'Hailing from parts unknown, Bob...', 
     'profile_pic' : 'http://....' 
    }, 
    ...more authors... 
}, 
'publications' : { 
    539 : { 
     'title' : 'Short Story Vol. 2', 
     'author_ids' : [ 234, 999, 220 ] 
    }, 
    93 : { 
     'title' : 'Mastering Fly Fishing', 
     'author_ids' : [ 234 ] 
    }, 
    ...more publications... 
    } 
} 

この不自然な例では状態によってアクセスされる、2つの領域を有していますauthorspublicationsのキー。

authorsキーは、作成者のIDでキー入力されたオブジェクトにつながり、一部の作成者データを含むオブジェクトにつながります。

publicationsキーは、出版データのある出版物のIDと著者の配列をキーにしたオブジェクトにつながります。

私の状態は以下の擬似JSXのような子コンポーネントとAppコンポーネントであると仮定します

... 
<App> 
    <AuthorList authors={this.state.authors} /> 
    <PublicationList authors={this.state.authors} publications={this.state.publications} /> 
</App> 
... 

... 
class AuthorList extends React.Component { 
    render() { 
    let authors = this.props.authors; 
    return (
     <div> 
     { Object.keys(authors).map((author_id) => { 
      return <Author author={authors[author_id]} />; 
     } 
     </div> 
    ); 
    } 
} 
... 

... 
class PublicationList extends React.Component { 
    render() { 
    let publications = this.props.publications; 
    let authors = this.props.authors; 
    return (
     <div> 
     { Object.keys(publications).map((publication_id) => { 
      return <Publication publication={publications[publication_id]} authors=authors />; 
     } 
     </div> 
    ); 
    } 
} 
... 

はレンダリングPublicationコンポーネントをAuthorListが子供の束Author要素を持っている、とPublicationListは子供の束を持っていると仮定それらの物の実際の内容。

は、ここに私の質問です:私は与えられた作者のためにbioを更新するとしますが、私はrender()が、内容に変更されていないすべてAuthorPublicationオブジェクトに対して呼び出される必要はありません。この回答から

ReactJS - Does render get called any time "setState" is called?

Aは、コンポーネントのrender()機能は、任意の時点の状態と呼ばれます反応して、またはその両親のいずれかの状態変化 - に関係なく、その状態変化がに何を持っているかどうかにそのコンポーネントの小道具で行う。この動作はshouldComponentUpdateで変更できます。

人々は上記のように複雑な状態をどのように処理するのですか?すべての状態変更で多数のコンポーネントに対してrender()を呼び出すのは良い解決策です(たとえ結果のレンダリングされたオブジェクトが同じで、実際のDOMに発生します)。

答えて

-2

Reduxで私を指摘してくれたjpdeatorreとdaveolsに感謝します。

ここでは、Reduxを使用してコンポーネントとは関係のない状態変更からコンポーネントを分離するアプリケーション例(コーナーカットが多数ありますが、その手法を示しています)です。

この例では、id1の作成者Aliceに変更を加えても、Authorは、Aliceに依存せずにrender()を呼び出すコンポーネントになります。

これは、Reduxが提供するshouldComponentUpdateが接続された反応コンポーネント用に提供されているため、小道具と関連する状態が変更されているかどうかが評価されます。

ここでのReduxの最適化は浅いことに注意してください。古いものと新しい小道具は、彼らが同じキーを持っており、それらのキーの値が===であることを=== 1に別の

  • か、そうでない場合はある

    • :かどうかを確認するにはrender()再来のshouldComponentUpdateチェックをスキップするために枯れたりありません互いに。

    だから、その値がまだ論理的に等価であるが、これらのコンポーネントとその最初のレベルのキーに小道具が===と等しいとしていないコンポーネントのために呼び出されrender()につながる可能性があります。参照:https://github.com/reactjs/react-redux/blob/master/src/utils/shallowEqual.js

    注意もそれがために、私はReduxののshouldComponentUpdateロジックを有効にするために再来へconnect()に持っていたAuthorの「ダム」コンポーネントにrender()を呼び出す防ぐために - そのコンポーネントが状態と全く何もしていないにもかかわらず、およびちょうどその小道具を読む。

    import ReactDOM from 'react-dom'; 
    import React from 'react'; 
    
    import { Provider, connect } from 'react-redux'; 
    import { createStore, combineReducers } from 'redux'; 
    
    import update from 'immutability-helper'; 
    
    
    const updateAuthor = (author) => { 
        return ({ 
        type : 'UPDATE_AUTHOR', 
        // Presently we always update alice and not a particular author, so this is ignored. 
        author 
        }); 
    }; 
    
    const updateUnused =() => { 
        return ({ 
        type : 'UPDATE_UNUSUED', 
        date : Date() 
        }); 
    }; 
    
    const initialState = { 
        'authors': { 
        1: { 
         'name': 'Alice Author', 
         'bio': 'Alice initial bio.' 
        }, 
        2: { 
         'name': 'Bob Baker', 
         'bio': 'Bob initial bio.' 
        } 
        }, 
        'publications': { 
        1 : { 
         'title' : 'Two Authors', 
         'authors' : [ 1, 2 ] 
        }, 
        2 : { 
         'title' : 'One Author', 
         'authors' : [ 1 ] 
        } 
        } 
    }; 
    
    const initialDate = Date(); 
    
    const reduceUnused = (state=initialDate, action) => { 
        switch (action.type) { 
        case 'UPDATE_UNUSED': 
         return action.date; 
    
        default: 
         return state; 
        } 
    }; 
    
    const reduceAuthors = (state=initialState, action) => { 
        switch (action.type) { 
        case 'UPDATE_AUTHOR': 
         let new_bio = state.authors['1'].bio + ' updated '; 
         let new_state = update(state, { 'authors' : { '1' : { 'bio' : {$set : new_bio } } } }); 
         /* 
         let new_state = { 
         ...state, 
         authors : { 
          ...state.authors, 
          [ 1 ] : { 
          ...state.authors[1], 
          bio : new_bio 
          } 
         } 
         }; 
         */ 
         return new_state; 
    
        default: 
         return state; 
        } 
    }; 
    
    const testReducers = combineReducers({ 
        reduceAuthors, 
        reduceUnused 
    }); 
    
    const mapStateToPropsAL = (state) => { 
        return ({ 
        authors : state.reduceAuthors.authors 
        }); 
    }; 
    
    class AuthorList extends React.Component { 
    
        render() { 
        return (
         <div> 
         { Object.keys(this.props.authors).map((author_id) => { 
          return <Author key={author_id} author_id={author_id} />; 
         }) } 
         </div> 
        ); 
        } 
    } 
    AuthorList = connect(mapStateToPropsAL)(AuthorList); 
    
    const mapStateToPropsA = (state, ownProps) => { 
        return ({ 
        author : state.reduceAuthors.authors[ownProps.author_id] 
        }); 
    }; 
    
    class Author extends React.Component { 
    
        render() { 
        if (this.props.author.name === 'Bob Baker') { 
         alert("Rendering Bob!"); 
        } 
    
        return (
         <div> 
         <p>Name: {this.props.author.name}</p> 
         <p>Bio: {this.props.author.bio}</p> 
         </div> 
        ); 
        } 
    } 
    Author = connect(mapStateToPropsA)(Author); 
    
    
    const mapStateToPropsPL = (state) => { 
        return ({ 
        authors : state.reduceAuthors.authors, 
        publications : state.reduceAuthors.publications 
        }); 
    }; 
    
    
    class PublicationList extends React.Component { 
    
        render() { 
        console.log('Rendering PublicationList'); 
        let authors = this.props.authors; 
        let publications = this.props.publications; 
        return (
         <div> 
         { Object.keys(publications).map((publication_id) => { 
          return <Publication key={publication_id} publication={publications[publication_id]} authors={authors} />; 
         }) } 
         </div> 
        ); 
        } 
    } 
    PublicationList = connect(mapStateToPropsPL)(PublicationList); 
    
    
    class Publication extends React.Component { 
    
        render() { 
        console.log('Rendering Publication'); 
        let authors = this.props.authors; 
        let publication_authors = this.props.publication.authors.reduce(function(obj, x) { 
         obj[x] = authors[x]; 
         return obj; 
        }, {}); 
    
        return (
         <div> 
         <p>Title: {this.props.publication.title}</p> 
         <div>Authors: 
          <AuthorList authors={publication_authors} /> 
         </div> 
         </div> 
        ); 
        } 
    } 
    
    const mapDispatchToProps = (dispatch) => { 
        return ({ 
        changeAlice : (author) => { 
         dispatch(updateAuthor(author)); 
        }, 
        changeUnused :() => { 
         dispatch(updateUnused()); 
        } 
        }); 
    }; 
    
    class TestApp extends React.Component { 
        constructor(props) { 
        super(props); 
        } 
    
        render() { 
        return (
         <div> 
         <p> 
          <span onClick={() => { this.props.changeAlice(this.props.authors['1']); } }><b>Click to Change Alice!</b></span> 
         </p> 
         <p> 
          <span onClick={() => { this.props.changeUnused(); } }><b>Click to Irrelevant State!</b></span> 
         </p> 
    
         <div>Authors: 
          <AuthorList authors={this.props.authors} /> 
         </div> 
         <div>Publications: 
          <PublicationList authors={this.props.authors} publications={this.props.publications} /> 
         </div> 
         </div> 
        ); 
        } 
    } 
    TestApp = connect(mapStateToPropsAL, mapDispatchToProps)(TestApp); 
    
    let store = createStore(testReducers); 
    
    ReactDOM.render(
        <Provider store={store}> 
        <TestApp /> 
        </Provider>, 
        document.getElementById('test') 
    ); 
    
  • +1

    うーん...私が言ったことと実質的に同じであるあなたの質問に答えを出す理由は分かりません。 – jpdelatorre

    +0

    これら2つの答えは事実上同じではないためです。 Author.List内の何かの状態が変わるたびにAuthorListのすべてのAuthorの子が再レンダリングされます。 Mineは、私がやろうとしていたコンテンツを変更したAuthorListの子のみを選択的に再レン​​ダリングします。 – deadcode

    0

    コンポーネントの状態には、実際には内部状態値のみが含まれている必要があります。

    Reduxで複数のコンポーネントに必要なより複雑な状態を格納する方法を検討する必要があります。

    2

    immutability helperReact's documentation)を使用してください。これにより、状態の一部を更新するためのメカニズムが提供され、可能な限り重複を排除して、必要な複製を処理します。それが唯一の変更によって影響を受けるコンポーネントを再レンダリングします

    this.setState(update(this.state, { authors: { $set: { ... } } })); 
    

    これはあなたのような何かを行うことができます。

    +0

    この例では、新しいオブジェクトを作成して状態全体を上書きしていませんか?実際の変更がthis.store.authors ['342']の中で切り離されていたとしても、これは状態の何かに依存するすべてのオブジェクトを再レンダリングしません。 – deadcode

    +0

    私は内部的にどのように動作するのか分かりませんが、Reactのドキュメントを読んでいるので、可能な限り浅いコピーを使用しています(変更されていない参照を再利用すると仮定しています)。 –

    +0

    何が起こっているのかを理解するのに役立つこの答えを見つけました:http://stackoverflow.com/questions/24718709/reactjs-does-render-get-called-any-time-setstate-is-called-デフォルトではコンポーネントのレンダリングその状態の変更がそのコンポーネントの小道具と何らかの関係があるかどうかにかかわらず、いつでもその状態またはその親の状態が変わるときに呼び出されます。この動作はshouldComponentUpdateで変更できます。 – deadcode

    4

    オブジェクトスプレッドシンタックスを使用して、これを効率的かつ可読な方法で実行する方法があります。

    let state = { 
        authors : { 
         ...this.state.authors, 
         [ givenId ] : { 
          ...this.state.authors[ givenID ], 
          bio : newValue 
         } 
        } 
    } 
    this.setState(state) 
    

    jsxでアイテムをマップするときは、「キー」を小道具として渡す必要があります。

    これは主に、リコンシリエーション(変更されたものを確認するためのReactの「diffing」アルゴリズム)が、マップされたjsxのキーをチェックするためです(概してjsxという名前)。

    とにかく、reacts state/setStateまたはreduxの状態を管理することは、 'reconciliation'とは関係ありません。

    どちらの場合でも、「オブジェクトスプレッド構文」構文を使用してネストされたデータの部分を変更できます。

    残念なことに、マップされたjsxに「同一の」キーを渡すだけです。そのため、rerendersに反応しますが、不要な部分をDOMで更新しようとせず、高価です。

    +0

    これは、状態のどこにでも依存するすべてを再レンダリングしませんか?私は更新を行うためのより良い構文を理解しています - しかし、私は1つの著者のバイオを変更するたびにすべてのコンポーネントを再レンダリングする必要がある場合、私は何か間違っているようです。 – deadcode

    +0

    あなたは、反応がnewStateとoldStateを参照によって比較すると仮定しますか?これは正しくない、反応はnewStateとoldStateのキーを深く比較する。特定のキー値だけが変更されたため、コンポーネント内の対応する部分が再レンダリングされます。 – FurkanO

    +0

    何が起こっているのかを理解するのに役立つこの答えを見つけました:http://stackoverflow.com/questions/24718709/reactjs-does-render-get-called-any-time-setstate-is-called-デフォルトではコンポーネントのレンダリングその状態の変更がそのコンポーネントの小道具と何らかの関係があるかどうかにかかわらず、いつでもその状態またはその親の状態が変わるときに呼び出されます。この動作はshouldComponentUpdateで変更できます。 – deadcode

    1

    Reduxを使用すると、アプリを効率的に管理しやすくなると思います。

    グローバル状態がのReduxストアとなっているため、いずれのコンポーネントでもストアのスライスをサブスクライブし、それらのデータが変更されたときに再レンダリングすることができます。

    あなたの例では、それはあなたのAuthorListコンポーネントだろう実装のReduxの方法はstate.authorsオブジェクトをサブスクライブし、AuthorListコンポーネント内または外部のいずれかのコンポーネントがstate.authorsを更新した場合、唯一のAuthorListコンポーネントが再レンダリング(およびそれらうそれはそれに加入している)。

    関連する問題