Skip to content
目录

3 月 7 号面试题(蚂蚁金服 二面)

1. 自我介绍

2. 什么是跨域,一般怎么解决

当一个请求 url 的协议域名端口三者之间的任意一个与当前页面 url不同即为跨域

出于浏览器的同源策略限制,是发生在 页面 到 服务端 请求的过程中

解决方法:

nginx 反向代理解决跨域(前端常用)

4.CORS 解决跨域(也就是添加响应头解决跨域)

浏览器先询问 b,b 允许 a 访问 access-control-allow-origin access-control-max-age PHP 端修改 header: image.png

awk
header('Access-Control-Allow-Origin:*');//允许所有来源访问
header('Access-Control-Allow-Method:POST,GET');//允许访问的方式

它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。

CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10。

浏览器端:

目前,所有浏览器都支持该功能(IE10 以下不行)。整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。

服务端:

CORS 通信与 AJAX 没有任何差别,因此你不需要改变以前的业务逻辑。只不过,浏览器会在请求中携带一些头信息,我们需要以此判断是否运行其跨域,然后在响应头中加入一些信息即可。这一般通过过滤器完成即可。

优势:

在服务端进行控制是否允许跨域,可自定义规则 支持各种请求方式

缺点:

会产生额外的请求

3. http 请求的状态码有哪些

200 OK:请求成功,服务器成功处理了请求。 201 Created:请求已成功,并在服务器上创建了新的资源。 204 No Content:服务器成功处理了请求,但没有返回任何内容。 301 Moved Permanently:永久移动。请求的资源已被永久的移动到新 URI。 302 Found:资源只是临时被移动。客户端应继续使用原有 URI。 400 Bad Request:服务器无法理解请求的语法,请求有语法错误。 401 Unauthorized:请求需要用户身份验证。 403 Forbidden:服务器拒绝请求,没有权限访问。 404 Not Found:请求的资源不存在。 405 Method Not Allowed:请求方法不被允许。 500 Internal Server Error:服务器内部错误,无法完成请求。 502 Bad Gateway:服务器作为网关或代理,从上游服务器收到无效响应。 503 Service Unavailable:服务器当前无法处理请求,通常由于过载或维护。

1XX 消息状态码:

  • 100:Continue 继续。客户端继续请求。
  • 101:Swiching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到 HTTP 的新版本协议。

