当将可能为空(但不为空)的值传递给匿名回调时,Typescript会抱怨

时间:2020-02-16 18:58:51

标签: typescript vue.js error-handling vuex

当我拥有一个最初以null开头的属性时,如何将其作为参数传递给期望非null值的匿名回调函数,而不会抱怨打字稿?

我可以将函数调用包装在if语句中以检查是否为null,这样可以在函数调用级别停止键入脚本的抱怨,但是在回调中,键入脚本仍然会抱怨。

这是代码段。我允许某些DatabaseState属性为null,因为这是它们的初始化方式。

//types.ts
export interface DatabaseState {
  db: PouchDB.Database | null;
  remoteDb: PouchDB.Database | null;
  dbReplication: PouchDB.Replication.Sync<{}> | null;
  dbSyncStatus: number;
  dbSyncInfo: string;
  dbSyncError: string;
}

//state typing - state: DatabaseState

//An action in database.ts
async startDatabaseSyncronisation({ state, commit, dispatch }) {
  var opts = { live: true, retry: true };

  if (state.remoteDb != null && state.db != null) {

    /* THE IF STATEMENT PREVENTS THE BELOW FUNCTION FROM CAUSING TYPESCRIPT TO COMPLAIN */

    await PouchDB.replicate(state.remoteDb, state.db, opts).on("complete",
      function(info) {

        /* WITHIN THE CALLBACK FUNCTION THOUGH, TYPESCRIPT COMPLAINS THAT state.remoteDb and state.db
        COULD BE NULL AND THE FUNCTION DOESN'T ACCEPT A NULL VALUE */

        let replication = PouchDB.sync(state.remoteDb, state.db, opts)
          .on("paused", function(err: {}) {
            dispatch("onSyncPaused", err);
          })
          .on("active", function() {
            dispatch("onSyncActive");
          })
          .on("denied", function(err: {}) {
            dispatch("onSyncError", err);
          })
          .on("error", function(err: {}) {
            dispatch("onSyncError", err);
          })
          .on("complete", function() {});
        commit(SET_DB_REPLICATION, replication);
      }
    );
  }
},

我应该如何进行这类类型的检查? 是否有一种更加“ Typescript友好”的方式将初始值描述为未设置?

该项目使用Vue和Vuex进行状态管理。

3 个答案:

答案 0 :(得分:0)

状态的值可以在if check和complate回调之间改变。如果您确定状态没有改变,可以使用null assertion operatorstate!.remoteDb告诉编译器认为这些值不能为空。

答案 1 :(得分:0)

我强烈建议您始终使用null帐户。 null的概念被认为是一个错误,并且不保护自己免受该值的侵扰会使您的代码不安全。但是,如果您坚持要这样做,则可以选择关闭名为tsconfig.json https://www.typescriptlang.org/docs/handbook/compiler-options.html

strictNullChecks

如果remoteDbdb is为空,您还要尝试查询吗?不仅仅是在运行时破坏代码吗?

编辑: 如果任何一个值为null,该函数将永远不会运行?如果是这种情况,库将错误地键入replicatesync。然后由程序包作者进行监督。

没有任何好的方法可以解决第三方的一个基本错误。记录此问题最简单的方法,但仍要继续键入操作是创建一个带有变量名称的type来声明推理。也许其他人有更好的主意,但我会这样做:

type POUCH_BD_DATABASE_OR_NULL = PouchDB.Database

export interface DatabaseState {
  db: POUCH_BD_DATABASE_OR_NULL;
  remoteDb: POUCH_BD_DATABASE_OR_NULL;
  dbReplication: PouchDB.Replication.Sync<{}> | null;
  dbSyncStatus: number;
  dbSyncInfo: string;
  dbSyncError: string;
}

EDIT2: 如果您正在{em>之前上对state进行验证,那么该验证将被传递到startDatabaseSyncronisation中,那么您为startDatabaseSyncronisation使用的键入从根本上是不正确的,并且您不能在两个函数之间直接共享interface DatabaseState。最好的办法是在两个共享元素中分别创建一个interface,然后在两个界面中创建extend

interface DatabaseMetaData {
    dbSyncStatus: number;
    dbSyncInfo: string;
    dbSyncError: string;
}

// for the function where it might be null
export interface DatabaseState extends DatabaseMetaData{
    db: PouchDB.Database | null;
    remoteDb: PouchDB.Database | null;
    dbReplication: PouchDB.Replication.Sync<{}> | null;
}

// for startDatabaseSyncronisation
export interface DatabaseSyncState extends DatabaseMetaData{
    db: PouchDB.Database;
    remoteDb: PouchDB.Database;
    dbReplication: PouchDB.Replication.Sync<{}>;
}

答案 2 :(得分:0)

@elderapo和@Andrew的答案很有用。

@elderapo的答案最直接地回答了我的初始查询,即“为什么打字稿不抱怨函数调用,而抱怨回调?”:

状态值可能会在if check和complate回调之间改变

@Andrew提请注意主要问题,在这种情况下使用null作为有效值可能不安全。

我强烈建议您始终考虑使用null。 null的概念被认为是一个错误,并且不保护自己免受该值的侵害会使您的代码不安全

尽管我不希望dbremoteDb的值在分派操作和回调之间恢复为空,但最好完全消除这种可能性。 缺少连接/与数据库的连接丢失由其他代码处理。

我现在不再只是简单地将类型设为null,而是通过类型转换将它们初始化为空对象。 因此,我不需要禁用strictNullChecks(我想要它们)。

新代码:

export interface DatabaseState {
  db: PouchDB.Database;
  remoteDb: PouchDB.Database;
  dbReplication: PouchDB.Replication.Sync<{}>;
  dbSyncStatus: number;
  dbSyncInfo: string;
  dbSyncError: string;
}

const state: DatabaseState = {
  db: {} as PouchDB.Database,
  remoteDb: {} as PouchDB.Database,
  dbReplication: {} as PouchDB.Replication.Sync<{}>,
  dbSyncStatus: SyncStatus.NONE,
  dbSyncInfo: "",
  dbSyncError: ""
};


async startDatabaseSyncronisation({ state, commit, dispatch }) {
  var opts = { live: true, retry: true };
  await PouchDB.replicate(state.remoteDb, state.db, opts).on(
    "complete",
    function(info) {
      let replication = PouchDB.sync(state.remoteDb, state.db, opts)
        .on("paused", function(err: {}) {
          dispatch("onSyncPaused", err);
        })
        .on("active", function() {
          dispatch("onSyncActive");
        })
        .on("denied", function(err: {}) {
          dispatch("onSyncError", err);
        })
        .on("error", function(err: {}) {
          dispatch("onSyncError", err);
        })
        .on("complete", function() {});
      commit(SET_DB_REPLICATION, replication);
    }
  );
},