在React

时间:2016-11-22 01:53:56

标签: reactjs

编辑:我重写了这个问题以澄清我之后的事情 - 感谢那些迄今为止帮助我磨练它的人。

我试图了解如何最好地在React中管理复杂的嵌套状态,同时还限制为内容未发生变化的组件调用render()的次数。

作为背景:

假设我和'#34;作者"和"出版物"在这样的对象中:

{
  '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...
  }
}

在这个人为的例子中,州有两个主要区域,可以通过authorspublications键访问。

authors键导致一个对象键入作者的ID,这将导致一个具有一些作者数据的对象。

publications键导致一个对象键入具有一些发布数据的发布的ID和一组作者。

假设我的状态位于App组件中,子组件类似于以下伪JSX:

...
<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>
    );
  }
}
...

假设AuthorList有一堆子Author个组件,PublicationList有一堆子Publication组件,可以呈现这些内容的实际内容。

以下是我的问题:假设我想更新给定作者的bio,但我不希望为render()和{Author调用Publication {1}}内容未更改的对象。

从这个回答:

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

React组件的render()函数将在其状态或其任何父项的状态发生变化时被调用 - 无论该状态更改是否与其中的道具有关。零件。可以使用shouldComponentUpdate更改此行为。

人们如何处理如上所述的复杂状态 - 在每次状态变化时对大量组件调用render()似乎都是一个很好的解决方案(即使生成的渲染对象是相同的,所以没有改变发生在实际的DOM)。

5 个答案:

答案 0 :(得分:5)

这是一种使用对象传播语法以高效可读的方式实现此目的的方法。

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

请记住,你必须传递一个键。在jsx中映射项目时作为道具。

这主要是因为,协调(React&#34;差异&#34;算法来检查已发生的变化)反应的事情会检查映射jsx的密钥(大致命名为jsx)。

无论如何,在状态/ setState或者redux中管理状态与&#39;对帐&#39;无关。

在这两种情况下,您都可以使用&#39;对象传播语法&#39;来更改嵌套数据的一部分。语法。

你要关心的其余部分是通过相同的&#39;映射的jsx的键。因此,尽管反应重新出现,但它并没有尝试对不必要的部分进行dom更新,这是昂贵的。

答案 1 :(得分:3)

您应该使用immutability helper,每React's documentation。这提供了一种更新部分状态的机制,并为您处理任何所需的克隆,并尽可能少地复制。

这允许您执行以下操作:

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

它只会重新渲染受更改影响的组件。

答案 2 :(得分:2)

我认为使用Redux可以让您的应用更高效,更易于管理。

拥有一个称为 Redux store 的全局状态,它允许您的任何组件订阅商店的一部分,并在这些数据发生更改时重新呈现。

在您的示例中,实现它的redux方式是您的AuthorList组件将订阅state.authors对象,如果AuthorList组件内部或外部的任何组件更新了{{ 1}},只有state.authors组件会重新渲染(以及那些订阅它的组件)。

答案 3 :(得分:0)

您的组件状态应该只包含内部状态值。

您应该考虑使用Redux存储多个组件中所需的更复杂状态。

答案 4 :(得分:-3)

感谢jpdeatorre和daveols指点我Redux。

这是一个示例应用程序(有大量的角切割,但它显示了技术),使用Redux将组件与状态变化隔离开来。

在此示例中,对ID为1的作者Alice的更改不会导致Author组件不依赖于Alice调用其render()。

这是因为Redux为其连接的反应组件提供shouldComponentUpdate来评估道具和相关状态是否已经改变。

预先警告Redux的优化很浅。要确定是否要跳过render() Redux的shouldComponentUpdate检查是否:

  • 新旧道具彼此===
  • 或者,如果不是,他们拥有相同的密钥,并且这些密钥的值彼此为===

因此,它可能导致render()被调用其值仍然在逻辑上相等的组件,但是这些组件的道具和它们的第一级键不会与===相等。请参阅:https://github.com/reactjs/react-redux/blob/master/src/utils/shallowEqual.js

另请注意,为了防止在&#34; dumb&#34;上调用render() Author的组件我必须connect()它才能启用Redux的shouldComponentUpdate逻辑 - 即使该组件对状态一无所知,只是读取它的道具。

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' )
);