在React中使用懒惰和悬念时,如何得出加载状态?

时间:2018-11-07 01:16:01

标签: reactjs

我正在尝试构建一个延迟加载选项卡组件,仅在单击该选项卡时才获取该选项卡的代码/捆绑包。我正在尝试使用lazy + suspense。我想为用户单击加载的选项卡设置动画/颜色,以指示该选项卡正在加载。我怎样才能做到最好?

这是我整理的一些示例代码。此处的错误是,在加载代码时,有时有时表头会被渲染两次。如何避免该问题并在新标签上显示加载状态。

import React, {lazy, Suspense, useState, useReducer} from "react";
import "./App.css";
import classNames from "classnames";
import Spinner from "./Spinner";

const Tokyo = lazy(() => {
  return import("./Tokyo");
});
const Mexico = lazy(() => {
  return import("./Mexico");
});
const London = lazy(() => {
  return import("./London");
});

const App = () => {
  const [_, setTab] = useState("Tokyo");
  return (
    <div className="App">
      <header className="App-header">
        <Tabs initialTab="Tokyo" onTabChange={setTab}>
          <Tab id="Tokyo" name="Tokyo">
            <Tokyo />
          </Tab>
          <Tab id="Mexico" name="Mexico">
            <Mexico />
          </Tab>
          <Tab id="London" name="London">
            <London />
          </Tab>
        </Tabs>
      </header>
    </div>
  );
};

const Tab = () => {
  return null;
};

function genClickLog(log, current) {
  const set = new Set([current]);
  const newLog = [current];
  log.forEach(l => {
    if (!set.has(l)) {
      log.push(l);
      newLog.push(l);
    }
  });
  return newLog;
}

function createSuspenseTree(targetTab, log, child, tabs, handleTabChange) {
  const head = log.shift();

  if (head !== targetTab) {
    console.warn(`expect ${head} to be ${targetTab}`);
  }
  let current = child;

  log.forEach(l => {
    current = (
      <Suspense
        fallback={
          <Fallback
            tabs={tabs}
            prevTab={l}
            activeTab={targetTab}
            onTabChange={handleTabChange}
          />
        }
      >
        {current}
      </Suspense>
    );
  });
  return <Suspense fallback={<Spinner />}>{current}</Suspense>;
}

function reducer(state, action) {
  switch (action.type) {
    case "change":
      if (state.current === action.id) {
        return state;
      }
      return {
        current: action.id,
        prev: state.current,
        clickLog: genClickLog(state.clickLog, action.id),
      };
    case "initial":
      return {
        current: action.id,
        prev: null,
        clickLog: [action.id],
      };
    default:
      throw new Error("bad reducer action");
  }
}

const Tabs = props => {
  const {children, onTabChange, initialTab} = props;
  const [state, dispatch] = useReducer(
    reducer,
    {
      clickLog: [],
      prev: null,
      current: null,
    },
    {type: "initial", id: initialTab}
  );

  const handleTabChange = tab => {
    dispatch({type: "change", id: tab});
    onTabChange(tab);
  };

  const tabs = React.Children.map(children, x => ({
    id: x.props.id,
    name: x.props.name,
    render: x.props.children,
  }));
  const child = (
    <>
      <TabHeader
        tabs={tabs}
        activeTab={state.current}
        onTabChange={handleTabChange}
      />
      {tabs.map(x => (
        <div key={x.id}>
          <TabFrag
            id={x.id}
            key={x.id}
            activeTab={state.current}
            render={x.render}
          />
        </div>
      ))}
    </>
  );

  return (
    <div className="TabContainer">
      {createSuspenseTree(
        state.current,
        [...state.clickLog],
        child,
        tabs,
        handleTabChange
      )}
    </div>
  );
};

const Fallback = props => {
  const {prevTab, activeTab, onTabChange, tabs} = props;
  if (prevTab && prevTab !== activeTab) {
    return (
      <>
        <TabHeader
          tabs={tabs}
          activeTab={prevTab}
          loadingTab={activeTab}
          onTabChange={onTabChange}
        />
        {tabs.map(x => (
          <div key={x.id}>
            <TabFrag
              id={x.id}
              key={x.id}
              activeTab={prevTab}
              render={x.render}
            />
          </div>
        ))}
      </>
    );
  }
  return <Spinner />;
};

const TabFrag = props => {
  if (props.id === props.activeTab) {
    return props.render;
  }
  return null;
};

const TabHeader = props => {
  const {tabs, activeTab, loadingTab, onTabChange} = props;

  return (
    <div className="TabHeader">
      {tabs.map(x => (
        <TabItem
          id={x.id}
          key={x.id}
          name={x.name}
          active={x.id === activeTab}
          loading={x.id === loadingTab}
          onTabChange={onTabChange}
        />
      ))}
    </div>
  );
};

const TabItem = props => {
  const {id, name, loading, active, onTabChange} = props;
  const handleTabChange = () => {
    onTabChange(id);
  };
  return (
    <div
      className={classNames("TabItem", {
        ActiveTab: active,
        LoadingTab: loading,
      })}
      onClick={handleTabChange}
    >
      {name}
    </div>
  );
};

0 个答案:

没有答案