编辑:我重写了这个问题,以澄清我所追求的内容 - 感谢迄今为止帮助我磨练它的人们.
我试图了解如何最好地在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...
}
}
Run Code Online (Sandbox Code Playgroud)
在这个人为的例子中,州有两个主要区域,由authors和publications键访问.
的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>
);
}
}
...
Run Code Online (Sandbox Code Playgroud)
假设AuthorList有一堆子Author组件,并且PublicationList有一堆子Publication组件可以呈现这些组件的实际内容.
这里是我的问题:假设我想更新bio对于给定的作家,但我不希望render()被称为所有Author和Publication对象,它们的内容都没有改变.
从这个答案:
ReactJS - 在调用"setState"的任何时候都会调用render吗?
React组件的render()函数将在其状态或其任何父级的状态发生变化时被调用 - 无论该状态更改是否与该组件的props有关.可以使用shouldComponentUpdate更改此行为.
人们如何像上面那样处理复杂的状态 - 看起来似乎不是在每个状态变化上对大量组件调用render()都是一个很好的解决方案(即使生成的渲染对象是相同的,因此实际上不会发生变化DOM).
这是一种使用对象传播语法以可读方式有效实现此目的的方法.
let state = {
authors : {
...this.state.authors,
[ givenId ] : {
...this.state.authors[ givenID ],
bio : newValue
}
}
}
this.setState(state)
Run Code Online (Sandbox Code Playgroud)
请记住,当您在jsx中映射项目时,必须将"键"作为道具传递.
这主要是因为,协调(React的"差异"算法来检查发生了什么变化)反应的事情会检查映射的jsx的密钥(大致命名为jsx).
无论如何,在state/stateState或redux中管理状态与"reconciliation"无关.
在这两种情况下,您都可以使用"Object Spread Syntax"语法更改嵌套数据的一部分.
您只关心其余的是将"相同"键传递给映射的jsx.因此,尽管反应重新出现,但它并没有尝试对不必要的部分进行dom更新,这是昂贵的.
dea*_*ode -3
感谢 jpdeatorre 和 daveols 向我指出 Redux。
这是一个使用 Redux 将组件与与其无关的状态更改隔离开来的示例应用程序(包含大量切角,但它展示了技术)。
在此示例中,对 id 1 的作者 Alice 的更改不会导致Author不依赖于 Alice 的组件调用其 render() 。
这是因为 ReduxshouldComponentUpdate为其连接的 React 组件提供了评估 props 以及相关状态是否已更改的信息。
预先警告一下,Redux 这里的优化是浅薄的。要确定是否枯萎,请跳过render()Redux 的shouldComponentUpdate检查,如果:
===一物===彼此相同。因此,这可能会导致render()调用其值在逻辑上仍然等效的组件,但是这些组件的 props 及其第一级键与===. 请参阅: https: //github.com/reactjs/react-redux/blob/master/src/utils/shallowEqual.js
另请注意,为了防止调用render()“哑”组件,Author我必须使用connect()Redux 来启用 Redux 的shouldComponentUpdate逻辑 - 即使该组件对状态根本不执行任何操作,只是读取其 props。
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' )
);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5303 次 |
| 最近记录: |