2XX 成功状态码

  • 200:OK 请求成功。一般用于 GET 和 POST 请求。
  • 201:Created 已创建。成功请求并创建了新的资源。
  • 202:Accepted 已接受。已经接受请求,但未处理完成。
  • 203:Non-Authoritative Information 非授权信息。请求成功,但返回的 meta 信息不在原始的服务器,而是一个副本。
  • 204:No Content 无内容。服务器处理成功,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档。
  • 205:Reset Content 重置内容。服务器处理成功,用户终端(例如浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域。
  • 206:Partial Content 部分内容。服务器成功处理了部分 GET 请求。响应报文中包含由 Content-Range 指定范围的实体内容。

3XX 重定向状态码

  • 300:Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择。
  • 301:Moved Permanently 永久移动。请求的资源已被永久的移动到新 URI,返回信息会包括新的 URI,浏览器会自动定向到新 URI。今后任何新的请求都应使用新的 URI 代替。
  • 302:Found 临时移动,与 301 类似。但资源只是临时被移动。客户端应继续使用原有 URI。
  • 303:See Other 查看其它地址。与 301 类似。使用 GET 和 POST 请求查看。
  • 304:Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源。
  • 305:Use Proxy 使用代理。所请求的资源必须通过代理访问。
  • 306:Unused 已经被废弃的 HTTP 状态码。
  • 307:Temporary Redirect 临时重定向。与 302 类似。使用 GET 请求重定向。

4XX 客户端错误状态码

  • 400:Bad Request 客户端请求的语法错误,服务器无法理解。
  • 401:Unauthorized 请求要求用户的身份认证。
  • 402:Payment Required 保留,将来使用。
  • 403:Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求。
  • 404:Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面。
  • 405:Method Not Allowed 客户端请求中的方法被禁止。
  • 406:Not Acceptable 服务器无法根据客户端请求的内容特性完成请求。
  • 407:Proxy Authentication Required 请求要求代理的身份认证,与 401 类似,但请求者应当使用代理进行授权。
  • 408:Request Time-out 服务器等待客户端发送的请求时间过长,超时。
  • 409:Conflict 服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突。
  • 410:Gone 客户端请求的资源已经不存在。410 不同于 404,如果资源以前有现在被永久删除了可使用 410 代码,网站设计人员可通过 301 代码指定资源的新位置。
  • 411:Length Required 服务器无法处理客户端发送的不带 Content-Length 的请求信息。
  • 412:Precondition Failed 客户端请求信息的先决条件错误。
  • 413:Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个 Retry-After 的响应信息。
  • 414:Request-URI Too Large 请求的 URI 过长(URI 通常为网址),服务器无法处理。
  • 415:Unsupported Media Type 服务器无法处理请求附带的媒体格式。
  • 416:Requested range not satisfiable 客户端请求的范围无效。
  • 417:Expectation Failed 服务器无法满足 Expect 的请求头信息。

5XX 服务端错误状态码

  • 500:Internal Server Error 服务器内部错误,无法完成请求。
  • 501:Not Implemented 服务器不支持请求的功能,无法完成请求。
  • 502:Bad Gateway 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应。
  • 503:Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的 Retry-After 头信息中。
  • 504:Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求。
  • 505:HTTP Version not supported 服务器不支持请求的 HTTP 协议的版本,无法完成处理。

4. 箭头函数和普通函数的区别

https://juejin.cn/post/6844903805960585224

箭头函数是 ES6 引入的一种函数声明方式,它具有更短的语法和词法作用域。 MDN 的描述: 箭头函数表达式的语法比传统的函数表达式更简洁,但在语义上有一些差异,在用法上也有一些限制:

  1. 箭头函数都是匿名函数,普通函数可以是匿名函数,也可以是具名函数。
  2. 普通函数的 this 是指向调用它的对象,箭头函数没有自己的 this,它会在声明时捕获其所在上下文 this 以供自己使用。
  3. 箭头函数不能做构造函数,也就是不能使用 new 关键字。箭头函数没有 arguments 参数,可以使用 rest 参数来代替。
  • 箭头函数没有独立的 thisargumentssuper 绑定,并且不可被用作方法。
  • 箭头函数不能用作构造函数。使用 new 调用它们会引发 TypeError。它们也无法访问 new.target 关键字。
  • 箭头函数不能在其主体中使用 yield,也不能作为生成器函数创建。
  • .call()/.apply()/.bind()无法改变箭头函数中 this 的指向

所谓的没有 this ,不是箭头函数中没有 this 这个变量,而是箭头函数不绑定自己的 this,它们会捕获其所在上下文的 this 值,作为自己的 this 值。这对于回调函数特别有用,可以避免传统函数中常见的 this 指向问题。例如,在对象方法中使用箭头函数可以确保 this 保持一致。

箭头函数是继承外层函数的 this 绑定

5. js 攻击 网络安全相关的

面试官:web常见的攻击方式有哪些?如何防御?

XSS跨域脚本攻击 和 CSRF跨站请求伪造攻击

XSS 跨域脚本攻击 和 CSRF 跨站请求伪造攻击

  • 总结

    1、页面安全问题 的主要原因就是浏览器为 同源策略 开的两个后门:

    • 页面中可以任意引用 第三方资源
    • 通过 CORS 策略让 XMLHttpRequestFetch 去 跨域 请求资源

    2、为了解决这些问题:

    • 引入了 CSP 内容安全策略 来限制页面任意引入外部资源
    • 引入了 HttpOnly 机制来禁止 XMLHttpRequest 或者 Fetch 发送一些关键 Cookie
    • 引入了 SameSiteOrigin 来防止 CSRF 攻击

XSS 攻击

XSS 攻击是通过注入恶意脚本代码来利用程序的

6. 防抖跟节流的区别

相同点:

  • 都可以通过使用 setTimeout 来实现
  • 目的都是通过降低回调执行频率来节省计算资源

不同点:

  • 防抖是在一段连续操作结束后,处理回调,利用 clearTimeout 和 setTimeout 实现。节流是在一段连续操作中,每一段时间只执行一次,频率较高的时间中使用来提高性能
  • 防抖关注一定时间连续触发的事件,只在最后一次执行,而节流一段时间内只执行一次。

7. 什么是浏览器的事件循环机制

微任务、宏任务分别有哪些方法

常见的微任务有:

  • Promise.then
  • MutaionObserver
  • Object.observe(已废弃;Proxy 对象替代)
  • process.nextTick(Node.js)

常见宏任务:

  • setTimeout
  • ajax
  • dom 事件
  • setImmediate(Node 环境)
  • requestAnimationFrame

8. umi 和 nextjs 的差异有哪些

Umi 和 Next.js 是两种不同的框架,它们都是用于开发 React 应用程序的。

Umi 是一个以路由为中心的框架,支持快速构建组件、页面和路由。它具有易于扩展的插件体系结构,支持在生产环境中进行代码切割,并且拥有内置的打包和部署工具。

Next.js 是一个服务器端渲染 (SSR) 的框架,提供了方便的页面导航和强大的 SEO 解决方案。它也支持代码切割,并且可以很容易地部署到云平台上,如 Vercel。

总的来说,Umi 和 Next.js 都是优秀的框架,具体使用哪一个取决于您的特定需求和项目要求。

9. 有参与前期项目的搭建吗?

10. 什么是 ssr?和客户端渲染有什么区别

两者本质的区别是什么?

客户端渲染和服务器端渲染的最重要的区别就是究竟是谁来完成 html 文件的完整拼接, 如果是在服务器端完成的,然后返回给客户端,就是服务器端渲染,而如果是前端做了更多的工作完成了 html 的拼接,则就是客户端渲染。

服务器端渲染的优缺点是怎样的?

优点:

  1. 前端耗时少:因为后端拼接完了 html,浏览器只需要直接渲染出来。用户看到页面的速度快。
  2. 有利于 SEO:因为在后端有完整的 html 页面,所以爬虫更容易爬取获得信息,更有利于 seo。
  3. 无需占用客户端资源:即解析模板的工作完全交由后端来做,客户端只要解析标准的 html 页面即可,这样对于客户端的资源占用更少,尤其是移动端,也可以更省电。
  4. 后端生成静态化文件:即生成缓存片段,这样就可以减少数据库查询浪费的时间了,且对于数据变化不大的页面非常高效 。

缺点:

  1. 不利于前后端分离,开发效率低。 使用服务器端渲染,则无法进行分工合作,则对于前端复杂度高的项目,不利于项目高效开发。另外,如果是服务器端渲染,则前端一般就是写一个静态 html 文件,然后后端再修改为模板,这样是非常低效的,并且还常常需要前后端共同完成修改的动作; 或者是前端直接完成 html 模板,然后交由后端。另外,如果后端改了模板,前端还需要根据改动的模板再调节 css,这样使得前后端联调的时间增加。
  2. 占用服务器端资源。即服务器端完成 html 模板的解析,如果请求较多,会对服务器造成一定的访问压力。而如果使用前端渲染,就是把这些解析的压力分摊了前端,而这里确实完全交给了一个服务器。

适用场景: 强交互、注重 SEO 的页面,比如购物网站

客户端渲染的优缺点是怎样的?

优点:

  1. 前后端分离。前端专注于前端 UI,后端专注于 api 开发,且前端有更多的选择性,而不需要遵循后端特定的模板。
  2. 体验更好。比如,我们将网站做成 SPA 或者部分内容做成 SPA,这样,尤其是移动端,可以使体验更接近于原生 app。

缺点:

  1. 前端响应较慢。如果是客户端渲染,前端还要进行拼接字符串的过程,需要耗费额外的时间,不如服务器端渲染速度快。
  2. 不利于 SEO。目前比如百度、谷歌的爬虫对于 SPA 都是不认的,只是记录了一个页面,所以 SEO 很差。因为服务器端可能没有保存完整的 html,而是前端通过 js 进行 dom 的拼接,那么爬虫无法爬取信息。 除非搜索引擎的 seo 可以增加对于 JavaScript 的爬取能力,这才能保证 seo。

适用场景: 强交互、不注重 SEO 的页面,比如管理类的项目。

使用服务器端渲染还是客户端渲染?

比如企业级网站,主要功能是展示没有复杂的交互,并且需要良好的 SEO,则这时我们就需要使用服务器端渲染;而类似后台管理页面,交互性比较强,不需要 seo 的考虑,那么就可以使用客户端渲染。

另外,具体使用何种渲染方法并不是绝对的,比如现在一些网站采用了首屏服务器端渲染,即对于用户最开始打开的那个页面采用的是服务器端渲染,这样就保证了渲染速度,而其他的页面采用客户端渲染,这样就完成了前后端分离。

11. React 写代码的时候有些推荐的写法?哪些是不推荐的?函数组件的最佳实践的,关于编码的内容?一般写组件的时候推荐怎么写?不推荐怎么写?最佳实践

比如:

  1. 子组件没有从父组件传入的 props 或者传入的 props 仅仅为简单数值类型使用 memo 即可。
  2. 子组件有从父组件传来的方法时,在使用 memo 的同时,使用 useCallback 包裹该方法,传入方法需要更新的依赖值。
  3. 子组件有从父组件传来的对象和数组等值时,在使用 memo 的同时,使用 useMemo 以方法形式返回该对象,传入需要更新的依赖值。

https://juejin.cn/post/7208716321123303483

https://www.freecodecamp.org/chinese/news/best-practices-for-react/#tips-to-help-you-write-better-react-code-the-cherries-on-top

1.避免在循环或嵌套函数中使用 Hooks

在 React Hooks 中,应该确保在组件最顶层使用,而不是在循环、条件语句或嵌套函数中使用。这是因为 Hooks 需要遵循 React 的渲染顺序,以便正确更新组件。

2.命名约定

在命名 Hooks 时,需要遵循 React 官方提供的约定。例如,useState、useEffect 和 useRef 等都是 React Hooks 中的常用 Hook。

3.使用 useEffect 来处理生命周期

在函数式组件中,没有 componentDidMount 和 componentWillUnmount 等生命周期方法。为了处理这些生命周期,我们可以使用 useEffect Hook。useEffect 可以在组件挂载、更新和卸载时执行一些操作,例如发送网络请求或订阅某个事件源。

4.使用 useMemo 和 useCallback 来优化效率

当组件需要计算大量数据或处理复杂的逻辑时,使用 useMemo 和 useCallback 可以有效地提高性能。useMemo 可以缓存函数的计算结果,而 useCallback 可以将函数缓存以减少重复渲染。

5.使用自定义 Hooks 来复用逻辑

自定义 Hooks 可以让我们将一些常用的逻辑封装到一个函数中,并且可以在多个组件中重复使用。例如,一个名为 useFetch 的自定义 Hook 可以用于发送网络请求并返回数据。

6.如何使用 React Hooks

下面我将演示如何使用 React Hooks,并做出相应的解析。

首先,我们来创建一个简单的计数器组件,该组件使用 useState Hook 来管理状态:

js
js
复制代码import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  const handleIncrement = () => setCount(count + 1);
  const handleDecrement = () => setCount(count - 1);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleDecrement}>Decrement</button>
    </div>
  );
}

