Next.js 新手容易犯的错误 | All about “use client“ (2)

news2024/12/26 9:27:36

在上一篇文章中(Next.js 新手容易犯的错误 | All about "use client" (1)),我们探讨了 Next.js 中服务端组件和客户端组件的运行机制以及常见的使用误区。

这篇文章将作为续集,进一步分析更多开发中容易遇到的问题,并提供实用的解决方案,帮助你在项目中更好地规避这些坑点。

5. 任何状态管理工具都需要在客户端组件中使用

比如下面这个例子:

"use client"

import { createContext } from "react";

export const ThemeContext = createContext(null);

export default function ThemeContextProvider({ children }: { children: React.ReactNode }) {
  return (
    <ThemeContext.Provider value="light">
      {children}
    </ThemeContext.Provider>
  );
}

虽然代码中没有react的钩子,但是我们仍然要使用"use client"。

原因是所有状态管理解决方案,比如 Context API 或 Zustand,都只能在客户端使用。

这是因为状态管理的目标本质上是为了追踪一些内容,但是我们无法在服务端追踪这些内容。对于服务端来说,它始终是一个请求-响应的循环,也就是服务端收到一个请求,处理后再返回一个响应,返回响应之后不会再追踪这个请求的任何内容但是用户在网站交互的过程中,浏览器是一直处于活跃状态的。所以这些状态管理的部分只能放在客户端处理,不能在服务端中使用。

6. 使用 use server 指令来标记服务端组件

比如对于product组件,我们使用了async,这只能在服务端组件中运行。

'use server' // 这是不对的

export default async function Product() {
  const res = await fetch("https://fakestoreapi.com/products/3");
  const product = await res.json();

  return (
    <section className="flex flex-col items-center gap-2">
      <p>{product.title}</p>
    </section>
  );
}

于是有些开发者就会显式地在顶部声明"use server"。

但是在nextjs的app路由中,所有的内容默认就是服务端组件,所以不需要使用"use server",遵循默认的循环就可以。

7. 敏感信息暴露在客户端

比如这个例子,我们从数据库中获取了用户数据,并且还包括了一个密码。这个数据将会作为props传递给其他的组件。

import FavoriteBtn from "@components/favorite-btn";
import Product from "@components/product";

export default function Home() {
  const user = {
    email: "john@gmail.com",
    password: "123456", // 敏感信息(应避免直接传递)
  };

  return (
    <main className="flex flex-col items-center mt-32 gap-12 text-xl">
      <h1 className="text-5xl font-semibold">My Store</h1>
      <Product />
      <FavoriteBtn user={user} /> {/* 将用户数据作为 props 传递 */}
    </main>
  );
}

如果,接收数据的这个组件正好是一个客户端的组件,那么这些数据在客户端中就会被看到。

"use client";

import { HeartFilledIcon, HeartIcon } from "@radix-ui/react-icons";
import { useState } from "react";

export default function FavoriteBtn({ user }: { user: { email: string; password: string } }) {
  console.log(user); // 在客户端打印用户数据(可能导致敏感信息泄露)

  const [isFavorite, setIsFavorite] = useState(false);

  return (
    <button
      onClick={() => setIsFavorite((prev) => !prev)}
      className="p-2 rounded-md bg-zinc-300"
    >
      {isFavorite ? <HeartFilledIcon /> : <HeartIcon />}
    </button>
  );
}

当然我们不希望用户的密码泄露给客户端,我们需要避免直接去传递敏感数据。Next.js 自动处理了服务端和客户端的网络边界,要注意检查客户端组件是不是在不必要的情况下接收了敏感数据。

8. 客户端组件在页面初始渲染的时候也会在服务端运行一次

还是之前的例子,page页面中,product组件时服务端组件,favoriteBtn组件时客户端组件。

// page.tsx
import FavoriteBtn from "@/components/favorite-btn";
import Product from "@/components/product";

