2016-11-22 22 views
8

EDIT: Tôi viết lại câu hỏi này để làm rõ những gì tôi đang theo dõi - cảm ơn bạn đến những người đã phản hồi cho đến nay giúp tôi trau dồi nó.Cách tiếp cận tốt để quản lý trạng thái lồng nhau trong React

Tôi đang cố gắng hiểu cách tốt nhất để quản lý trạng thái lồng nhau phức tạp trong React, đồng thời giới hạn số lần render() được gọi cho các thành phần có nội dung không thay đổi.

Là nền:

Giả sử tôi có nhà nước với cả hai "tác giả" và "ấn" trong một đối tượng như thế này:

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

Trong ví dụ giả tạo này nhà nước có hai lĩnh vực chính, truy cập bằng cách các phím authorspublications.

Phím authors dẫn đến một đối tượng được khóa trên ID của tác giả, dẫn đến đối tượng có dữ liệu tác giả.

Phím publications dẫn đến một đối tượng được khóa trên ID của ấn phẩm có một số dữ liệu xuất bản và một loạt tác giả.

Giả sử trạng thái của tôi là trong một bộ phận App với các thành phần con như giả sau 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> 
    ); 
    } 
} 
... 

Giả AuthorList có một loạt các con Author thành phần, và PublicationList có một loạt các con Publication thành phần mà làm cho nội dung thực tế của những điều đó.

Đây là câu hỏi của tôi: Giả sử tôi muốn cập nhật bio cho một tác giả nào đó, nhưng tôi không muốn render() được gọi cho tất cả các đối tượng AuthorPublication có nội dung không thay đổi.

Từ câu trả lời này:

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

Một Phản ứng chức năng thành phần của render() sẽ được gọi bất cứ lúc nào trạng thái của nó, hoặc tình trạng của bất kỳ cha mẹ của nó, thay đổi - dù rằng sự thay đổi trạng thái có bất cứ điều gì để làm với các đạo cụ của thành phần đó. Hành vi này có thể được thay đổi với shouldComponentUpdate.

Cách mọi người xử lý trạng thái phức tạp như trên - có vẻ như gọi hàm render() trên số lượng lớn các thành phần trên mọi thay đổi trạng thái là một giải pháp tốt (ngay cả khi đối tượng được hiển thị là giống nhau và không thay đổi xảy ra với DOM thực tế).

Trả lời

-2

Cảm ơn jpdeatorre và daveols đã chỉ cho tôi tại Redux.

Dưới đây là một ứng dụng ví dụ (với tấn cắt góc, nhưng nó cho thấy những kỹ thuật) của việc sử dụng Redux để cô lập các thành phần từ những thay đổi trạng thái đó không liên quan đến họ.

Trong ví dụ này, những thay đổi đối với tác giả Alice có id 1 không gây ra các thành phần Author không phụ thuộc vào Alice để hiển thị() được gọi.

Điều này là do cung cấp của Redux shouldComponentUpdate cho các thành phần phản ứng được kết nối của nó đánh giá liệu các đạo cụ và nếu trạng thái có liên quan đã thay đổi.

Hãy cảnh báo rằng tối ưu hóa của Redux ở đây cạn. Để xác định bị teo nhỏ hay không để bỏ qua render()shouldComponentUpdate kiểm tra Redux nếu:

  • Các đạo cụ cũ và mới là === với nhau
  • Hoặc, nếu không muốn nói rằng họ có các phím tương tự và các giá trị của những phím === với nhau.

Vì vậy, có thể dẫn đến render() được gọi cho các thành phần có giá trị vẫn tương đương về mặt logic tuy nhiên đạo cụ cho các thành phần đó và khóa cấp đầu tiên không so sánh bằng ===. Xem: https://github.com/reactjs/react-redux/blob/master/src/utils/shallowEqual.js

Cũng lưu ý rằng, để ngăn ngừa việc gọi render() trên thành phần "ngớ ngẩn" của Author tôi phải connect() nó để Redux để cho phép shouldComponentUpdate Logic Redux của - mặc dù thành phần đó đang làm gì cả với nhà nước và chỉ cần đọc đạo cụ của nó.

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

Hmm ... không chắc chắn lý do tại sao bạn sẽ đặt câu trả lời cho câu hỏi của bạn, thực tế giống như những gì tôi đã nói. – jpdelatorre

+0

Vì hai câu trả lời này không giống nhau. Kết quả của bạn trong tất cả các tác giả trẻ em của AuthorList được tái rendered bất cứ khi nào trạng thái của bất cứ điều gì trong state.authors thay đổi. Tôi chọn lọc lại chỉ hiển thị các trẻ em của AuthorList đã thay đổi nội dung, đó là những gì tôi đã cố gắng để làm. – deadcode

0

Trạng thái thành phần của bạn chỉ nên chứa giá trị trạng thái nội bộ.

Bạn nên xem xét lưu trữ trạng thái phức tạp hơn cần thiết trong nhiều thành phần với Redux.

2