export default Counter;

在上面的代码中,我们使用 useState Hook 来定义一个名为 count 的状态,并使用 setCount 函数来更新该状态。useState Hook 的返回值是一个数组,第一个值是状态的初始值,第二个值是用于更新状态的函数。当我们调用 setCount 函数时,React 会重新渲染组件并更新状态。在该组件中,我们定义了两个操作 count 状态的函数,handleIncrement 和 handleDecrement,分别可以用于增加和减少计数器。

接下来,让我们来创建一个使用 useEffect Hook 的组件,该组件会在组件挂载时订阅某个事件源,并在组件卸载时取消订阅:

js
js
复制代码import React, { useState, useEffect } from 'react';

function EventSubscriber() {
  const [eventData, setEventData] = useState(null);

  useEffect(() => {
    const subscription = eventSource.subscribe((data) => {
      setEventData(data);
    });

    return () => {
      subscription.unsubscribe();
    };
  }, []);

  return (
    <div>
      <h1>Event data: {eventData}</h1>
    </div>
  );
}

export default EventSubscriber;

在上面的代码中,我们使用 useEffect Hook 来订阅某个事件源。我们通过传递一个空数组作为 useEffect 的第二个参数来确保 useEffect 只在组件挂载时执行一次。在订阅事件源时,我们返回一个函数来取消订阅,以确保在组件卸载时取消订阅。