export default function Mistake1() {
  return (
    <main className="flex flex-col items-center mt-32 gap-12 text-xl">
      <h1 className="text-5xl font-semibold">My Store</h1>
      <Product /> 
      <FavoriteBtn /> 
    </main>
  );
}

在product和favoriteBtn组件中分别打印出来 'hi this is server component' 和 'hi this is client component'。

// product.tsx
export default async function Product() {
  console.log('hi this is server component')
  const res = await fetch("https://fakestoreapi.com/products/3");
  const product = await res.json();

  return (
    <section className="flex flex-col items-center gap-2">
      <p>{product.title}</p>
    </section>
  );
}
// favorite-btn.tsx
'use client'
import { HeartFilledIcon, HeartIcon } from "@radix-ui/react-icons";
import { useState } from "react";

export default function FavoriteBtn() {
  console.log('hi this is client component')
  const [isFavorite, setIsFavorite] = useState(false);

  return (
    <button
      onClick={() => setIsFavorite((prev) => !prev)}
      className="p-2 rounded-md bg-zinc-300"
    >
      {isFavorite ? <HeartFilledIcon /> : <HeartIcon />}
    </button>
  );
}

运行的时候,服务端组件在服务端运行,所以在浏览器的控制台中可以看到server的标签:

但是这里打印出来的内容,在终端中也会打印出来,因为服务端的代码输出的内容本身就会在终端中显示:

但是,客户端的代码,在终端中也显示出来了。这就是因为客户端组件,在页面初始渲染的时候,也会在服务端运行一次用于预渲染HTML,激活了之后才能在客户端交互。

总结下来就是:

  1. 服务端组件
    • 只在服务端运行。
    • 生成 HTML 并将其发送到客户端。
  1. 客户端组件
    • 会在客户端运行,但在页面的初始渲染时,也会在服务端运行一次(用于预渲染 HTML)。
    • 激活(hydrate)后才能在客户端进行交互。
  1. 日志行为
    • 服务端组件的日志出现在服务端的终端中(浏览器中会有server标签)。
    • 客户端组件的日志会同时出现在服务端(初次渲染时)和客户端(激活后运行)中。

9. 正确使用和浏览器相关的API

假设我们有一个收藏按钮组件(FavoriteButton),其功能是记录用户是否收藏了某个项目,并将状态保存在 localStorage 中。

"use client";

export default function FavoriteButton() {
  const isFavorite = localStorage.getItem("isFavorite"); // 直接访问 localStorage
  return (
    <button>
      {isFavorite ? "Unfavorite" : "Favorite"}
    </button>
  );
}

这时候,客户端组件在服务端预渲染的时候,会出现报错:ReferenceError: localStorage is not defined。

这是因为localStorage是浏览器的API,当组件在服务端运行的时候,localStorage并不存在。

解决的方法有三种:

1是在使用API之前,先检查代码是否在客户端环境中运行:

"use client";

export default function FavoriteButton() {
  let isFavorite = false;

  if (typeof window !== "undefined") {
    isFavorite = localStorage.getItem("isFavorite") === "true";
  }

  return (
    <button>
      {isFavorite ? "Unfavorite" : "Favorite"}
    </button>
  );
}

其中typeof window !== "undefined" 检查 window 是否可用,仅在客户端环境中访问 localStorage

2是使用useEffect,因为useEffect只会在客户端运行:

"use client";

import { useEffect, useState } from "react";

export default function FavoriteButton() {
  const [isFavorite, setIsFavorite] = useState(false);

  useEffect(() => {
    const favorite = localStorage.getItem("isFavorite") === "true";
    setIsFavorite(favorite);
  }, []);

  return (
    <button>
      {isFavorite ? "Unfavorite" : "Favorite"}
    </button>
  );
}

3是使用next/dynamic模块进行动态导入,并禁用服务器渲染:

import dynamic from "next/dynamic";

const FavoriteButton = dynamic(() => import("./FavoriteButton"), { ssr: false });

export default function Home() {
  return (
    <main>
      <h1>My Store</h1>
      <FavoriteButton />
    </main>
  );
}

