web-dev-qa-db-ja.com

React Router v4-コンポーネントを切り替えるときにスクロール位置を維持する

私は2つを持っています <Route>sはreact-routerで作成されました。

  • / cards->カードゲームのリスト
  • / cards/1->カードゲームの詳細#1

ユーザーが[リストに戻る]をクリックすると、ユーザーがリストのどこにいたかスクロールします。

これどうやってするの?

8
Sancho

codesandboxでの作業例

React Router v4は、すぐにスクロール復元をサポートしません。現在のところ、どちらもサポートしていません。セクションの React Router V4-Scroll Restoration のドキュメントで、詳細を読むことができます。

したがって、これをサポートするロジックを作成するのはすべての開発者次第ですが、この機能を実現するためのツールはいくつかあります。

element.scrollIntoView()

.scrollIntoView()は要素に対して呼び出すことができ、ご想像のとおり、スクロールして表示します。サポートは非​​常に良好で、現在、ブラウザの97%がサポートしています。 ソース:icanuse

_<Link />_コンポーネントは状態を渡すことができます

React RouterのLinkコンポーネントには、文字列の代わりにオブジェクトを提供できるtoプロパティがあります。これがだれだ。

_<Link to={{ pathname: '/card', state: 9 }}>Card nine</Link>
_

状態を使用して、レンダリングされるコンポーネントに情報を渡すことができます。この例では、状態に番号が割り当てられます。これは質問に答えるのに十分です。後で表示しますが、それは何でもかまいません。 _/card_をレンダリングするルート_<Card />_は、props.location.stateで変数stateにアクセスできるようになり、使用できます希望通り。

各リストアイテムのラベル付け

さまざまなカードをレンダリングするときに、それぞれに一意のクラスを追加します。これにより、渡すことができる識別子が得られ、カードリストの概要に戻るときにこのアイテムをスクロールして表示する必要があることがわかります。

解決

  1. _<Cards />_はリストをレンダリングします。各アイテムは一意のクラスを持ちます。
  2. アイテムがクリックされると、_Link />_は一意の識別子を_<Card />_に渡します。
  3. _<Card />_は、カードの詳細と一意の識別子を持つ戻るボタンをレンダリングします。
  4. ボタンがクリックされ、_<Cards />_がマウントされると、.scrollIntoView()は、_props.location.state_のデータを使用して以前にクリックされたアイテムにスクロールします。

以下は、さまざまな部分のコードスニペットです。

_// Cards component displaying the list of available cards.
// Link's to prop is passed an object where state is set to the unique id.
class Cards extends React.Component {
  componentDidMount() {
    const item = document.querySelector(
      ".restore-" + this.props.location.state
    );
    if (item) {
      item.scrollIntoView();
    }
  }

  render() {
    const cardKeys = Object.keys(cardData);
    return (
      <ul className="scroll-list">
        {cardKeys.map(id => {
          return (
            <Link
              to={{ pathname: `/cards/${id}`, state: id }}
              className={`card-wrapper restore-${id}`}
            >
              {cardData[id].name}
            </Link>
          );
        })}
      </ul>
    );
  }
}

// Card compoment. Link compoment passes state back to cards compoment
const Card = props => {
  const { id } = props.match.params;
  return (
    <div className="card-details">
      <h2>{cardData[id].name}</h2>
      <img alt={cardData[id].name} src={cardData[id].image} />
      <p>
        {cardData[id].description}&nbsp;<a href={cardData[id].url}>More...</a>
      </p>
      <Link
        to={{
          pathname: "/cards",
          state: props.location.state
        }}
      >
        <button>Return to list</button>
      </Link>
    </div>
  );
};

// App router compoment.
function App() {
  return (
    <div className="App">
      <Router>
        <div>
          <Route exact path="/cards" component={Cards} />
          <Route path="/cards/:id" component={Card} />
        </div>
      </Router>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);_
12
Roy Scheffers

これに対するもう1つの可能な解決策は、/cards/:id全画面モーダルとしてルーティングし、/cards背後にマウントされたルート

1
Jon Wyatt

Reduxを使用したfull implementationの場合、これは CodeSandbox で確認できます。

履歴APIを利用してこれを行いました。

  1. ルート変更後のスクロール位置を保存します。

  2. ユーザーが戻るボタンをクリックしたときにスクロール位置を復元します。

スクロール位置をgetSnapshotBeforeUpdateに保存し、componentDidUpdateに復元します。

  // Saving scroll position.
  getSnapshotBeforeUpdate(prevProps) {
    const {
      history: { action },
      location: { pathname }
    } = prevProps;

    if (action !== "POP") {
      scrollData = { ...scrollData, [pathname]: window.pageYOffset };
    }

    return null;
  }

  // Restore scroll position.
  componentDidUpdate() {
    const {
      history: { action },
      location: { pathname }
    } = this.props;

    if (action === "POP") {
      if (scrollData[pathname]) {
        setTimeout(() =>
          window.scrollTo({
            left: 0,
            top: scrollData[pathname],
            behavior: "smooth"
          })
        );
      } else {
        setTimeout(window.scrollTo({ left: 0, top: 0 }));
      }
    } else {
      setTimeout(window.scrollTo({ left: 0, top: 0 }));
    }
  }
0
Agus Syahputra