Edit This Page

疑難排解

這是一個用來分享常見問題和解決辦法的地方。 這些範例使用 React,不過如果你使用其他的東西,你應該還是會發現它們非常有用。

我 dispatch 一個 action 但什麼事都沒發生

有時候,你試著 dispatch 一個 action,但是你的 view 並沒有更新。為什麼會發生這種狀況呢?這有幾個可能的原因。

永遠不要去變動 reducer 的參數

修改 Redux 傳遞給你的 stateaction 很誘人。但請不要這樣做!

Redux 假設你不會在 reducer 中變動它給你的物件。每一次,你都必須回傳新的 state 物件。即使你沒有使用像是 Immutable 之類的 library,你也需要完全避免變動。

Immutability 是讓 react-redux 能有效率訂閱 state 確切更新的關鍵。它也促成一些很棒的開發者體驗功能,像是用 redux-devtools 來 time travel。

例如,像是這樣的 reducer 是錯的,因為它變動了 state:

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      // 錯了!這變動了 state
      state.push({
        text: action.text,
        completed: false
      })
      return state
    case 'COMPLETE_TODO':
      // 錯了!這變動了 state[action.index]。
      state[action.index].completed = true
      return state
    default:
      return state
  }
}

它需要被改寫成像這樣:

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      // 回傳一個新的陣列
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case 'COMPLETE_TODO':
      // 回傳一個新的陣列
      return state.map((todo, index) => {
        if (index === action.index) {
          // 在變動之前先複製物件
          return Object.assign({}, todo, {
            completed: true
          })
        }
        return todo
      })
    default:
      return state
  }
}

這樣程式碼更多了,不過這就是讓 Redux 可預測與高效能的關鍵。如果你想要減少程式碼的量,你可以使用像是 React.addons.update 之類的 helper 來用一個簡潔的語法撰寫 immutable 轉換:

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

// 使用之後:
return update(state, {
  [action.index]: {
    completed: {
      $set: true
    }
  }
})

最後,要更新物件你會需要一些像是 Underscore 的 _.extend 的東西,或甚至更好的,一個 Object.assign 的 polyfill。

請確保你有正確地使用 Object.assign。例如,請從你的 reducer 回傳 Object.assign({}, state, newData),而不要回傳像是 Object.assign(state, newData) 這樣。這樣你才不會覆寫掉前面的 state

你也可以為更簡潔的語法啟用 object spread 運算子提案

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

// 啟用之後:
return state.map((todo, index) => {
  if (index === action.index) {
    return { ...todo, completed: true }
  }
  return todo
})

需要注意的是,實驗性的語言功能有可能會變動。

不要忘記呼叫 dispatch(action)

如果你定義了一個 action creator,呼叫它會自動的 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() {
    // 不會正常運作!
    addTodo('Fix the issue')
  }

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

這不會正常運作,因為你的 action creator 只是一個回傳 action 的 function。而要由你來實際的 dispatch 它。我們不能在定義的時候把你的 action creator 綁定到一個特定的 Store 實體上,因為要在伺服器上 render 的應用程式需要對每個請求有一個獨立的 Redux store。

解決方法是在 store 實體上呼叫 dispatch() method:

handleClick() {
  // 正常運作!(不過無論如何你需要抓得到 store)
  store.dispatch(addTodo('Fix the issue'))
}

如果你在某個 component 層級中很深的地方,把 store 手動的傳遞下去很麻煩。這就是為什麼 react-redux 讓你使用一個 connect higher-order component,它除了會幫你訂閱 Redux store 之外,還會把 dispatch 注入到你的 component 的 props。

修復後的程式碼看起來像這樣:

AddTodo.js

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

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

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

// 除了 state 以外,`connect` 還把 `dispatch` 放到我們的 props 裡。
export default connect()(AddTodo)

如果你想要的話,你可以接著手動的把 dispatch 傳下去給其他的 component。

確保 mapStateToProps 是正確的

可能導致你正確的 dispatch 了 action 也使用了 reducer,但是對應的 state 卻並沒有正確的被轉換成 props。

其他不能正常運作的原因

#redux Reactiflux Discord 頻道上詢問,或是開一個 issue。 如果你搞清楚了,請編輯這份文件作為好意給下一個遇到同樣問題的人。