Redux не обновляет компоненты при обновлении свойств глубокого неизменяемого состояния
Мой вопрос: почему обновление свойства объекта в массиве в моем неизменяемом состоянии (Map) не вызывает Redux для обновления моего компонента?
Я пытаюсь создать виджет, который загружает файлы на мой сервер, и мое начальное состояние (из моего UploaderReducer, который вы увидите ниже) объекта выглядит следующим образом:
let initState = Map({
files: List(),
displayMode: 'grid',
currentRequests: List()
});
У меня есть метод thunk, который запускает загрузку и отправляет действия, когда происходит событие (например, обновление хода выполнения). Например, событие onProgress выглядит так:
onProgress: (data) => {
dispatch(fileUploadProgressUpdated({
index,
progress: data.percentage
}));
}
Я использую redux-actions для создания и обработки своих действий, поэтому мой редуктор для этого действия выглядит следующим образом:
export default UploaderReducer = handleActions({
// Other actions...
FILE_UPLOAD_PROGRESS_UPDATED: (state, { payload }) => (
updateFilePropsAtIndex(
state,
payload.index,
{
status: FILE_UPLOAD_PROGRESS_UPDATED,
progress: payload.progress
}
)
)
}, initState);
И updateFilePropsAtIndex выглядит так:
export function updateFilePropsAtIndex (state, index, fileProps) {
return state.updateIn(['files', index], file => {
try {
for (let prop in fileProps) {
if (fileProps.hasOwnProperty(prop)) {
if (Map.isMap(file)) {
file = file.set(prop, fileProps[prop]);
} else {
file[prop] = fileProps[prop];
}
}
}
} catch (e) {
console.error(e);
return file;
}
return file;
});
}
Пока что все это кажется прекрасно работает! В Redux в инструментах разработчика, он показывает, как действие, как ожидалось. Однако ни один из моих компонентов не обновляется! Добавление новых элементов в массив files повторно визуализирует мой пользовательский интерфейс с добавленными новыми файлами, поэтому Redux, безусловно, не имеет проблем со мной. тот...
Мой компонент верхнего уровня, который подключается к магазину с помощью connect выглядит следующим образом:
const mapStateToProps = function (state) {
let uploadReducer = state.get('UploaderReducer');
let props = {
files: uploadReducer.get('files'),
displayMode: uploadReducer.get('displayMode'),
uploadsInProgress: uploadReducer.get('currentRequests').size > 0
};
return props;
};
class UploaderContainer extends Component {
constructor (props, context) {
super(props, context);
// Constructor things!
}
// Some events n stuff...
render(){
return (
<div>
<UploadWidget
//other props
files={this.props.files} />
</div>
);
}
}
export default connect(mapStateToProps, uploadActions)(UploaderContainer);
uploadActions это объект с действиями, созданными с помощью redux-actions.
Объект file в массиве files в основном таков:
{
name: '',
progress: 0,
status
}
UploadWidget в основном является перетаскиванием n drop div и массивом files, распечатанным на экране.
Я пытался использовать redux-immutablejs, чтобы помочь, как я видел во многих постах на GitHub, но я понятия не имею, помогает ли это... Это мой корень. редуктор:
import { combineReducers } from 'redux-immutablejs';
import { routeReducer as router } from 'redux-simple-router';
import UploaderReducer from './modules/UploaderReducer';
export default combineReducers({
UploaderReducer,
router
});
Моя точка входа в приложение выглядит следующим образом:
const store = configureStore(Map({}));
syncReduxAndRouter(history, store, (state) => {
return state.get('router');
});
// Render the React application to the DOM
ReactDOM.render(
<Root history={history} routes={routes} store={store}/>,
document.getElementById('root')
);
Наконец, мой компонент <Root/> выглядит следующим образом:
import React, { PropTypes } from 'react';
import { Provider } from 'react-redux';
import { Router } from 'react-router';
export default class Root extends React.Component {
static propTypes = {
history: PropTypes.object.isRequired,
routes: PropTypes.element.isRequired,
store: PropTypes.object.isRequired
};
get content () {
return (
<Router history={this.props.history}>
{this.props.routes}
</Router>
);
}
//Prep devTools, etc...
render () {
return (
<Provider store={this.props.store}>
<div style={{ height: '100%' }}>
{this.content}
{this.devTools}
</div>
</Provider>
);
}
}
Таким образом, в конечном счете, если я попытаюсь обновить "прогресс" в следующем объекте состояния, React/Redux не обновит мои компоненты:
{
UploaderReducer: {
files: [{progress: 0}]
}
}
Почему это? Я думал, что вся идея использования неизменяемого.js было ли проще сравнивать измененные объекты независимо от того, насколько глубоко вы их обновили?
Кажется, что обычно становится неизменным, чтобы работа с Redux не так проста, как кажется:
Как пользоваться неизменяемым.JS с возвращением?
https://github.com/reactjs/redux/issues/548
Тем не менее, расхваливаемые преимущества использования Immutable, похоже, стоят этой битвы, и я хотел бы выяснить, что я делаю неправильно!
Обновление 10 апреля 2016
Выбранный ответ сказал мне, что я делаю неправильно, и для полноты картины моя функция updateFilePropsAtIndex теперь содержит просто следующее:
return state.updateIn(['files', index], file =>
Object.assign({}, file, fileProps)
);
Это работает прекрасно! :)
1 ответ:
Сначала две общие мысли:
- неизменный.js является потенциально полезным, да, но вы можете выполнить ту же самую неизменяемую обработку данных без его использования. Существует ряд библиотек, которые могут помочь сделать неизменяемые обновления данных более легкими для чтения, но по-прежнему работать с простыми объектами и массивами. Многие из них перечислены на страниценеизменяемые данные в моем репозитории библиотек, связанных с Redux.
- Если компонент React не кажется обновляющимся, это почти всегда потому, что редуктор на самом деле мутирует данные. В Redux FAQ есть ответ на эту тему, по адресу http://redux.js.org/docs/FAQ.html#react-not-rerendering .
Теперь, учитывая, что вы используете неизменяемый.js, я признаю, что мутация данных кажется немного маловероятной. То, что сказал... строка
file[prop] = fileProps[prop]в вашем редукторе действительно кажется ужасно любопытной. Что именно вы ожидаете там увидеть? Я бы хорошо рассмотрел эту часть.На самом деле, теперь, когда я смотреть на это... Я почти на 100% уверен, что вы мутируете данные. Обратный вызов программы обновления в
state.updateIn(['files', index])возвращает точно такой же файловый объект, который вы получили в качестве параметра. За непреложное.JS docs at https://facebook.github.io/immutable-js/docs/#/Map :Если функция updater возвращает то же значение, с которым она была вызвана, то никаких изменений не произойдет. Это по-прежнему верно, если указано значение notSetValue.
Так что да. Вы возвращаете то же самое значение, которое вам было дано, ваше прямые мутации к нему появляются в DevTools, потому что этот объект все еще висит вокруг, но так как вы вернули тот же самый объект неизменным.в JS на самом деле не возвращая никаких модифицированных объектов в иерархии. Таким образом, когда Redux проверяет объект верхнего уровня, он видит, что ничего не изменилось, не уведомляет подписчиков, и поэтому ваш компонент
mapStateToPropsникогда не запускается.Очистите свой редуктор и верните новый объект изнутри этого обновителя, и он должен все просто работа.
(Довольно запоздалый ответ, но я только сейчас увидел вопрос, и он, кажется, все еще открыт. Надеюсь, вы действительно получили это исправлено сейчас...)
Comments