Próbuję oddzielić składnik prezentacji od składnika kontenera. Mam SitesTable
i SitesTableContainer
. Kontener jest odpowiedzialny za wyzwalanie działań redukujących w celu pobrania odpowiednich witryn na podstawie bieżącego użytkownika.
Problem polega na tym, że bieżący użytkownik jest pobierany asynchronicznie, po początkowym renderowaniu komponentu kontenera. Oznacza to, że komponent kontenera nie wie, że musi ponownie wykonać kod w swojej componentDidMount
funkcji, który zaktualizowałby dane do wysłania do SitesTable
. Myślę, że muszę ponownie wyrenderować komponent kontenera, gdy zmieni się jeden z jego właściwości (użytkownika). Jak mam to zrobić poprawnie?
class SitesTableContainer extends React.Component {
static get propTypes() {
return {
sites: React.PropTypes.object,
user: React.PropTypes.object,
isManager: React.PropTypes.boolean
}
}
componentDidMount() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
render() {
return <SitesTable sites={this.props.sites}/>
}
}
function mapStateToProps(state) {
const user = userUtils.getCurrentUser(state)
return {
sites: state.get('sites'),
user,
isManager: userUtils.isManager(user)
}
}
export default connect(mapStateToProps)(SitesTableContainer);
reactjs
redux
react-redux
David
źródło
źródło
componentDidMount
zmieniąsites
węzeł w stanie aplikacji, który jest przekazywany doSitesTable
. Węzeł SitesStablesites
będzie się zmieniał.Odpowiedzi:
Musisz dodać warunek w swojej
componentDidUpdate
metodzie.Przykład używa
fast-deep-equal
do porównania obiektów.import equal from 'fast-deep-equal' ... constructor(){ this.updateUser = this.updateUser.bind(this); } componentDidMount() { this.updateUser(); } componentDidUpdate(prevProps) { if(!equal(this.props.user, prevProps.user)) // Check if it's a new user, you can also use some unique property, like the ID (this.props.user.id !== prevProps.user.id) { this.updateUser(); } } updateUser() { if (this.props.isManager) { this.props.dispatch(actions.fetchAllSites()) } else { const currentUserId = this.props.user.get('id') this.props.dispatch(actions.fetchUsersSites(currentUserId)) } }
Używanie hooków (React 16.8.0+)
import React, { useEffect } from 'react'; const SitesTableContainer = ({ user, isManager, dispatch, sites, }) => { useEffect(() => { if(isManager) { dispatch(actions.fetchAllSites()) } else { const currentUserId = user.get('id') dispatch(actions.fetchUsersSites(currentUserId)) } }, [user]); return ( return <SitesTable sites={sites}/> ) }
Jeśli porównywany atrybut jest obiektem lub tablicą, powinieneś użyć
useDeepCompareEffect
zamiastuseEffect
.źródło
user
zmianie? Ile to kosztuje?ComponentWillReceiveProps()
w przyszłości zostanie uznany za przestarzały z powodu błędów i niespójności. Alternatywnym rozwiązaniem ponownego renderowania komponentu przy zmianie rekwizytów jest użycieComponentDidUpdate()
iShouldComponentUpdate()
.ComponentDidUpdate()
jest wywoływana za każdym razem, gdy składnik aktualizuje ORAZ jeśliShouldComponentUpdate()
zwraca wartość true (jeśliShouldComponentUpdate()
nie jest zdefiniowana, zwraca wartośćtrue
domyślną).shouldComponentUpdate(nextProps){ return nextProps.changedProp !== this.state.changedProp; } componentDidUpdate(props){ // Desired operations: ex setting state }
To samo zachowanie można osiągnąć tylko przy użyciu
ComponentDidUpdate()
metody, umieszczając w niej instrukcję warunkową.componentDidUpdate(prevProps){ if(prevProps.changedProp !== this.props.changedProp){ this.setState({ changedProp: this.props.changedProp }); } }
Jeśli ktoś spróbuje ustawić stan bez warunku lub bez definiowania
ShouldComponentUpdate()
komponentu, zostanie nieskończenie ponownie renderowanyźródło
componentWillReceiveProps
wkrótce zostanie wycofana i jest sugerowana przeciwko użyciu.Możesz użyć
KEY
unikalnego klucza (kombinacji danych), który zmienia się wraz z właściwościami, a ten komponent zostanie ponownie wyrenderowany ze zaktualizowanymi właściwościami.źródło
componentWillReceiveProps(nextProps) { // your code here}
Myślę, że to wydarzenie, którego potrzebujesz.
componentWillReceiveProps
uruchamia się, gdy komponent otrzyma coś przez rekwizyty. Stamtąd możesz sprawdzić, a następnie zrobić, co chcesz.źródło
componentWillReceiveProps
przestarzałe *Poleciłbym przyjrzeć się mojej odpowiedzi i sprawdzić, czy ma ona znaczenie dla tego, co robisz. Jeśli rozumiem twój prawdziwy problem, jest to, że po prostu nie używasz poprawnie akcji asynchronicznej i nie aktualizujesz "sklepu" redux, który automatycznie zaktualizuje twój komponent o nowe rekwizyty.
Ta sekcja twojego kodu:
componentDidMount() { if (this.props.isManager) { this.props.dispatch(actions.fetchAllSites()) } else { const currentUserId = this.props.user.get('id') this.props.dispatch(actions.fetchUsersSites(currentUserId)) } }
Nie powinno być wyzwalane w komponencie, powinno być obsługiwane po wykonaniu pierwszego żądania.
Spójrz na ten przykład z Redux-thunk :
function makeASandwichWithSecretSauce(forPerson) { // Invert control! // Return a function that accepts `dispatch` so we can dispatch later. // Thunk middleware knows how to turn thunk async actions into actions. return function (dispatch) { return fetchSecretSauce().then( sauce => dispatch(makeASandwich(forPerson, sauce)), error => dispatch(apologize('The Sandwich Shop', forPerson, error)) ); }; }
Nie musisz koniecznie używać redux-thunk, ale pomoże ci to wnioskować w takich scenariuszach i pisać dopasowany kod.
źródło
makeASandwichWithSecretSauce
swój komponent?Przyjazna metoda jest następująca, ponieważ po zaktualizowaniu właściwości automatycznie wyrenderuje komponent:
render { let textWhenComponentUpdate = this.props.text return ( <View> <Text>{textWhenComponentUpdate}</Text> </View> ) }
źródło