前端GraphQL客户端:优雅地获取数据

前端GraphQL客户端:优雅地获取数据

毒舌时刻

前端GraphQL?这不是后端的事吗?

"REST API就够了,为什么要用GraphQL"——结果前端需要多次请求,数据冗余,
"GraphQL太复杂了,我学不会"——结果错过了更灵活的数据获取方式,
"我直接用fetch请求GraphQL,多简单"——结果缺少缓存、错误处理等功能。

醒醒吧,GraphQL不是后端的专利,前端也需要专业的客户端工具!

为什么你需要这个?

  • 减少网络请求:一次请求获取所有需要的数据
  • 数据精确:只获取需要的数据,避免冗余
  • 类型安全:自动生成TypeScript类型
  • 缓存优化:智能缓存,减少重复请求
  • 开发效率:简化数据获取逻辑

反面教材

// 反面教材:直接使用fetch请求GraphQL async function fetchGraphQL(query, variables) { const response = await fetch('https://api.example.com/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query, variables }), }); const data = await response.json(); if (data.errors) { console.error('GraphQL errors:', data.errors); throw new Error('GraphQL request failed'); } return data.data; } // 反面教材:重复请求相同数据 async function loadUserAndPosts() { // 第一次请求用户信息 const { user } = await fetchGraphQL(` query GetUser($id: ID!) { user(id: $id) { id name email } } `, { id: 1 }); // 第二次请求用户的帖子 const { user: userWithPosts } = await fetchGraphQL(` query GetUserWithPosts($id: ID!) { user(id: $id) { id posts { id title content } } } `, { id: 1 }); return { user, posts: userWithPosts.posts }; } 

正确的做法

// 正确的做法:使用Apollo Client import { ApolloClient, InMemoryCache, gql } from '@apollo/client'; // 创建Apollo Client实例 const client = new ApolloClient({ uri: 'https://api.example.com/graphql', cache: new InMemoryCache(), headers: { authorization: localStorage.getItem('token') || '' } }); // 定义查询 const GET_USER_WITH_POSTS = gql` query GetUserWithPosts($id: ID!) { user(id: $id) { id name email posts { id title content createdAt } } } `; // 定义变更 const CREATE_POST = gql` mutation CreatePost($input: PostInput!) { createPost(input: $input) { id title content createdAt } } `; // 在React组件中使用 import React from 'react'; import { useQuery, useMutation } from '@apollo/client'; function UserProfile({ userId }) { // 使用useQuery钩子获取数据 const { loading, error, data, refetch } = useQuery(GET_USER_WITH_POSTS, { variables: { id: userId }, // 缓存策略 fetchPolicy: 'cache-and-network' }); // 使用useMutation钩子执行变更 const [createPost, { loading: creating }] = useMutation(CREATE_POST, { // 变更后更新缓存 update(cache, { data: { createPost } }) { const { user } = cache.readQuery({ query: GET_USER_WITH_POSTS, variables: { id: userId } }); cache.writeQuery({ query: GET_USER_WITH_POSTS, variables: { id: userId }, data: { user: { ...user, posts: [...user.posts, createPost] } } }); } }); if (loading) return <div>加载中...</div>; if (error) return <div>错误:{error.message}</div>; const handleCreatePost = async (title, content) => { await createPost({ variables: { input: { title, content, userId } } }); }; return ( <div> <h2>{data.user.name}</h2> <p>{data.user.email}</p> <h3>帖子</h3> <ul> {data.user.posts.map(post => ( <li key={post.id}> <h4>{post.title}</h4> <p>{post.content}</p> <p>{post.createdAt}</p> </li> ))} </ul> <button onClick={() => refetch()}>刷新</button> <button onClick={() => handleCreatePost('新帖子', '帖子内容')} disabled={creating} > {creating ? '创建中...' : '创建帖子'} </button> </div> ); } // 正确的做法:使用URQL import { createClient, gql } from 'urql'; // 创建URQL客户端 const client = createClient({ url: 'https://api.example.com/graphql', fetchOptions: () => ({ headers: { authorization: localStorage.getItem('token') || '' } }) }); // 在React组件中使用 import React from 'react'; import { useQuery, useMutation } from 'urql'; function UserList() { const [result, reexecuteQuery] = useQuery({ query: gql` query GetUsers { users { id name email } } ` }); const { data, fetching, error } = result; if (fetching) return <div>加载中...</div>; if (error) return <div>错误:{error.message}</div>; return ( <div> <h2>用户列表</h2> <ul> {data.users.map(user => ( <li key={user.id}> {user.name} - {user.email} </li> ))} </ul> <button onClick={() => reexecuteQuery()}>刷新</button> </div> ); } // 正确的做法:使用Relay import { Environment, Network, RecordSource, Store, useLazyLoadQuery, graphql } from 'relay-runtime'; // 创建Relay环境 function fetchQuery(operation, variables) { return fetch('https://api.example.com/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', authorization: localStorage.getItem('token') || '' }, body: JSON.stringify({ query: operation.text, variables, }), }).then(response => { return response.json(); }); } const environment = new Environment({ network: Network.create(fetchQuery), store: new Store(new RecordSource()), }); // 定义查询 const UserQuery = graphql` query UserQuery($id: ID!) { user(id: $id) { id name email posts { edges { node { id title content } } } } } `; // 在React组件中使用 function UserDetail({ userId }) { const data = useLazyLoadQuery( UserQuery, { id: userId } ); return ( <div> <h2>{data.user.name}</h2> <p>{data.user.email}</p> <h3>帖子</h3> <ul> {data.user.posts.edges.map(edge => ( <li key={edge.node.id}> <h4>{edge.node.title}</h4> <p>{edge.node.content}</p> </li> ))} </ul> </div> ); } 

