使用 Next Js Link 时更新 React 类组件

Posted

技术标签:

【中文标题】使用 Next Js Link 时更新 React 类组件【英文标题】:Update a React class component when Next Js Link used 【发布时间】:2021-11-01 20:49:38 【问题描述】:

原帖

我有一个用于 Next/JS 动态路径的 React 类组件。在初始加载时一切都很好。但是有一个边缘用例,如果我在已经安装了该组件的页面上,然后单击与组件路径相同的动态基础但具有不同参数的Link,则该组件不会unmountgetIntialProps 运行但它不调用构造函数来更新新状态。

路径示例:

动态模式

/vos/[id]/[slug]

初始路径

/vos/203dk2-d33d-3e3e3d/thisName

新路径

/vos/554-34r4f-44d4e/aNewName

活动

1. component loads with initial props/state
2. user clicks Link to same component path but updated params
3. component lifecycles run to reset state but does not and does not unmount
4. 2-5 seconds pass... no activity
5. getIntialProps finally runs with new params
6. componentDidUpdate lifecycle called to update state with new props

我也尝试将Link 更改为Router.push(),但结果相同。

问题:

    有没有办法强制卸载组件以允许创建它的新实例。 如果不是上述情况,在组件生命周期中处理这种边缘情况的最佳方法是什么?我尝试使用 componentDidUpdate() 循环中的函数更新状态,但这会有点混乱,因为它在调用 s-s-r 之前运行,因此状态管理不同步。

代码示例

static getInitialProps = async (ctx: NextPageContext) => 

    const services = VerifiedOrganizationProfilePage.PageServices(ctx);

    const  lang: language, id: voId  = ctx.query as IProfilePagesQueryParams;

    // check VO page is existing
    // if VO owner or Admin
    let verifiedOrganization: IVerifiedOrganizationResponse | undefined;
    let user: IUserResponse | undefined;
    let isVoOwner: boolean = false;
    let isPlatformAdmin: boolean = false;
    let isVoOwnerOrPlatformAdmin: boolean = false;
    try 
      verifiedOrganization = await services.verifiedOrganizationService.getVerifiedOrganization(voId);

      if (!verifiedOrganization) throw new Error('No verified organization with that id was found!');

      const userId = await services.cognitoIdentityService.getUserIdInSession();
      if (userId) 
        user = await services.usersService.getUser(userId);

        isPlatformAdmin = AuthUtil.hasRoles(
          [ERole.PLATFORM_ADMIN],
          user.platformRoles
        );
        isVoOwner = OrganizationUtil.isVerifiedOrganizationOwner(
          verifiedOrganization.id,
          user
        );
        isVoOwnerOrPlatformAdmin = isVoOwner || isPlatformAdmin;
      
     catch (error) 
      NextUtil.redirectTo(
        '/not-found',
        ctx.res,
        HTTP_REDIRECT.TEMPORARY,
        language
      );
    
    // fetch publicly visible data
    const  store  = ctx;

    store.dispatch(fetchCampaignsRequest(
      verified_organization_id: voId,
      limit: isVoOwnerOrPlatformAdmin ? EPaginationLimit.FIVE_HUNDRED : EPaginationLimit.DEFAULT,
    , ctx));

    store.dispatch(fetchCausesRequest(
      verified_organization_id: voId,
      limit: EPaginationLimit.DEFAULT
    , ctx));

    store.dispatch(fetchCommentsRequest(
      verified_organization_id: voId,
      limit: EPaginationLimit.DEFAULT
    , ctx));

    store.dispatch(fetchUpdatesRequest(
      verified_organization_id: voId,
      limit: EPaginationLimit.DEFAULT
    , ctx));

    // wait for redux saga updating state
    await new Promise<void>((resolve) => 
      const unsubscribe = store.subscribe(() => 
        const state = store.getState();
        if (!state.campaign.isFetching && !state.cause.isFetching && !state.comment.isFetching && !state.update.isFetching) 
          unsubscribe();
          resolve();
        
      );
    );

    return 
      user,
      voId,
      isVoOwner,
      isPlatformAdmin,
      verifiedOrganization,
      isVoOwnerOrPlatformAdmin,
      tabValue: EVerifiedOrganizationProfilePageTabs.CAMPAIGNS,
      pageUrl: NextUtil.getPageUrl(ctx),
    ;
  
...
constructor(props)

  super(props);
  this.state = ...this.props


...
// used to check new props from VO if coming from another VO page and set the state
  static async getDerivedStateFromProps(nextProps: IVerifiedOrganizationProfilePageProps, prevState: IVerifiedOrganizationProfilePageState) 
    if (nextProps.voId !== prevState.voId) 
      return 
        voId: nextProps.voId,
        urlChanged: true,
        tabValue: EVerifiedOrganizationProfilePageTabs.CAMPAIGNS,
        isWaitingAdminApproval: false,
        isUserBothVoAndIpRepresentative: false,
        visibleBeneficiaryList: listResponse,
        beneficiaryGroups: listResponse,
        followingVerifiedOrganizations: ,
        beneficiaryBlockchainCSVData: undefined,
        userRating: undefined,
        isLoading: true,
      ;
    
  
...
async componentDidMount() 
    Router.events.on('routeChangeStart', this.handleRouteChangeComplete); // to trigger callback beofre NEXT Router/Link executes
    await this.fetchPersonalData(); // method to fetch user specific data


...
async componentDidUpdate() 
    if (this.state.urlChanged) 
      await this.fetchPersonalData();
    
  
...
componentWillUnmount() 
    Router.events.off('routeChangeStart', this.handleRouteChangeComplete);