禁用服务器渲染后,组件只会在客户端运行,但是可能会略微延迟组件的加载时间。

理解服务端组件和客户端组件的运行机制,并合理使用 use client,可以有效避免常见问题,提高开发效率。

希望本文的总结能为你提供思路,让项目开发更加顺畅。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2254031.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

优化SEO策略的关键在于长尾关键词的有效运用

内容概要 在数字营销的快速发展背景下&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;策略的制定显得尤为重要。而长尾关键词作为一种有效的关键词策略&#xff0c;正逐渐成为提升搜索引擎排名的重要工具。长尾关键词通常由三到四个词组成&#xff0c;具体而具有针对性&…

医学临床机器学习中算法公平性与偏差控制简析

摘要 随着医疗领域中数据的不断积累和计算能力的提升&#xff0c;临床机器学习技术发展迅速&#xff0c;但算法不公平性和偏差问题凸显。本文深入探讨了临床机器学习算法公平性的重要性、概念与定义、在临床应用中的影响、偏差来源、降低偏差方法及提升公平性策略。通过对不同…

​​​​​​​移远通信基于高通平台发布可集成边缘计算功能的5G MBB解决方案

在5G技术与人工智能深度融合的背景下&#xff0c;各行各业正迎来前所未有的创新机遇。为了加速5G移动宽带&#xff08;MBB&#xff09;行业向智能化转型&#xff0c;并简化边缘计算应用的开发流程&#xff0c;移远通信近期隆重推出了基于骁龙5G调制解调器及射频系统打造&#x…

六、文本搜索工具(grep)和正则表达式

一、grep工具的使用 1、概念 grep&#xff1a; 是 linux 系统中的一个强大的文本搜索工具&#xff0c;可以按照 正则表达式 搜索文本&#xff0c;并把匹配到的行打印出来&#xff08;匹配到的内容标红&#xff09;。 2、语法 grep [options]…… pattern [file]…… 工作方式…

【计算机网络】实验3:集线器和交换器的区别及交换器的自学习算法

实验 3&#xff1a;集线器和交换器的区别及交换器的自学习算法 一、 实验目的 加深对集线器和交换器的区别的理解。 了解交换器的自学习算法。 二、 实验环境 • Cisco Packet Tracer 模拟器 三、 实验内容 1、熟悉集线器和交换器的区别 (1) 第一步&#xff1a;构建网络…

linux-安全-iptables防火墙基础笔记

目录 一、 iptables链结构 五链 二、 iptables表结构 四表 三、 匹配流程 四、 语法 五、 匹配 1. 通用匹配 2. 隐含匹配 3. 显示匹配 六、 SNAT 七、 DNAT 八、 规则备份及还原 1. 备份 2. 还原 这篇将讲解iptables防火墙的基础知识 一、 iptables链结构 规则…

一个实用的端到端的深度学习库存模型

G1 文章信息 文章题为“A Practical End-to-End Inventory Management Model withDeep Learning”&#xff0c;该文于2022年发表至“MANAGEMENT SCIENCE”。文章的核心是提出了端到端的框架用于多周期库存补货问题。 2 摘要 文章研究了一个数据驱动的多周期库存补货问题&am…

<工具 Claude Desktop> 配置 MCP server 连接本地 SQLite, 本机文件夹(目录) 网络驱动器 Windows 11 系统

也是在学习中... 起因&#xff1a; 抖音博客 艾克AI分享 他的视频 #143《Claude开源MCP彻底打破AI的信息孤岛》 提到: Claude开源的MCP太强了&#xff0c;视频后面是快速演示&#xff0c;反正看了好几遍也没弄明白。菜单都不一样&#xff0c;感觉用的不是同一家 Claude. 探…

交易所 Level-2 历史行情数据自动化导入攻略

用户部署完 DolphinDB 后&#xff0c;需要将历史股票数据批量导入数据库&#xff0c;再进行数据查询、计算和分析等操作。DolphinDB 开发了 ExchData 模块&#xff0c;主要用于沪深交易所 Level-2 行情原始数据的自动化导入&#xff0c;目前已支持的数据源包括&#xff1a; 沪…