毒舌点评

看看,这才叫前端GraphQL客户端!不是简单地使用fetch请求,而是使用Apollo Client、URQL或Relay等专业的客户端工具。

记住,GraphQL客户端不仅仅是发送请求,还包括缓存管理、错误处理、类型生成等功能。这些工具可以大大简化你的前端代码,提高开发效率。

所以,别再觉得GraphQL复杂了,使用专业的客户端工具,让数据获取变得更加优雅!

总结

  • Apollo Client:功能强大,生态丰富,适合大型应用
  • URQL:轻量级,API简洁,适合中小型应用
  • Relay:Facebook开发,性能优异,适合大型应用
  • 缓存管理:智能缓存,减少重复请求
  • 类型安全:自动生成TypeScript类型
  • 错误处理:统一的错误处理机制
  • 变更管理:执行GraphQL变更并更新缓存
  • 开发工具:GraphQL Playground、Apollo DevTools等

前端GraphQL客户端,让数据获取变得更加优雅!

Read more

从HTML到性能优化:web.dev.cn国内镜像站课程资源详解

从HTML到性能优化:web.dev.cn国内镜像站课程资源详解 作为一名在国内摸爬滚打了多年的Web开发者,我深知获取一手、高质量且持续更新的技术资料有多不容易。很多时候,我们不得不依赖二手翻译、零散的博客文章,甚至是一些过时的教程,学习路径被切割得支离破碎。当听说谷歌开发者将web.dev和Chrome for Developers的内容通过.cn域名镜像进来时,我的第一反应是“终于来了”。这不仅仅是一个访问地址的变更,它更像是一座桥梁,将我们与全球Web开发的前沿标准、最佳实践直接连通。这篇文章,我想和你一起,像一个老朋友分享宝藏地图一样,深入探索web.dev.cn这个镜像站里到底藏了哪些宝贝,特别是那些从HTML基础到性能优化高阶的课程资源,看看它们如何能真正融入我们的日常学习和项目实战,帮你构建一套坚实且现代的Web开发知识体系。 1. 镜像站的价值:不止于“可访问” 对于许多中国开发者而言,技术信息的“可及性”一直是块心病。网络延迟、访问不稳定,这些物理层面的障碍常常让学习热情大打折扣。web.dev.cn镜像站的出现,首要解决的就是这个问题。页面加载速度显著提升

前端引入的JS加载失败页面功能无法使用?JS加载失败的终极解决方案

前端引入的JS加载失败页面功能无法使用?JS加载失败的终极解决方案

🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Micro麦可乐的博客 🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战 🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战 🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解 🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用 🍎 《前端技术》专栏以实战为主介绍日常开发中前端应用的一些功能以及技巧,均附有完整的代码示例 ✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧 💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程 🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整 👍《Spring Security》专栏中我们将逐步深入Spring Security的各个

在 OpenClaw 中安装 baidu-web-search skill(百度网页搜索技能)

在 OpenClaw 中安装 baidu-web-search skill(百度网页搜索技能),最推荐用 ClawHub CLI 一键安装,再配置百度千帆 API Key 即可使用。 一、前置准备 1. 安装 Node.js(v20+)与 npm/pnpm 验证安装 clawhub --version 全局安装 ClawHub CLI(OpenClaw 官方技能管理器) npminstall-g clawhub # 或国内加速pnpmadd-g clawhub 二、一键安装百度搜索技能 # 安装 baidu-search(百度网页搜索) clawhub install baidu-search --no-input * 安装路径:~/.openclaw/workspace/skills/baidu-search/

实战演练:基于快马平台快速构建一个支持tokenp钱包登录的DApp前端

今天想和大家分享一个实战项目:如何快速构建一个支持TokenP钱包登录的DApp前端。这个项目特别适合想学习Web3开发的初学者,整个过程在InsCode(快马)平台上完成,省去了本地环境配置的麻烦。 1. 项目准备 首先需要明确几个核心功能:钱包连接、用户信息展示、链上数据查询和退出登录。选择Next.js框架是因为它既支持服务端渲染,又能很好地与各种Web3库集成。Wagmi和Viem这两个库是目前最流行的以太坊开发工具组合,能大大简化钱包交互流程。 2. 钱包连接实现 在首页添加"使用钱包登录"按钮后,通过Wagmi提供的useConnect钩子就能轻松实现钱包连接功能。这里需要注意处理用户拒绝连接的情况,以及不同钱包提供商的兼容性问题。TokenP钱包作为移动端主流钱包,通过WalletConnect协议可以很好地与网页应用交互。 3. 用户信息展示 连接成功后,使用Wagmi的useAccount钩子获取用户的钱包地址。为了提升用户体验,我做了地址缩写处理(显示前4位和后4位),并在页面顶部显示欢迎信息。这里还添加了一个复制地址的小功能,方便用户操作。 4. 链上数