在React/Redux应用程序中,一个人应该把服务实例放在哪里?

7
假设我正在使用Redux编写应用程序,并被要求使用第三方库添加日志记录。它的API如下:
function createLogger(token) {
    // the logger has internal state!
    let logCount = 0;

    return {
        log(payload) {
            logCount++;            // modify local state

            fetch('/someapi', {    // ship payload to some API
                method: 'POST',
                body: payload
            });
        }
    };
}

我会像这样使用该库:

我会像这样使用该库:

let logger = createLogger('xyz');
logger.log('foobar');

我希望在应用程序初始化期间仅创建一次记录器实例。但问题是:我应该将日志记录器实例存储在哪里

首先的想法是将其放在存储区中。但这样做是否明智呢?就像代码示例中所演示的那样,记录器对象具有状态,它在闭包中存储一个计数器。我不会像使用不可变对象那样获得新实例。正如我们所知,状态只能通过纯净的reducer函数进行修改。

其他可能性是在redux中间件闭包中的某个地方创建实例,或者只是创建全局变量,这显然对于可测试性来说是不好的。

对于这种(我认为)相当常见的情况是否有最佳实践呢?


你是否正在使用ES6模块?组件是否直接记录调用,还是由store/actions自动完成日志记录? - Kyeotic
是的,我正在使用ES6模块。至于其他问题,我希望这些调用的自然位置是通过类似redux-thunk的动作创建器。我不希望我的组件引起副作用。 - VoY
如果记录器本身不会对应用程序状态造成任何副作用,那么它很可能属于中间件。不过你的代码还有一些不清楚的地方。(1) token 参数的目的是什么?(2) logCount 变量的目的是什么?(post 请求中均未包含 token 和 logCount)(3) 最后,payload 是什么?它是由操作和/或状态导出的吗? - David L. Walsh
(1) 那只是记录器对象的一些配置。我可以想象该对象本身使用该参数进行异步初始化(例如,登录日志然后同步返回记录器,并在登录完成后排队记录调用)。 (2) 当然它本身没有意义,但我用它来表明记录器具有某种内部状态,随着时间的推移而改变,而我不一定需要获取新实例。 (3) 在实践中,这可能是从动作和/或状态派生出的某些消息。暂时将其放入中间件中似乎是最好的选择...感谢您的评论! - VoY
2个回答

4

由于您使用的是ES6模块,我建议将日志记录器设置为一个模块,导出它,并在计划使用它的任何地方导入它。我认为从操作中记录日志是一个可靠的计划,因为它使组件不知道该操作,而且不会使仓库受到副作用的污染。

function createLogger(token) {
    // the logger has internal state!
    let logCount = 0;

    return {
        log(payload) {
            logCount++;            // modify local state

            fetch('/someapi', {    // ship payload to some API
                method: 'POST',
                body: payload
            });
        }
    };
}

export default const logger = createLogger('xyz');

你的操作创建器

import logger from 'logger-module';

//
logger.log('somestuff');

通过导入记录器并在其方法上放置所需的间谍/存根,仍然可以轻松地进行测试。


所以基本上您将使用日志记录器测试操作,通过利用某个库来覆盖使导入语句导入您的模拟而不是真实的日志记录器,我理解得对吗? - VoY
你仍然可以导入真实的日志记录器。只要在使用之前将其方法进行Spy/Stub替换,调用就会被拦截。它只是一个JavaScript对象,而Spying/Stubing则替换了它的属性。这只是标准的测试实践。 - Kyeotic
对我来说,这在某种程度上类似于全局变量,只是你可以相对容易地交换实现。但是你将无法创建两个独立的redux应用程序实例,因为你只能获得一个模块级别的记录器。 - VoY
1
如果必要的话,您可以导出一个日志记录器工厂,该工厂基于键返回日志记录器。然后,每个应用程序都可以使用生成的密钥请求日志记录器,并且它们将各自拥有自己的单例。不过我们现在开始深入细节了。 - Kyeotic

0

来自Redux文档

/**
 * Sends crash reports as state is updated and listeners are notified.
 */
const crashReporter = store => next => action => {
  try {
    return next(action)
  } catch (err) {
    console.error('Caught an exception!', err)
    Raven.captureException(err, {
      extra: {
        action,
        state: store.getState()
      }
    })
    throw err
  }
}

Raven 是一个第三方库。

如果该库有自己的状态,那么在中间件中使用它不应该是一个问题(状态属于库而不是您的应用程序)。如果您为其创建状态,出于某种原因,则该状态应该属于 Redux 存储,可能位于 store.logger 或其他位置。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接