如何使用每个操作的其他状态更改详细信息记录Redux操作

时间:2016-06-20 20:50:30

标签: redux

我有一个Redux应用程序,我将记录用户的操作,并希望将我的操作记录到不同的位置(例如我的日志文件;如果出现问题我的错误日志; Google Analytics)。目前,每个地方都有自己的中间件

/middleware
  log_action_to_file.js
  log_some_actions_to_analytics.js

我的操作对象很简洁,只包含进行状态更改所需的信息。但是,对于人类可读的日志,获取更多信息(通常是操作的结果)很有用。从理论上讲,我们可以添加一个diff状态,但实际上我的日志的读者会想要更具语义意义的东西:

// actions
{
   type: "MOVED_FORWARD_X_PAGES,
   numberOfPages: 5,
   // nowShowingPage: 53         <== log file would like this additional info
}
{
   type: "SELECTED_ITEM_FROM_LIST,
   index: 4,
   // itemSelected: "cheese"     <== log file would like this additional info
}

我可以有一个单独的文件,其中包含一系列操作,指定需要记录的其他属性:

export function addPropertiesBeforeLogging(action, nextState) {
  let loggableAction = { ...action};
  if (action.type == "MOVED_FORWARD_X_PAGES") {
    loggableAction.nowShowingPage = nextState.showingPage;
  } else if (...)

但是每当我添加一个新动作时,我都需要记住更新这个文件,它会变得非常混乱。

有什么更好的方法可以解决这个问题?我应该向action-object本身添加一些额外的信息,自我描述要添加的状态数据吗?

//actions.js

export function moveForwardXPages(numberOfPages) {
  return {
    type: "MOVED_FORWARD_X_PAGES",
    numberOfPages
    additionalLogDataFromNextState: {
      nowShowingPage: "next.showingPage"
    }
  };
}

然后我的中间件可以解析该字符串以了解在新状态下要查询的内容?或者这种状态与行动的联系过于紧密?什么是更好的方式?

1 个答案:

答案 0 :(得分:1)

我认为你的方向正确。 正如您所说,您可以在操作中添加一个新字段,其中包含您需要记录的额外信息。例如,如果您查看FSA (Flux Standard Action),则会为此目的使用属性meta

  

可选的元属性可以是任何类型的值。它适用于不属于有效载荷的任何额外信息。

要记录您的操作,一个好的解决方案是使用一个或多个专用的redux-middleware收费,以捕获具有特殊“特征”的每个操作。 例如,您可以使用符号(例如LOG)标记操作,并轻松地使用中间件捕获它,或者,您可以捕获显示属性meta的操作。

灵感来自redux repo中的real-world示例的小片段:

// LOG symbol
export const LOG = Symbol('LOG')

// action
import { LOG } from './....'
export function moveForward () {
return {
   [LOG]: {
       type: "MOVED_FORWARD_X_PAGES,
       numberOfPages: 5,
       nowShowingPage: 53        // <== log file would like this additional info
       }
}

// A Redux middleware that interprets actions with LOG info specified.
import { LOG } from './....'
export default store => next => action => {
  const log = action[LOG]
  if (typeof log === 'undefined') {
    return next(action)
  }
  // here perform log
  // ...
}

另请参阅此库redux-segment(针对redux的分析集成),它使用属性meta来告诉中间件记录操作。

希望它有所帮助。

更新

我尝试回答下面的问题。

redux的良好模式是将状态形状隐藏到动作(和组件)中。因此,传递项目的路径,您需要登录该操作并不是很重要。

说这个,我不知道你的问题是否有一个简单而优雅的解决方案,但我试着给你一些想法:)

例如,您可以在操作中设置要用于记录信息的selector的名称,而不是路径

通过这种安排: - 您将动作与状态分离,因此将来您可以在不更改动作的情况下更改状态形状,但仅更改选择器 - 您可以使用reselect并撰写更多选择器来记录更多信息 - 您不需要使用eval - 您需要为每个需要记录的信息设置一个选择器

一些代码(未经测试)可以更好地解释我的意思。

// action moveForward.js
export function moveForward () {
  return {
   type: "MOVED_FORWARD_X_PAGES",
   payload: { numberOfPages: 5, nowShowingPage: 53 }
   meta: { selector: 'getShowedPage' }
   }
}

// action selectItem.js
export function selectItem () {
  return {
   type: "SELECTED_ITEM_FROM_LIST",
   payload: { index: 4 }
   meta: { selector: 'getSelectedItem' }
   }
}

// reducer pages.js
export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case "MOVED_FORWARD_X_PAGES":
      return Object.assign({}, state, {
          numberOfPages: action.payload.numberOfPages,
          nowShowingPage: action.payload.nowShowingPage,
          // ...
       })
  }

// reducer items.js
const initialState = { list: [], currentItem: undefined }
export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case "SELECTED_ITEM_FROM_LIST":
      return Object.assign({}, state, {
          currentItem: action.payload.index,
          // ...
       })
  }


// log_middleware.js
import logger from './logger'
export default store => next => action => {
    // if action not contains extra info to log (meta) go ahead
    if (!action.meta)
      return next(action)

    // Save current state and get the next one
    const prevState = store.getState()
    const returnedValue = next(nextAction)
    const nextState = store.getState()

   // Perform log 
   logger(prevState, nextState, action)

   // return the next state
   return returnedValue   
}


// selectors.js 
const getShowedPage = state => state.nowShowingPage
const getSelectedItem = state => state.list[state.currentItem]"

export default selectors = {
   getShowedPage,
   getSelectedItem
}

// logger.js
import selectors from '../selectors'

export default logger (prevState, nextState, action) {
    const selector = state => selectors[action.meta.selector]
    const infoToLog = selector(nextState)

    // Perform log
    console.log(infoToLog)
}

要撰写更多选择器,您可以使用reselect

总结一下,看看D. Abramov选择器的this视频教程和redux-logger源代码。