问题
General question, but will include a specific example: where is the correct place to loop through state/stored data to extract calculations?
So here, I need to make some calculations to display in a 'stats' sidebar that requires looping through each of an array of clients (could be a rather large number of clients) to pull out different props/values and add them all together. I did it in render just to get it to work which I know is incorrect, but does it happen still in the component and outside of render or in the reducer?
Of note, too, these will be updated values (a client can be marked as 'served' and then the stats sidebar will increment the number of served clients and decrement the number of to be served clients). But that is a little outside the scope of my general question.
Hows and whys are much appreciated and thanks a million!
import React, { Component, PropTypes } from 'react';
import { browserHistory } from 'react-router';
import './ScheduleDayContainer.scss';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as ScheduleActions from '../../actions/ScheduleActions';
class ScheduleDayContainer extends Component {
static propTypes = {
actions: PropTypes.object,
clients: PropTypes.array.isRequired
};
constructor(props, context) {
super(props, context);
}
componentWillMount() {
// gets schedule (code removed because doesn't matter here) and clients array (used below)
this.props.actions.fetchDaySchedule();
}
render() {
const { clients } = this.props;
const getStats = function (clients) {
let totalClientsExpected = clients.length,
totalHousehold = clients.length,
totalServed = 0,
totalNoShows = 0,
totalUnverifiedExpected = 0,
totalNotYetServed = 0;
clients.forEach(function(client) {
totalHousehold += client.family_count;
client.served_at != null ? totalServed += 1 : totalNotYetServed += 1;
// TODO: no show?
client.verified_at === null ? totalUnverifiedExpected += 1 : null;
});
return {
totalClientsExpected,
totalHousehold,
totalServed,
totalNoShows,
totalUnverifiedExpected,
totalNotYetServed
};
};
const stats = getStats(clients);
return (
<div className="day-container">
<aside className="column">
<div className="statistics-bar-container">
<h3 className="statistics-title">Statistics</h3>
<ul className="statistics-items">
<li className="statistics-item">
<p>Clients expected</p>
<span>{stats.totalClientsExpected}</span>
</li>
<li className="statistics-item">
<p>Total household members to be served</p>
<span>{stats.totalHousehold}</span>
</li>
<li className="statistics-item">
<p>Served</p>
<span>{stats.totalServed}</span>
</li>
<li className="statistics-item">
<p>Did not show</p>
<span>{stats.totalNoShows}</span>
</li>
<li className="statistics-item">
<p>Unverified clients expected</p>
<span>{stats.totalUnverifiedExpected}</span>
</li>
<li className="statistics-item">
<p>Yet to be served</p>
<span>{stats.totalNotYetServed}</span>
</li>
</ul>
</div>
</aside>
</div>
);
}
}
function mapStateToProps(state) {
return {
clients: state.schedule.clients
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(ScheduleActions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScheduleDayContainer);
And then in the reducer:
export default function scheduleReducer(state = initialState, action) {
switch (action.type) {
case types.FETCH_DAY:
return {
...state,
clients: action.data.clients,
daySummary: action.data.summary,
times: action.data.times,
serviceDate: action.data.serviceDate,
time: action.data.time
};
default:
return state;
}
}
回答1:
It's generally agreed best practice to try and keep state as normalized (think relational database!) as possible.
Your derived data can be calculated on the fly by helper functions known as selectors. If some of these calculations are expensive you may want to consider the reselect library.
Some reading (sorry but they explain it far better than I can!):
http://redux.js.org/docs/recipes/ComputingDerivedData.html
http://www.thinkloop.com/article/extreme-decoupling-react-redux-selectors/
https://medium.com/@adamrackis/querying-a-redux-store-37db8c7f3b0f#.gl7g9suh2
回答2:
As a general approach, I would not store the calculation results in your application state, but rather do the calculations in the mapStateToProps step in your container.
This is also the approach that the sample todo list application from the redux docs is following:
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
}
}
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
Calculating a filtered list or doing other calculations based on your state is really not that different from a high level perspective.
Once you use that approach in practice and encounter performance issues, you could think about caching the results either in the state or in separate caches. However, optimizations like this before actually facing any issues only make your code more complex without real benefits.
来源:https://stackoverflow.com/questions/40769152/where-to-do-calculations-in-redux