我有一个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"
}
};
}
然后我的中间件可以解析该字符串以了解在新状态下要查询的内容?或者这种状态与行动的联系过于紧密?什么是更好的方式?
答案 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源代码。