matlab finv()函数解释 F分布 和 逆累积分布函数 卡方分布

1.Earths flattening 翻译并解释含义 "Earths flattening" 翻译为中文是“地球的扁率”。 含义解释&#xff1a; 地球的扁率是指地球形状偏离完美球形的程度。地球并非一个完美的球体&#xff0c;而是一个扁球体&#xff0c;即在两极略微扁平&#xff0c;赤道略微…

Istio笔记01--快速体验Istio

Istio笔记01--快速体验Istio 介绍部署与测试部署k8s安装istio测试istio 注意事项说明 介绍 Istio是当前最热门的服务网格产品&#xff0c;已经被广泛应用于各个云厂商和IT互联网公司。企业可以基于Istio轻松构建服务网格&#xff0c;在接入过程中应用代码无需更改&#xff0c;…

国家烟草局招聘信息查看

以下是官网链接&#xff1a; 国家烟草专卖局 在首页&#xff0c;鼠标移到行业资讯&#xff0c;点击人事与培训&#xff0c;即可看到相应的招聘信息了。

【SpringMVC】用户登录器项目,加法计算器项目的实现

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;用户登录项目实现 1&#xff1a;需求 2&#xff1a;准备工作 &#xff08;1&#xf…

联通光猫DT741-csf 完全po解 改桥接

1.管理员密码破解&#xff0c;把光猫的loid pppoe用户名密码&#xff0c;各个连接vlan id记下来 打开链接 http://192.168.1.1/hidden_version_switch.html version选择Default Version&#xff0c;点击submit&#xff0c;光猫默认重启。重启后ip地址变为192.168.1.1 并且dhcp…

12月2日星期一今日早报简报微语报早读

12月2日星期一&#xff0c;农历十一月初二&#xff0c;早报#微语早读。 1、公安部&#xff1a;全国机动车所有人12月2日起均可申领电子行驶证&#xff1b; 2、2025年国考笔试开考&#xff1a;参考率约为86.7%&#xff0c;约65人录1人&#xff1b; 3、今日头条、拼多多等9款A…

机器学习概述,特征工程简述2.1——2.3

机器学习概述&#xff1a; 1.1人工智能概述 达特茅斯会议—人工智能的起点 机器学习是人工智能的一个实现途径 深度学习是机器学习的一个方法发展而来 1.1.2 机器学习和深度学习能做什么 传统预测 图像识别 自然语言处理 1.2什么是机器学习 数据 模型 预测 从历史数…

C语言(一维数组练习)

键盘录入一组数列&#xff0c;利用冒泡排序将数据由大到小排序 #include <stdio.h>int main(int argc,char *argv[]) {int i,j,tmep;int arr[10];printf("请输入10个测试整数&#xff1a;\n");int lensizeof(arr)/sizeof(arr[0]);for(i0;i<len;i){scanf(&q…

【2025最新计算机毕业设计】基于SSM+Vue中华传统文化吟诵知识学习系统

作者简介&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容&#xff1a;&#x1f31f;Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…

MySQL需掌握到何种程度?才能胜任工作

大家好&#xff0c;我是袁庭新。星友问&#xff1a;MySQL需要学到什么程度&#xff1f;才能胜任日常的软件开发工作呢&#xff01;以下是一些建议的学习目标和程度&#xff0c;这些目标旨在帮助你在工作中高效地使用MySQL。 数据库的基本概念、MySQL的安装及配置、SQL的概念、S…

[TPAMI 2024]Vision-Language Models for Vision Tasks: A Survey

论文网址&#xff1a;Vision-Language Models for Vision Tasks: A Survey | IEEE Journals & Magazine | IEEE Xplore 论文Github页面&#xff1a;GitHub - jingyi0000/VLM_survey: Collection of AWESOME vision-language models for vision tasks 英文是纯手打的&…