...
// sets the current open tab to CAMPAIGNS if a VO navigates to a connected VO profile from a restricted tab
  public handleRouteChangeComplete = async (url: string) => 
    this.setState(tabValue: EVerifiedOrganizationProfilePageTabs.CAMPAIGNS,);
  
...
public fetchPersonalData = async () => 
    const  voId, user, verifiedOrganization, isPlatformAdmin, isVoOwnerOrPlatformAdmin  = this.props;
    let isVoRepresentative: boolean = false;
    let isIpRepresentative: boolean = false;
    let isUserBothVoAndIpRepresentative: boolean = false;
    let isWaitingAdminApproval: boolean = false;
    let visibleBeneficiaryList: IListResponse<IBeneficiaryWithInvitationStatus> | undefined;
    let beneficiaryGroups: IListResponse<IGroup> | undefined;
    try 
      const services = VerifiedOrganizationProfilePage.PageServices();

      if (user) 

        isWaitingAdminApproval = verifiedOrganization.verifiedOrganizationStatus === EVerifiedOrganizationStatus.PENDING_PLATFORM_ADMIN_APPROVAL;

        // If Verified Organization is waiting for Admin Platform approval, only Platform Admin can see the page.
        if (isWaitingAdminApproval && !isPlatformAdmin) 
          throw new NotFoundError();
        

        isVoRepresentative = AuthUtil.hasRoles(
          [ERole.VERIFIED_ORGANIZATION_REPRESENTATIVE],
          user.platformRoles
        );
        isIpRepresentative = AuthUtil.hasRoles(
          [ERole.IMPLEMENTING_PARTNER_REPRESENTATIVE],
          user.platformRoles
        );
        isUserBothVoAndIpRepresentative =
          isVoRepresentative && isIpRepresentative;
        // If Verified Organization is waiting for Admin Platform approval, only Platform Admin can see the page.
        if (isWaitingAdminApproval && !isPlatformAdmin) 
          throw new NotFoundError();
        

        // add the prefix to the id so we can match the record in the Connections table.
        const prefixedId = EIdTypes.VERIFIED_ORGANIZATION.toUpperCase() + '#' + verifiedOrganization.id;

        // Fetch  data visible only to VoOwner and Aidonic
        const connections = [] as unknown as IListResponse<IConnectionVOIP>;

        if (isVoOwnerOrPlatformAdmin) 
          // Get from the API all the connections sent or received

          // Commenting this out as it calling twice the API. The call to the API is done from the Tab instead.
          // connections = await services.connectionsService.getVisibleConnectionsByOrganization(prefixedId);

          visibleBeneficiaryList = await services.beneficiaryService.getBeneficiariesVisibleToOrganization(prefixedId);
          beneficiaryGroups = await services.beneficiaryGroupsService.getBeneficiaryGroupsList(prefixedId, limit: EPaginationLimit.THIRTY);
        
        const follows = await services.followsService.getFollowsList(
          user_id: user.id
        );

        const [followingVerifiedOrganizations] = mapFollowsByKey(follows, [
          'verifiedOrganizationId'
        ]);

        const userRating = await services.ratingsService.getRatingList(
          user_id: user.id,
          verified_organization_id: verifiedOrganization.id
        );

        this.setState(
          voId,
          connections,
          tabValue: EVerifiedOrganizationProfilePageTabs.CAMPAIGNS,
          beneficiaryGroups,
          isWaitingAdminApproval,
          visibleBeneficiaryList,
          followingVerifiedOrganizations,
          isUserBothVoAndIpRepresentative,
          userRating: userRating && userRating[0],
          isLoading: false,
          urlChanged: false
        );
      
     catch (e) 
      console.log('Error in data fetching on VO profile page: ', e);
    
  

更新

我已将道具从状态中分离出来以维持一个真实来源,并使用getDerivedStateFromProps() 来捕捉变化并调用fetchPersonalData()。一切正常。

唯一的问题是加载新更新的道具/状态所需的时间似乎是初始加载的两倍。想法?

【问题讨论】:

“加载新更新的道具/状态的时间似乎是初始加载的两倍” - 您在运行生产构建时是否遇到相同的行为 (next build &amp;&amp; next start)? @juliomalves 我是。比首次安装组件时快一点,但仍然明显更长。我认为正在发生的是,当路由更改时,组件从getIntialProps()(从不unmounts)“重新加载”,一切都很好。但在getInitialProps() 的回归和comonentDidUpdate() 生命周期之间,存在一个瓶颈。新的 props 被传入,但 state 没有那么快更新。我被难住了。 您能显示getInitialProps fetchPersonalDatahandleRouteChangeComplete 的代码吗?有哪些耗时的操作? @juliomalves 我肯定会把它贴在上面。 getInitialProps() 有几个 redux 调用,但似乎都正常执行。在他们被退回之后,瓶颈发生了。 我想我把范围缩小到fetchPersonalData()中的两个API调用 【参考方案1】:

解决方案: 就我而言,这是由生命周期中的 API 调用引起的。框架运行良好。

【讨论】:

以上是关于使用 Next Js Link 时更新 React 类组件的主要内容,如果未能解决你的问题,请参考以下文章

在 Next.js 中,如何使用来自 getServerSideProps 的数据更新 React Context 状态?

Next.js:错误:React.Children.only 预期接收单个 React 元素子项

无法在 react/next js 中获取数据,尽管能够控制台记录它

使用 React 和 Next.js 构建博客

使用动态路由、下一个 JS 和 useEffect 在 React 中获取数据

Next.js 具有 React 钩子和 localStorage 的持久状态