最后,让我们来创建一个使用 useMemo 和 useCallback Hooks 的组件,该组件会计算出斐波那契数列:

js
js
复制代码import React, { useMemo, useCallback } from 'react';

function Fibonacci() {
  const calculateFibonacci = useCallback((n) => {
    if (n <= 1) {
      return 1;
    }
    return calculateFibonacci(n - 1) + calculateFibonacci(n - 2);
  }, []);

  const fibonacciNumber = useMemo(() => calculateFibonacci(10), [calculateFibonacci]);

  return (
    <div>
      <h1>Fibonacci number: {fibonacciNumber}</h1>
    </div>
  );
}

export default Fibonacci;

在上面的代码中,我们使用 useCallback Hook 来缓存 calculateFibonacci 函数,以便在组件重渲染时不会重复计算斐波那契数列。我们还使用 useMemo Hook 来缓存计算结果,以便在组件重渲染时不会重复计算。

7.总结

综上所述,React Hooks 可以提高 React 应用程序的可维护性和性能。在使用 Hooks 时需要遵循上述最佳实践,确保代码的正确性和可读性。

12. 项目难点

被问到项目亮点、难点、遇到的问题、解决思路

https://blog.csdn.net/gaoyu007/article/details/117200172

商品同步的问题

购物车 有加购的商品列表 然后它下面会展示一些相关联的商品信息 下面的商品也是可以加购的,但是加购之后需要同步到上面的商品列表

