【问题标题】:Synchronize Vuex store with server side in Nuxt.js在 Nuxt.js 中将 Vuex 存储与服务器端同步
【发布时间】:2021-11-19 05:20:24
【问题描述】:

问题

Nuxt 中间件下面

const inspectAuthentication: Middleware = async (): Promise<void> => {
  await AuthenticationService.getInstance().inspectAuthentication();
};

在返回每个页面的 HTML 之前在服务器端执行,并且检查是否已通过用户身份验证。如果是,则将 CurrentAuthenticatedUser 存储在 Vuex 模块中:

import {
  VuexModule,
  getModule as getVuexModule,
  Module as VuexModuleConfiguration,
  VuexAction,
  VuexMutation
} from "nuxt-property-decorator";


@VuexModuleConfiguration({
  name: "AuthenticationService",
  store,
  namespaced: true,
  stateFactory: true,
  dynamic: true
})
export default class AuthenticationService extends VuexModule {

  public static getInstance(): AuthenticationService {
    return getVuexModule(AuthenticationService);
  }


  private _currentAuthenticatedUser: CurrentAuthenticatedUser | null = null;

  public get currentAuthenticatedUser(): CurrentAuthenticatedUser | null {
    return this._currentAuthenticatedUser;
  }


  @VuexAction({ rawError: true })
  public async inspectAuthentication(): Promise<boolean> {

    // This condition is always falsy after page reloading
    if (this.isAuthenticationInspectionSuccessfullyComplete) {
      return isNotNull(this._currentAuthenticatedUser);
    }


    this.onAuthenticationInspectionStarted();

    // The is no local storage on server side; use @nuxtjs/universal-storage instead
    const accessToken: string | null = DependenciesInjector.universalStorageService.
        getItem(AuthenticationService.ACCESS_TOKEN_KEY_IN_LOCAL_STORAGE);

    if (isNull(accessToken)) {
      this.completeAuthenticationInspection();
      return false;
    }


    let currentAuthenticatedUser: CurrentAuthenticatedUser | null;

    try {

      currentAuthenticatedUser = await DependenciesInjector.gateways.authentication.getCurrentAuthenticatedUser(accessToken);

    } catch (error: unknown) {

      this.onAuthenticationInspectionFailed();
      // error wrapping / rethrowing
    }


    if (isNull(currentAuthenticatedUser)) {
      this.completeAuthenticationInspection();
      return false;
    }


    this.completeAuthenticationInspection(currentAuthenticatedUser);

    return true;
  }

  @VuexMutation
  private completeAuthenticationInspection(currentAuthenticatedUser?: CurrentAuthenticatedUser): void {

    if (isNotUndefined(currentAuthenticatedUser)) {
      this._currentAuthenticatedUser = currentAuthenticatedUser;
      DependenciesInjector.universalStorageService.setItem(
        AuthenticationService.ACCESS_TOKEN_KEY_IN_LOCAL_STORAGE, currentAuthenticatedUser.accessToken
      );
    }

    // ...
  }
}

上面的代码在服务器端运行良好,但是在客户端,如果要尝试获取AuthenticationService.getInstance().currentAuthenticatedUser,它将是null! 我预计 Nuxt.js 会将包括 AuthenticationService 在内的 Vuex 存储与服务器端同步,但是它没有。

目标

AuthenticationService必须与服务器端同步,所以如果用户已经通过身份验证,在客户端AuthenticationService.getInstance().currentAuthenticatedUser即使在页面重新加载后也必须是非空的。

服务器端不需要同步整个Vuex store(例如客户端只需要负责浮动通知栏的模块)但是如果没有开发选择性方法,至少同步整个Vuex store现在就够了。

请不要向我推荐用于身份验证的库或 Nuxt 模块,例如 Nuxt Auth module,因为这里我们讨论的是 Vuex 存储与服务器的同步,而不是用于身份验证的最佳 Nuxt 模块。此外,客户端和服务器之间的 vuex 存储同步不仅可以用于身份验证。

更新

preserveState解决方案尝试

很遗憾,

import { store } from "~/Store";
import { VuexModule, Module as VuexModuleConfiguration } from "nuxt-property-decorator";


@VuexModuleConfiguration({
  name: "AuthenticationService",
  store,
  namespaced: true,
  stateFactory: true,
  dynamic: true,
  preserveState: true /* New */
})
export default class AuthenticationService extends VuexModule {}

原因

Cannot read property '_currentAuthenticatedUser' of undefined

服务器端出错。

错误指的是

@VuexAction({ rawError: true })
public async inspectAuthentication(): Promise<boolean> {
  if (this.isAuthenticationInspectionSuccessfullyComplete) {
    // HERE ⇩
    return isNotNull(this._currentAuthenticatedUser);
  }
}

我检查了this 的值。这是一个大物体;我只留下值得注意的部分:

{                                                                                                                      
  store: Store {
    _committing: false,
    // === ✏ All actual action here
    _actions: [Object: null prototype] {
      'AuthenticationService/inspectAuthentication': [Array],
      'AuthenticationService/signIn': [Array],
      'AuthenticationService/applySignUp': [Array],
      // ...      

  // === ✏ Some mutations ...
  onAuthenticationInspectionStarted: [Function (anonymous)],
  completeAuthenticationInspection: [Function (anonymous)],
  // ...
  context: {
    dispatch: [Function (anonymous)],
    commit: [Function (anonymous)],
    getters: {
      currentAuthenticatedUser: [Getter],
      isAuthenticationInspectionSuccessfullyComplete: [Getter]
    },
    // === ✏ The state in undefined!
    state: undefined
  }
}

我想我需要告诉我如何初始化 vuex 存储。 动态模块的working Nuxt methodology 是:

// store/index.ts
import Vue from "vue";
import Vuex, { Store } from "vuex";


Vue.use(Vuex);

export const store: Store<unknown> = new Vuex.Store<unknown>({});

nuxtServerInit解决方案尝试

这里是另一个问题 - 如何在上面的存储初始化方法中集成nuxtServerInit?我想,要回答这个问题,需要 Vuex 和 vuex-module-decorators。在store/index.ts 下面,nuxtServerInit 甚至不会被调用:

import Vue from "vue";
import Vuex, { Store } from "vuex";


Vue.use(Vuex);

export const store: Store<unknown> = new Vuex.Store<unknown>({
  actions: {
    nuxtServerInit(blackbox: unknown): void {
      console.log("----------------");
      console.log(blackbox);
    }
  }
});

我把这个问题提取到other question

【问题讨论】:

    标签: nuxt.js vuex vuex-module-decorators


    【解决方案1】:

    这是使用 SSR 时的主要挑战之一。 因此,在从服务器接收到带有静态 HTML 的响应后,客户端会发生一个称为 Hydration 的过程。 (你可以阅读更多关于这个Vue SSR guide

    由于 Nuxt 的构建方式以及 SSR/Client 关系如何用于 hydration,可能发生的情况是您的服务器呈现应用的快照,但在客户端安装应用之前异步数据不可用,从而导致呈现不同的商店状态,打破水合作用。

    Nuxt 和 Next(用于 React)等事实框架为 Auth 实现了自己的组件,以及许多其他框架,是为了处理手动调解过程以实现正确的水合。

    因此,深入了解如何在不使用 Nuxt 内置身份验证模块的情况下解决该问题,您可能需要注意以下几点:

    1. serverPrefetch 方法将在服务器端调用,该方法将等到 promise 解决后再发送给客户端进行渲染
    2. 除了组件渲染之外,还有服务器发送给客户端的上下文,可以使用rendered 钩子注入,当应用程序完成渲染时调用它,所以是时候将你的商店状态发送回客户在水合过程中重复使用它
    3. 在商店本身,如果你使用registerModule,它支持一个属性preserveState,它负责保持服务器注入的状态。

    有关如何使用这些部分的示例,您可以查看this page上的代码

    最后,与您的用户身份验证挑战更相关,另一种选择是在存储操作上使用nuxtServerInit 来运行此身份验证处理,因为之后它将直接传递给客户端,如Nuxt docs 所述。

    更新

    same page 上,文档显示 nextServerInit 上的第一个参数是 context,这意味着您可以从那里获得 store

    还有一点需要提一下,在您最初的问题中,您已经提到您不想要第 3 方库,但您已经在使用一个会给表格带来很多复杂性的库,即nuxt-property-decorator。 因此,您不仅要处理与使用框架时一样复杂的 SSR,而且您使用的不是纯 Vue,而是 Next,而不是纯 TS Nuxt,而是使用 store 的装饰器增加了另一个复杂性。

    我为什么要提到它?因为快速浏览了 lib 问题,有other people with the same issue 没有正确访问this

    来自同时使用 Nuxt (Vue) 和 Next (React) 的人的背景,我对您的建议是在尝试许多不同的东西之前尝试降低复杂性。 因此,我会在没有此 nuxt-property-decorator 的情况下测试运行您的应用程序,以检查这是否适用于开箱即用的商店实现,确保它不是在未完全准备好支持 SSR 复杂性的库上引起的错误。

    【讨论】:

    • 感谢您的回答!因为无论特定组件如何,此机制都必须起作用,所以 3rd 解决方案和 last 解决方案可能是合适的。我都试过了,但发生了更多相关的问题。我可以请您检查问题的“更新”部分吗?
    • @TakeshiTokugawaYD,请查看“更新”部分,看看是否有帮助
    • 我们来做个判断吧。您已经解释了为什么会出现这个问题以及常见的解决方法。这就是为什么我接受了你的回答并投了赞成票。问题归结为 nuxt-property-decorator/vuex-module-decorators 问题,将在How to make visible the "nuxtServerInit" action for Nuxt.js action in the case with dynamic modules only? 中讨论。再次感谢您的回答!
    • 很高兴帮助@TakeshiTokugawaYD! :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-20
    • 2021-06-14
    • 2019-04-04
    • 1970-01-01
    • 2021-03-11
    • 2021-06-26
    • 1970-01-01
    相关资源
    最近更新 更多