如何在返回下一个 js 时更改滚动行为?

IT技术 javascript reactjs next.js
2021-05-06 07:44:10

我在索引 js 中获取一个帖子列表,如下所示:

const Index = (props) => {
    return (
        <div>
            {props.posts.map((each) => {
                return (
                    <Link scroll={false} as={`/post/${each.id}`} href="/post/[id]" key={each.id}>
                        <a>
                            <h1>{each.title}</h1>
                        </a>
                    </Link>
                );
            })}
        </div>
    );
};

export async function getStaticProps() {
    const url = `https://jsonplaceholder.typicode.com/posts`;
    const res = await axios.get(url);

    return {
        props: { posts: res.data }
    };
}

当用户点击任何链接时,它会转到发布页面,即:

function post({ post }) {
    return (
            <h1>{post.id}</h1>
    );
}

export async function getServerSideProps({ query }) {
    const { id } = query;
    const res = await Axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`);

    return {
        props: { post: res.data}
    };
}

问题是当我点击回滚位置重置为顶部,它获取所有帖子。我包含scroll={false}在 Link 中,但它不起作用。

当用户从帖子页面单击返回时,如何防止滚动重置?

3个回答

scroll={false}不保持上一页的滚动;它不会改变在所有的滚动,这意味着滚动将是网页的要链接您可以使用scroll={false}覆盖将 scrollY 设置为 0 的默认行为,以便您可以实现自己的行为。

这是我实现恢复滚动位置的方法。这与 Max william 的回答非常相似,但使用的是useRef代替useState我们使用useRef,而不是useState因为useRef不会导致重新渲染每当它的value突变,不像useState每次用户更改滚动时,我们都会将值更新为当前滚动位置,如果我们要使用 useState,这将意味着大量无用的重新渲染。

首先,定义一个 UserContext 组件,以便轻松地将滚动数据从 _app.js 组件传递到您需要的任何位置:

import { createContext } from 'react';

const UserContext = createContext();

export default UserContext;

然后,在您的 _app.js 组件中,使用 UserContext 包装您的页面并创建一个 useRef 属性来存储滚动位置。

import { useRef } from 'react';
import UserContext from '../components/context'

function MyApp({ Component, pageProps }) {

  const scrollRef = useRef({
      scrollPos: 0
  });
    
  return (
      <Layout>
          <UserContext.Provider value={{ scrollRef: scrollRef }}>
              <Component {...pageProps} />
          </UserContext.Provider>
      </Layout>
  )
}

export default MyApp

然后在您想要恢复滚动位置的任何页面组件(即您要返回并看到与您离开时相同的滚动位置的页面)中,您可以放置​​此代码来设置页面的滚动位置并将滚动事件绑定到函数以更新存储的滚动位置。

import UserContext from '../components/context'
import { useContext } from 'react';

export default function YourPageComponent() {

  const { scrollRef } = useContext(UserContext);

  React.useEffect(() => {
    
    //called when the component has been mounted, sets the scroll to the currently stored scroll position
    window.scrollTo(0, scrollRef.current.scrollPos);

    const handleScrollPos = () => {
      //every time the window is scrolled, update the reference. This will not cause a re-render, meaning smooth uninterrupted scrolling.
      scrollRef.current.scrollPos = window.scrollY
    };

    window.addEventListener('scroll', handleScrollPos);

    return () => {
      //remove event listener on unmount
      window.removeEventListener('scroll', handleScrollPos);
    };
  });

  return (
    //your content
  )
}

最后一件小事是scroll={false}在链接回 YourPageComponent 的 Link 组件上使用这样 next.js 不会自动将滚动设置为 0,覆盖我们所做的一切。

归功于 Max william 对大部分结构的回答,我的主要变化是使用 useRef。我还添加了一些解释,希望对您有所帮助!

事实上,Next.js 内置了在返回上一页时恢复滚动位置的支持。我们可以简单地通过编辑来启用它next.config.js

module.exports = {
  experimental: {
    scrollRestoration: true,
  },
}

我在上下文和窗口滚动位置的帮助下解决了这个问题,如下所示:

import UserContext from '../context/context';

function MyApp({ Component, pageProps }) {
    const [ scrollPos, setScrollPos ] = React.useState(0);
    return (
        <UserContext.Provider value={{ scrollPos: scrollPos, setScrollPos: setScrollPos }}>
            <Component {...pageProps} />
        </UserContext.Provider>
    );
}

export default MyApp;

索引js文件:

import Link from 'next/link';
import UserContext from '../context/context';
import { useContext } from 'react';
import Axios from 'axios';

export default function Index(props) {
    const { scrollPos, setScrollPos } = useContext(UserContext);

    const handleScrollPos = () => {
        setScrollPos(window.scrollY);
    };

    React.useEffect(() => {
        window.scrollTo(0, scrollPos);
    }, []);

    React.useEffect(() => {
        window.addEventListener('scroll', handleScrollPos);
        return () => {
            window.removeEventListener('scroll', handleScrollPos);
        };
    }, []);

    if (props.err) {
        return <h4>Error bro</h4>;
    }

    return (
        <div>
            {props.res.map((each) => {
                return (
                    <div key={each.id}>
                        <Link scroll={true} as={`/post/${each.id}`} href="/post/[id]">
                            {each.title}
                        </Link>
                    </div>
                );
            })}
        </div>
    );
}

export async function getServerSideProps() {
    let res;
    let err;
    try {
        res = await Axios.get('https://jsonplaceholder.typicode.com/posts');
        err = null;
    } catch (e) {
        err = 'Error bro';
        res = { data: [] };
    }

    return {
        props: {
            res: res.data,
            err: err
        }
    };
}

发布js文件:

import Axios from 'axios';

function Post(props) {
    if (props.err) {
        return <h4>{props.err}</h4>;
    }
    return <h1>{props.post.title}</h1>;
}

export async function getServerSideProps(ctx) {
    const { query } = ctx;
    let err;
    let res;
    try {
        res = await Axios.get(`https://jsonplaceholder.typicode.com/posts/${query.id}`);
        err = null;
    } catch (e) {
        res = { data: [] };
        err = 'Error getting post';
    }

    return {
        props: {
            post: res.data,
            err: err
        }
    };
}

export default Post;

因此,当您从 post js 页面单击返回时,index js 中的第一个 useEffect 将运行,并且您将滚动到该位置。

之后,第二个 useEffect 将通过侦听滚动事件侦听器来捕获用户的滚动位置,因此它将始终在上下文中保存最新的滚动 y 位置,因此下次您返回索引 js 时,第一个 useEffect 将运行并将滚动位置设置为上下文中的值。