React 앱을 개별적으로 새로고침

단일 페이지 앱에 도입된 장애물 중 하나는 사용자가 최신 배포 코드로 업데이트하지 않고도 훨씬 더 오래 사용할 수 있다는 것입니다. 이것은 사용자 정의 React 설정뿐만 아니라 Next.js 과 같은 더 독단적인 옵션에도 영향을 미칩니다. 완벽한 세상에서 API는 이전 버전과 호환되어야 하며 무언가를 놓쳤을 때 정상적으로 실패해야 하지만 며칠 된 클라이언트 번들을 사용하는 사용자가 문제에 부딪힐 가능성이 더 높다는 것은 의심의 여지가 없습니다. 다행히도 사용자가 현명하지 않은 상태에서 클라이언트 앱을 업데이트할 수 있는 쉬운 방법이 있습니다. React 및 React Router를 사용하여 예제를 빌드하지만 개념은 모든 클라이언트 JavaScript 프레임워크에 적용됩니다.

링크 및 앵커



사용자가 새 JavaScript를 받지 않고도 훨씬 더 오래 실행되는 세션을 가질 수 있는 주된 이유는 단일 페이지 애플리케이션의 특성 때문입니다. 단일 페이지 응용 프로그램은 종종 client-side routing 을 활용합니다. 즉, 전체 페이지가 새로 고쳐지지 않습니다. 대신 앱이 다음 페이지에 필요한 데이터를 가져오고 전체 HTML을 요청하지 않고 수동으로 브라우저 기록을 조작합니다. 우리는 클라이언트 측 라우팅을 사용할 수 없었지만 기능이 풍부한 이러한 웹 애플리케이션과 관련된 속도를 많이 잃게 될 것입니다. 필요할 때만 네이티브 앵커로 대체할 수 있다면 어떨까요?

function SuperLink({ href, ...other }) {
  const { shouldUseAnchor } = useSomeFunction();

  if (shouldUseAnchor) {
    return <a href={href} {...other} />;
  }

  // a React Router <Link />
  return <Link to={href} {...other} />;
}


이 코드는 유망해 보입니다. 그러나 렌더링할 링크 유형을 결정하기 위해 어떻게 계산shouldUseAnchor할 수 있습니까?

자식.txt



한 가지 간단한 옵션은 소스 코드에서 생성된 Git 해시가 있는 텍스트 파일을 노출하는 것입니다. 글꼴과 가능한 이미지(예: /static )를 노출할 때마다 빌드 시 git.txt를 배치할 수 있습니다.

{
  "git:generate-hash": "git ls-files -s src/ | git hash-object --stdin > static/git.txt"
}


빌드 명령의 일부로 && npm run git:generate-hash를 호출하여 공개적으로 액세스할 수 있는 디렉토리에 배치합니다. 지금 해야 할 일은 고정된 간격으로 이 파일을 폴링하여 새 업데이트를 확인하고 SuperLink 구성 요소를 업데이트하기만 하면 됩니다.

GitHash제공자



모든 페이지에는 여러 개의 링크가 있을 수 있습니다. 각 인스턴스가 해시 파일을 폴링하도록 하는 것은 실수입니다. 대신 SuperLink의 모든 인스턴스에서 사용할 수 있도록 앱을 React context 공급자로 래핑합니다.

import * as React from 'react';

// Some boilerplate to prepare our Context
const GitHashContext = React.createContext({
  hash: '',
  hasUpdated: false
});

// Setup our hook that we'll use in `SuperLink`
export const useGitHash = () => React.useContext(GitHashContext);

// Function used to actually fetch the Git hash
const TEN_MINUTES_IN_MS = 60000 * 10;
async function fetchGitHash() {
  let gitHash = '';

  try {
    const result = await fetch('/static/git.txt');
    gitHash = await result.text();
  } catch (error) {
    console.error(error);
  }

  return gitHash;
}

// The provider we'll wrap around our app and fetch the Git hash
// on an interval
export const GitHashProvider = ({ children }) => {
  const [state, setState] = React.useState({ hasUpdated: false, hash: '' });

  const updateGitVersion = React.useCallback(async () => {
    const hash = await fetchGitHash();

    if (hash) {
      setState((prevState) => ({
        hash,
        hasUpdated: !!prevState.hash && prevState.hash !== hash
      }));
    }
  }, []);

  React.useEffect(() => {
    const interval = setInterval(() => {
      updateGitVersion();
    }, TEN_MINUTES_IN_MS);

    return () => clearInterval(interval);
  }, [updateGitVersion]);

  return (
    <GitHashContext.Provider value={state}>{children}<GitHashContext.Provider>
  );
};


꽤 많은 코드이므로 살펴보겠습니다. 컨텍스트에 대한 상용구와 해당 데이터에 대한 액세스를 제공할 후크를 정의합니다( GitHashContextuseGitHash ). 다음으로, git.txt를 쿼리하고 해시를 추출하는 가져오기 주위에 간단한 래퍼를 정의합니다.

논리의 고기는 GitHashProvider에 있으며 나쁘지 않습니다. 상태를 정의하고 10분마다 실행되는 간격을 시작하고 최신 Git 해시를 가져옵니다. 이전에 이미 Git 해시를 저장했고 최신 해시와 다른 경우 hasUpdatedtrue로 설정합니다. 나중에 비교할 수 있도록 이전 해시를 추적합니다. 이제 SuperLink에서 사용할 준비가 되었습니다!

function SuperLink({ href, ...other }) {
  const { hasUpdated: hasGitHashUpdated } = useGitHash();

  if (hasGitHashUpdated) {
    return <a href={href} {...other} />;
  }

  // a React Router <Link />
  return <Link to={href} {...other} />;
}


사용 시기



응용 프로그램에 따라 새 제품SuperLink을 사용하려는 위치가 변경될 수 있습니다. 개인적으로 헤더의 링크는 거의 항상 좋은 후보라고 생각합니다. 최종 사용자로서 흐름을 상상해 봅시다. 밤새도록 탭을 열어두고 SomeCoolWebApp.xyz 로 돌아갑니다. 우리에게 알려지지 않은 개발자는 이러한 "스마트"링크 중 하나를 클릭하면 수신하게 될 코드에 정말 중요한 버그 수정을 배포했습니다. 사용자는 내비게이션에서 전체 페이지가 로드될 때 빠른 깜박임을 알아차릴 수 있지만 실제로 눈에 띄지 않을 정도로 드물게 발생해야 합니다.

좋은 웹페이지 즐겨찾기