解决的方式

维护页面

客户希望系统维护期间,网站可以展示维护页面,前期的要求是输入密码可正常访问

分页算不算?

正常分页的话是这样的,接口请求时传递当前页索引、每页数量、总数。 但是因为 RTI 的接口所给的数据没有总数,所以分页的时候需要前端做一些处理

将 page(当前页索引)记录到 url 上,点击分页按钮时,将页数据信息通过接口请求给后端 web 很好实现

wap 端使用的是上下滑动分页

所以需要监听滑动事件从而控制接口请求,另外就是接口请求的参数处理和拿到数据以后的处理

实现:

1. 监听滑动事件从而控制接口请求

可能的方法如下: 使用 scrollTop、clientHeight 等属性方法来获取想要的滚动数据等,即使用传统的滚动事件监听

js
window.addEventListener("scroll", function () {
  // 获取滚动条滚动的距离
  var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  // 获取整个页面的高度
  var scrollHeight =
    document.documentElement.scrollHeight || document.body.scrollHeight;
  // 获取视口的高度
  var clientHeight =
    document.documentElement.clientHeight || document.body.clientHeight;
  // 计算距离底部的高度
  var scrollDistance = scrollHeight - scrollTop - clientHeight;
  // 如果距离底部的高度小于某个值(比如100),可以认为是滚动到底部了
  if (scrollDistance <= 100) {
    // 触发滚动到底部的逻辑
    console.log("页面滚动到底部了!");
    // 在这里可以调用加载更多内容、显示加载提示等逻辑
  }
});

项目中使用的是 IntersectionObserver

js
// 创建一个观察器实例
const observer = new IntersectionObserver(
  (entries, observer) => {
    entries.forEach((entry) => {
      // 检查目标元素是否进入视口
      if (entry.isIntersecting) {
        // 触发滚动到底部的逻辑
        console.log("页面滚动到底部了!");
        // 加载更多内容或者执行其他操作
        // ...

        // 如果不再需要观察,可以停止观察
        observer.unobserve(entry.target);
      }
    });
  },
  {
    // 配置选项
    threshold: 1.0, // 当目标元素的可见比例达到100%时,触发回调函数
    root: null, // 使用视口作为根
    rootMargin: "0px", // 根边界
  }
);

// 获取页面底部的元素,通常是一个占位符或者加载更多的按钮
const bottomElement = document.querySelector("#bottom-element");

// 观察页面底部的元素
observer.observe(bottomElement);

比如一页展示 5 条数据,在数据展示的末尾处插入一个 id 为 BOTTOM_ID 的 div,用作标记末尾位置。 获取 BOTTOM_ID 元素,然后创建一个 IntersectionObserver 实例,并配置监听的对象和相关属性, 比如 threshold 为 0.1,表示当目标元素的可见比例(相交比例)达到 10%时,就触发回调函数。