Bạn nên sử dụng immutability helper, mỗi React's documentation. Điều này cung cấp một cơ chế để cập nhật một phần của nhà nước, và xử lý bất kỳ nhân bản cần thiết cho bạn, với ít nhất là sao chép càng tốt.

này cho phép bạn làm điều gì đó như:

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

Nó sẽ chỉ lại làm cho các thành phần bị ảnh hưởng bởi sự thay đổi.

+0

Trong trường hợp này, bạn có đang tạo đối tượng mới và ghi đè toàn bộ tiểu bang bằng nó không? Sẽ không tái hiện lại mọi vật thể phụ thuộc vào bất cứ thứ gì trong trạng thái - mặc dù sự thay đổi thực sự đã được phân tách theo cách này trong this.state.authors ['342']. – deadcode

+0

Tôi không biết nó hoạt động như thế nào trong nội bộ, nhưng dựa trên việc đọc tài liệu React, nó sử dụng các bản sao nông nếu có thể (và tôi giả sử sử dụng lại các tham chiếu không thay đổi). –

+0

Tôi thấy câu trả lời này hữu ích trong việc hiểu điều gì đang xảy ra: http://stackoverflow.com/questions/24718709/reactjs-does-render-get-called-any-time-setstate-is-called - theo mặc định, hiển thị của thành phần Hàm() sẽ được gọi bất kỳ lúc nào trạng thái của nó, hoặc trạng thái của bất kỳ cha mẹ nào của nó, thay đổi - bất kể sự thay đổi trạng thái đó có liên quan gì đến các đạo cụ của thành phần đó hay không. Hành vi này có thể được thay đổi với shouldComponentUpdate. – deadcode

4

Đây là cách để thực hiện điều này một cách hiệu quả và dễ đọc bằng cách sử dụng Cú pháp lan truyền đối tượng.

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

Hãy nhớ rằng bạn phải chuyển 'khóa' làm bản đồ khi bạn ánh xạ các mục trong jsx.

Điều này chủ yếu là do, hòa giải (thuật toán "difft" của React để kiểm tra điều gì đã thay đổi) mà phản ứng không kiểm tra khóa cho jsx được ánh xạ (khoảng đặt tên là jsx).

Dù sao, quản lý nhà nước trong tạo ra phản ứng tiểu bang/setstate hoặc trong Redux là không liên quan đến 'hòa giải'.

Trong cả hai trường hợp, bạn có thể thay đổi một phần của một dữ liệu lồng nhau sử dụng cú pháp 'Object Spread Cú pháp'.

Tất cả các bạn sẽ quan tâm đến phần còn lại là để vượt qua phím 'giống hệt' đến jsx ánh xạ. Vì vậy, mặc dù phản ứng lại các rerenders, nó không cố gắng để làm cho bản cập nhật dom cho các bộ phận không cần thiết, đó là tốn kém.

+0

Điều này không tái hiện lại mọi thứ phụ thuộc vào bất cứ điều gì ở bất kỳ đâu trong tiểu bang? Tôi đánh giá cao cú pháp đẹp hơn để thực hiện cập nhật - nhưng nếu tôi phải render lại mọi thành phần mỗi khi tôi thay đổi tiểu sử của một tác giả thì có vẻ như tôi đang làm điều gì sai. – deadcode

+0

Bạn cho rằng phản ứng so sánh newState và oldState bằng cách tham chiếu? Điều này là không chính xác, phản ứng làm cho một so sánh sâu sắc cho các phím của newState và oldState. Vì chỉ có giá trị khóa cụ thể thay đổi, nó sẽ lập lại các phần tương ứng trong các thành phần của bạn. – FurkanO

+0

Tôi thấy câu trả lời này hữu ích trong việc tìm hiểu những gì đang diễn ra: http://stackoverflow.com/questions/24718709/reactjs-does-render-get-called-any-time-setstate-is-called - theo mặc định hiển thị của thành phần Hàm() sẽ được gọi bất kỳ lúc nào trạng thái của nó, hoặc trạng thái của bất kỳ cha mẹ nào của nó, thay đổi - bất kể sự thay đổi trạng thái đó có liên quan gì đến các đạo cụ của thành phần đó hay không. Hành vi này có thể được thay đổi với shouldComponentUpdate. – deadcode

1

Tôi nghĩ rằng sử dụng Redux sẽ làm cho ứng dụng của bạn hiệu quả và dễ dàng hơn để quản lý.

Có trạng thái toàn cầu được gọi là Lưu trữ Redux, cho phép bất kỳ thành phần nào của bạn đăng ký một lát của cửa hàng và hiển thị lại khi có thay đổi đối với dữ liệu đó.

Trong ví dụ của bạn, cách Redux thực hiện nó sẽ là thành phần AuthorList của bạn sẽ đăng ký vào các đối tượng state.authors và nếu bất kỳ thành phần bên trong hoặc bên ngoài AuthorList thành phần cập nhật state.authors, chỉ có thành phần AuthorList sẽ tái render (và những người được đăng ký với nó).

Các vấn đề liên quan