Перейти к основному содержимому
Неофициальный Бета-перевод

Эта страница переведена PageTurner AI (бета). Не одобрена официально проектом. Нашли ошибку? Сообщить о проблеме →

Решение проблем

Здесь собраны распространённые проблемы и способы их решения. В примерах используется React, но они будут полезны и при работе с другими инструментами.

При диспетчеризации экшена ничего не происходит

Иногда при попытке диспетчеризовать экшен представление не обновляется. Почему так происходит? Причин может быть несколько.

Никогда не изменяйте аргументы редюсера

Возникает соблазн изменить state или action, переданные в редюсер. Не делайте этого!

Redux предполагает, что вы никогда не изменяете объекты, которые он передаёт в редюсер. Каждый раз вы должны возвращать новый объект состояния. Даже если вы не используете библиотеки вроде Immer, мутации нужно избегать полностью.

Неизменяемость позволяет react-redux эффективно подписываться на детальные обновления состояния. А также она обеспечивает отличные возможности для разработчиков, например, путешествие по времени с redux-devtools.

Например, такой редюсер неправильный, потому что он изменяет состояние:

function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
// Wrong! This mutates state
state.push({
text: action.text,
completed: false
})
return state
case 'COMPLETE_TODO':
// Wrong! This mutates state[action.index].
state[action.index].completed = true
return state
default:
return state
}
}

Его нужно переписать так:

function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
// Return a new array
return [
...state,
{
text: action.text,
completed: false
}
]
case 'COMPLETE_TODO':
// Return a new array
return state.map((todo, index) => {
if (index === action.index) {
// Copy the object before mutating
return Object.assign({}, todo, {
completed: true
})
}
return todo
})
default:
return state
}
}

Кода стало больше, но именно это делает Redux предсказуемым и эффективным. Если вы хотите писать меньше кода, можно использовать вспомогательные функции, например React.addons.update, для записи неизменяемых преобразований в лаконичном синтаксисе:

// Before:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: true
})
}
return todo
})

// After
return update(state, {
[action.index]: {
completed: {
$set: true
}
}
})

Для обновления объектов вам понадобится что-то вроде _.extend из Underscore или, что лучше, полифилл для Object.assign.

Убедитесь, что вы используете Object.assign правильно. Например, вместо возврата Object.assign(state, newData) из редюсера возвращайте Object.assign({}, state, newData). Так вы не перезапишете предыдущее состояние state.

Также можно использовать предложение по оператору расширения объекта для более лаконичного синтаксиса:

// Before:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: true
})
}
return todo
})

// After:
return state.map((todo, index) => {
if (index === action.index) {
return { ...todo, completed: true }
}
return todo
})

Обратите внимание, что экспериментальные возможности языка могут измениться.

Также следите за вложенными объектами состояния, которые нужно копировать глубоко. И _.extend, и Object.assign создают поверхностную копию состояния. Рекомендации по работе с вложенными объектами состояния смотрите в разделе Обновление вложенных объектов.

Не забывайте вызывать dispatch(action)

Если вы создали генератор экшенов, его вызов не приведёт к автоматической диспетчеризации экшена. Например, этот код ничего не сделает:

TodoActions.js

export function addTodo(text) {
return { type: 'ADD_TODO', text }
}

AddTodo.js

import React, { Component } from 'react'
import { addTodo } from './TodoActions'

class AddTodo extends Component {
handleClick() {
// Won't work!
addTodo('Fix the issue')
}

render() {
return <button onClick={() => this.handleClick()}>Add</button>
}
}

Это не работает, потому что генератор экшенов — это просто функция, которая возвращает экшен. Диспетчеризовать его — ваша задача. Мы не можем привязать генераторы экшенов к конкретному экземпляру хранилища во время определения, потому что приложения, рендерящиеся на сервере, требуют отдельного хранилища Redux для каждого запроса.

Решение — вызвать метод dispatch() экземпляра хранилища:

handleClick() {
// Works! (but you need to grab store somehow)
store.dispatch(addTodo('Fix the issue'))
}

Если вы находитесь глубоко в иерархии компонентов, передача хранилища вручную может быть утомительной. Поэтому react-redux позволяет использовать компонент высшего порядка connect, который, помимо подписки на хранилище Redux, внедряет dispatch в пропсы вашего компонента.

Исправленный код выглядит так:

AddTodo.js

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { addTodo } from './TodoActions'

class AddTodo extends Component {
handleClick() {
// Works!
this.props.dispatch(addTodo('Fix the issue'))
}

render() {
return <button onClick={() => this.handleClick()}>Add</button>
}
}

// In addition to the state, `connect` puts `dispatch` in our props.
export default connect()(AddTodo)

При необходимости вы можете передавать dispatch вручную в другие компоненты.

Убедитесь в корректности mapStateToProps

Возможно, вы корректно диспатчите экшен и применяете редюсер, но соответствующее состояние не преобразуется должным образом в пропсы.

Если другие проблемы

Задайте вопрос в Discord-канале #redux Reactiflux или создайте issue.

Если вы нашли решение, пожалуйста, отредактируйте этот документ — это поможет следующим пользователям с аналогичной проблемой.