如何用Redux Toolkit结合Apollo创建客户端,通过client方法执行GraphQL操作?
用Redux Toolkit结合GraphQL客户端(client.mutate/client.query)实现查询与变更
核心思路
放弃React的useQuery/useMutation Hooks,改用Redux Toolkit的createAsyncThunk封装GraphQL客户端的client.query和client.mutate方法,将异步操作的加载、成功、失败状态统一交由Redux Store管理,同时针对登录场景单独处理令牌获取逻辑。
步骤1:基础配置
先安装必要依赖:
npm install @reduxjs/toolkit @apollo/client graphql
初始化GraphQL客户端
以Apollo Client为例,创建可全局访问的客户端实例:
// src/graphql/client.js import { ApolloClient, InMemoryCache } from '@apollo/client'; export const client = new ApolloClient({ uri: '你的GraphQL接口地址', cache: new InMemoryCache(), });
配置Redux Store
将GraphQL客户端通过extraArgument传入Thunk,方便在异步操作中调用:
// src/app/store.js import { configureStore } from '@reduxjs/toolkit'; import { client } from '../graphql/client'; import authReducer from '../features/auth/authSlice'; import dataReducer from '../features/data/dataSlice'; export const store = configureStore({ reducer: { auth: authReducer, data: dataReducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ thunk: { extraArgument: client, }, }), });
步骤2:登录Mutation(获取令牌)
创建Auth Slice,封装登录异步Thunk,仅在用户触发登录时调用client.mutate获取令牌:
// src/features/auth/authSlice.js import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import { gql } from '@apollo/client'; // 定义登录GraphQL Mutation const LOGIN_MUTATION = gql` mutation Login($email: String!, $password: String!) { login(email: $email, password: $password) { token user { id name } } } `; // 封装登录异步操作 export const login = createAsyncThunk( 'auth/login', async ({ email, password }, { extra: client, rejectWithValue }) => { try { const { data } = await client.mutate({ mutation: LOGIN_MUTATION, variables: { email, password }, }); // 持久化令牌,避免刷新丢失 localStorage.setItem('authToken', data.login.token); // 更新客户端授权头,后续请求自动携带令牌 client.setHeaders({ authorization: `Bearer ${data.login.token}`, }); return data.login; } catch (error) { return rejectWithValue(error.message); } } ); const authSlice = createSlice({ name: 'auth', initialState: { user: null, token: localStorage.getItem('authToken') || null, loading: false, error: null, }, reducers: { logout: (state) => { state.user = null; state.token = null; localStorage.removeItem('authToken'); }, }, extraReducers: (builder) => { builder .addCase(login.pending, (state) => { state.loading = true; state.error = null; }) .addCase(login.fulfilled, (state, action) => { state.loading = false; state.user = action.payload.user; state.token = action.payload.token; }) .addCase(login.rejected, (state, action) => { state.loading = false; state.error = action.payload; }); }, }); export const { logout } = authSlice.actions; export default authSlice.reducer;
登录组件调用示例
// src/components/Login.js import { useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { login } from '../features/auth/authSlice'; export default function Login() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const dispatch = useDispatch(); const { loading, error } = useSelector((state) => state.auth); const handleSubmit = (e) => { e.preventDefault(); dispatch(login({ email, password })); }; return ( <form onSubmit={handleSubmit}> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="邮箱" required /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="密码" required /> <button type="submit" disabled={loading}> {loading ? '登录中...' : '登录'} </button> {error && <p style={{ color: 'red' }}>{error}</p>} </form> ); }
步骤3:通用GraphQL查询与变更封装
针对其他业务场景,同样用createAsyncThunk封装client.query和client.mutate:
示例:用户列表查询与创建
// src/features/data/dataSlice.js import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import { gql } from '@apollo/client'; // 查询用户列表的GraphQL Query const GET_USERS_QUERY = gql` query GetUsers { users { id name email } } `; // 封装查询异步操作 export const fetchUsers = createAsyncThunk( 'data/fetchUsers', async (_, { extra: client, rejectWithValue }) => { try { const { data } = await client.query({ query: GET_USERS_QUERY, fetchPolicy: 'network-only', // 根据需求选择缓存策略 }); return data.users; } catch (error) { return rejectWithValue(error.message); } } ); // 创建用户的GraphQL Mutation const CREATE_USER_MUTATION = gql` mutation CreateUser($name: String!, $email: String!) { createUser(name: $name, email: $email) { id name email } } `; // 封装创建用户异步操作 export const createUser = createAsyncThunk( 'data/createUser', async (userData, { extra: client, rejectWithValue }) => { try { const { data } = await client.mutate({ mutation: CREATE_USER_MUTATION, variables: userData, }); return data.createUser; } catch (error) { return rejectWithValue(error.message); } } ); const dataSlice = createSlice({ name: 'data', initialState: { users: [], loading: false, error: null, }, extraReducers: (builder) => { builder // 处理用户列表查询 .addCase(fetchUsers.pending, (state) => { state.loading = true; state.error = null; }) .addCase(fetchUsers.fulfilled, (state, action) => { state.loading = false; state.users = action.payload; }) .addCase(fetchUsers.rejected, (state, action) => { state.loading = false; state.error = action.payload; }) // 处理创建用户 .addCase(createUser.pending, (state) => { state.loading = true; state.error = null; }) .addCase(createUser.fulfilled, (state, action) => { state.loading = false; state.users.push(action.payload); }) .addCase(createUser.rejected, (state, action) => { state.loading = false; state.error = action.payload; }); }, }); export default dataSlice.reducer;
用户列表组件调用示例
// src/components/UserList.js import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { fetchUsers, createUser } from '../features/data/dataSlice'; export default function UserList() { const dispatch = useDispatch(); const { users, loading, error } = useSelector((state) => state.data); useEffect(() => { dispatch(fetchUsers()); }, [dispatch]); const handleAddUser = () => { dispatch(createUser({ name: '新用户', email: 'new@example.com' })); }; if (loading) return <div>加载中...</div>; if (error) return <div>出错了:{error}</div>; return ( <div> <button onClick={handleAddUser}>添加用户</button> <ul> {users.map((user) => ( <li key={user.id}>{user.name} - {user.email}</li> ))} </ul> </div> ); }
关键注意点
- 令牌持久化:登录后将令牌存入
localStorage,客户端初始化时自动读取并设置授权头,避免刷新后丢失登录状态。 - 缓存策略:根据业务需求选择
client.query的fetchPolicy,比如cache-first优先使用缓存,network-only强制请求接口。 - 错误处理:在Thunk中用
rejectWithValue传递错误信息到Slice,统一管理错误状态。
内容的提问来源于stack exchange,提问作者Busenur Adıbelli
相关产品推荐
相关产品推荐