jsx
useEffect(() => {
  let observer;
  const ref = document.getElementById(BOTTOM_ID);
  if (isNext) {
    observer = new IntersectionObserver(handleScroll, {
      threshold: 0.1,
    });
    if (ref) {
      observer.observe(ref);
    }
  }
  return () => {
    if (isNext && ref) {
      observer.unobserve(ref);
    }
    mountedRef.current = true;
  };
}, []);

回调函数有一个参数,是数组,每一个成员都是 IntersectionObserverEntry 对象,IntersectionObserverEntry 对象有几个属性, 其中 isIntersecting 的值是一个布尔值,指示目标元素是否已转换为相交状态 ( true) 还是脱离相交状态 ( false)。 如果处于相交状态就触发后续代码操作,修改 router 的 page 的值

useEffect 依赖了 router,当 router 修改的时候会重新获取数据

jsx
const handleScroll = useCallback(
  throttle((event) => {
    const entry = event[0];
    if (entry?.isIntersecting && !loadingRef.current && !isAllRef.current) {
      router.replace(
        {
          pathname: router.asPath.split("?")[0],
          query: handleUrlParams({
            ...routerRef.current,
            page: currentRef.current + 1,
          }),
        },
        null,
        { scroll: false, shallow: true }
      );
    }
  }, 400),
  []
);

补充知识点: IntersectionObserver API 使用教程

js
var io = new IntersectionObserver(callback, option);

IntersectionObserver 是浏览器原生提供的构造函数,接受两个参数:callback 是可见性变化时的回调函数,option 是配置对象(该参数可选)

构造函数的返回值是一个观察器实例。实例的 observe 方法可以指定观察哪个 DOM 节点。

js
// 开始观察
io.observe(document.getElementById("example"));

// 停止观察
io.unobserve(element);

// 关闭观察器
io.disconnect();

如果要观察多个节点,就要多次调用这个方法。

js
io.observe(elementA);
io.observe(elementB);

该 IntersectionObserverEntry 接口的只读 isIntersecting 属性是一个布尔值,表示 true 目标元素是否与相交观察器的根相交。如果是 true,则 IntersectionObserverEntry 描述了到相交状态的转变;如果是 false,那么您知道过渡是从相交到不相交。

2. 接口请求的参数处理和拿到数据以后的处理

接口参数处理: 确定当前页索引 如果购物车列表 shopCarList 为空且页码 val 大于 1,说明刷新了页面,购物车的做法是重置 page 为 1,后续就不需要了。直接获取第一页的数据就行。

接口请求到数据后: 修改某些属性,比如 loading 为 false; 如果拿到的 list 长度为 0,表示后续没有数据可以请求了,isAll 修改为 true 否则就处理数据,保存数据。 保存数据的时候需要判断,当前 page 为 1,直接 set 数据,否则,将新数据和旧数据合并

js
const getProducts = () => {
  const val = parseInt(page);
  if (shopCarList.length == 0 && val > 1) {
    const url = {
      pathname: router.pathname,
      query: { ...router.query, page: 1 },
    };
    router.replace(url);
    return;
  }
  const current = typeof val === "number" && !isNaN(val) && val > 0 ? val : 1;
  currentRef.current = current;
  loadingRef.current = true;
  const pageNo = shopCarList.length == 0 && val != "1" ? 1 : current;

  setLoading(true);
  request({ skip: pageNo - 1, take: PGAE_SIZE })
    .then(({ data = {} }) => {
      const list = data.shopCarList || [];
      if (!mountedRef.current) {
        routerRef.current = router.query;
        loadingRef.current = false;
        const isAll = list.length == 0;
        isAllRef.current = isAll;
        currentRef.current =
          current == 1 ? 1 : isAll ? currentRef.current : current;
        list.map((item) => {
          /* 商品数据处理 */
        });
        if (current == 1) {
          setShopCarList(list);
          setOtherShopCarList(data.otherShopCarList || []);
        } else if (!isAll) {
          setShopCarList((v) => [...v, ...list]);
        }
        setLoading(false);
      }
    })
    .catch((err) => {
      !mountedRef.current && setLoading(false);
    });
};

13. 面试官:有什么想问我的?