reactjs - 组件的 NextJS/Script — defer Stripe

标签 reactjs next.js stripe-payments deferred-loading

我是第一次使用 NextJS,它构建网站的速度给我留下了难以置信的印象……直到我添加了 Stripe Elements。

在添加 Stripe 之前,我的 pagespeed 移动得分范围为 93-99。后来就50左右了。:(

我试过了,按照this dev.to article ,使用 /pure 导入 Stripe导入路径:

import { loadStripe } from '@stripe/stripe-js/pure';

我不确定它的作用,因为它仍然将外部 Stripe 脚本的链接放在 <head> 中。 ,并且不添加任何附加标签:

<script src="https://js.stripe.com/v3"></script>

尽管如此,它似乎确实起到了一些作用,因为页面速度略有提高,达到 58-63 范围 - 但这仍然是 Not Acceptable 。

这一切都是通过 Stripe React 库完成的,所以它的实现看起来像这样:

import React, { useEffect, useState } from 'react';
import { Elements } from '@stripe/react-stripe-js';

import ElementForm from './ElementForm';
import getStripe from '../lib/get-stripejs';

const PurchaseSection = () => {
  const [ stripePromise, setStripePromise ] = useState(null);
  const [ clientSecret, setClientSecret ] = useState('');

useEffect(() => {
    fetch('api/keys', {
        method: 'GET',
        headers: { 'Content-Type': 'application/json' },
    })
    .then((res) => res.json())
    .then((data) => {
        setStripePromise(getStripe(data.publishableKey));
    });
  }, []);

  useEffect(() => {
    fetch('api/create-payment-intent', {
      method: 'POST',
      header: { 'Content-Type': 'applcation/json' },
      body: JSON.stringify({
        productId: '34032255',
        productType: 'seminar'
      })
    })
    .then(async (res) => {
      const { clientSecret } = await res.json();
      setClientSecret(clientSecret);
    });
  }, [])

  return (  
    <section>
      {stripePromise && clientSecret && (
        <Elements stripe={stripePromise} options={{ clientSecret }}>
          <ElementForm />
        </Elements>
      )}
    </section>
  )
}

lib/get-stripejs

import { loadStripe } from '@stripe/stripe-js/pure';

let stripePromise;

const getStripe = (publishableKey) => {
    if (!stripePromise) {
        stripePromise = loadStripe(publishableKey);
    }
    return stripePromise;
}
 
export default getStripe

元素表单

import React, { useState } from "react";
import { PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js';


function ElementForm({ paymentIntent = null }) {
    const stripe = useStripe();
    const elements = useElements();

    const [ isProcessing, setIsProcessing ] = useState(false);

    async function handleSubmit(e) {
        // do stuff
    }

    return (
      <form id='payment-form' onSubmit={handleSubmit}>
        <div id='payment-element'>
          <PaymentElement />
        </div>
        <button disabled={isProcessing} type='submit'>
          {isProcessing ? 'Processing...' : 'Submit'}
        </button>
      </form>
    );
}

export default ElementForm

我不确定问题是否出在 <PaymentElement>或其他loadStripe确实如此,或两者兼而有之,但我的想法是我想做类似 next/script 的事情提供,至少尝试一下 the various strategies 。问题是,这似乎只适用于来自 src 的项目。 (我猜这最终是这样,但这不是代码中的实现方式,因为使用了各种 Stripe 包)。

那么,a) 有什么方法可以让我应用 next/script策略直接针对组件,而不是远程脚本?

或者,b) 更广泛地说,延迟加载所有这些 Stripe 内容的“nextjs”神奇方法是什么? (注意: <PurchaseSection> 组件直到远低于折叠才会出现,因此没有理由提前加载或以任何形式的阻塞方式加载。)

最佳答案

根据 @juliomalves 的评论,我去玩了 next/dynamicIntersectionObserver。从本质上讲,您必须在此处进行用户体验权衡。

推迟 Elements 的加载,直到它进入视口(viewport),将 PageSpeed 指标提高到 80,这更好,但不是很好。低分主要是由于交互时间为 5 秒造成的。

除此之外,如果我推迟 Stripe 库本身的加载,则 PageSpeed 会跳回到 90 年代中期……但是当用户向下滚动到他们想要输入付款信息的位置时并购买,在表格显示之前会有 5 秒的延迟。

老实说,我不确定哪种体验更糟糕,哪种体验会导致更多的流失,因此您可能需要进行自己的实验。

延迟加载元素

我将 IntersectionObserver 放入自定义 Hook 中,因为我已经沿着这条路走下去了,所以我确信我会在其他地方使用它:

utils/showOnScreen.js

import React, { useEffect, useState} from 'react';

const showOnScreen = (ref) => {
    const [ isIntersecting, setIsIntersecting ] = useState(false);

    useEffect(() => {
        const observer = new IntersectionObserver(
            ([entry]) => setIsIntersecting(entry.isIntersecting)
        );

        if (ref.current) {
            observer.observe(ref.current);
        }
    }, []);

    return isIntersecting;
}

export default showOnScreen

组件/PurchaseSection.js

import React, { useEffect, useRef, useState } from 'react';
import dynamic from 'next/dynamic';
import { Elements } from '@stripe/react-stripe-js';
import getStripe from '../lib/get-stripejs';
import showOnScreen from '../utils/showOnScreen';

// We're making the `ElementForm` dynamic, but not `Elements`
// itself, as the latter is not a renderable component, but 
// just a wrapper to pass state down to child components.
const ElementForm = dynamic(() => import('./ElementForm'));

const PurchaseSection = () => {
  const purchaseSectionRef = useRef();
  const purchaseSectionRefValue = showOnScreen(purchaseSectionRef);
  // we're keeping track of this state to deal with `IntersectionObserver`
  // transitions if the user scrolls (e.g., false, true, false, true).
  // An alternative would be to remove the observer once it flips to 
  // true.
  const [ isPurchaseSectionRef, setIsPurchaseSectionRef ] = useState(false);

  useEffect(() => {
    // if we've ever seen the section before, don't change anything,
    // so we don't re-render
    if (!isPurchaseSectionRef) {
      setIsPurchaseSectionRef(purchaseSectionRefValue);
}, [purchaseSectionRefValue]);

...

  return (  
    <section>
      { isPurchaseSectionRef && <div> { /* only true when we've intersected at least once */ }
        {stripePromise && clientSecret && (
          <Elements stripe={stripePromise} options={{ clientSecret }}>
            <ElementForm />
          </Elements>
        )}
      </div> }
    </section>
  )
}

延迟 Stripe 库

如果您还想推迟库本身的加载,以便在用户到达付款部分时获得更快的页面加载速度,但较慢的表单加载速度,则需要将 Stripe API 调用拉入新的库中useEffect,并延迟加载getStripe:

import React, { useEffect, useRef, useState } from 'react';
import dynamic from 'next/dynamic';
import { Elements } from '@stripe/react-stripe-js';
// import getStripe from '../lib/get-stripejs';
import showOnScreen from '../utils/showOnScreen';

...

  useEffect(() => {
    // if we've ever seen the section before, don't change anything, so we don't rerender
    if (!isPurchaseSectionRef) {
      setIsPurchaseSectionRef(purchaseSectionRefValue);

      // only do Stripe interactions if we've intersected
      // this functionality used to be in `useEffect(() => {...}, [])`
      if (purchaseSectionRefValue) {
        fetch('api/keys', {
            method: 'GET',
            headers: { 'Content-Type': 'application/json' },
        })
        .then((res) => res.json())
        .then(async (data) => {
            const getStripe = (await import('../lib/get-stripejs')).default; // <- getStripe deferred until here
            setStripePromise(getStripe(data.publishableKey));
        })

        fetch('api/create-payment-intent', {
          method: 'POST',
          header: { 'Content-Type': 'applcation/json' },
          body: JSON.stringify({
            productId: '34032255',
            productType: 'seminar'
          })
        })
        .then(async (res) => {
          const { clientSecret } = await res.json();
          setClientSecret(clientSecret);
        })
      }
    }
  }, [purchaseSectionRefValue]);

  ...

关于reactjs - 组件的 NextJS/Script — defer Stripe,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74093819/

相关文章:

javascript - React JS this.state未定义: Cannot read property 'components' of undefined,,其中组件是 "Board"组件的状态

javascript - 我可以告诉 useMemo 跳过其依赖项数组中的空值吗?

node.js - 如何使用Stripe NodeJS SDK获取更多客户源?

node.js - React + Material-UI - 警告 : Prop className did not match

javascript - 无法 POST/付款(Node.js 和 Stripe)

java - 使用 Stripe 时 Maven 编译错误 : Cannot find symbol APIException, APIConnectionException

reactjs - 自定义历史记录不起作用的 react 路由器

javascript - 使用 React 的天气应用程序 : problems inputing custom Lat/Long into API request

javascript - 在 useEffect 内部或外部定义函数?

reactjs - regeneratorRuntime 未定义 rete.js