通过 RTK 查询在 React Native 中高效处理数据
在本指南中,我们将介绍:
- CRUD 操作
- 分页
- Redux 通过 RTK 查询持久化
- 多个基本 URL 使用
- 受保护和公共路线
- 缓存管理和失效
RTK 查询 是内置于 Redux Toolkit (RTK) 中的高级数据获取和缓存工具。它通过为获取、缓存和更新数据等常见任务生成 Redux 切片和挂钩来简化 API 交互。主要功能包括:
- 自动缓存:RTK Query 缓存数据,并在数据失效时自动重新获取,确保 UI 始终拥有最新数据。
- 缓存失效:RTK 查询使用标签,让您定义何时应重新获取某些数据。这有助于保持缓存最新,无需手动更新数据。
- 自动生成的钩子:RTK Query 为每个 API 端点创建钩子,允许您使用简单的 React 钩子(useGetPostsQuery、useCreatePostMutation 等)调用 API。
- 错误处理:包括通过中间件的自定义错误处理,可以轻松捕获和显示错误。
- 简化的 Redux 集成:RTK Query 直接与 Redux 集成,因此您不需要额外的库来进行全局状态管理或缓存。
RTK 查询与 React 查询
React Query 和 RTK Query 都提供了 React 应用程序中的数据获取和缓存解决方案,但它们具有不同的优势和用例:
Feature | RTK Query | React Query |
---|---|---|
Purpose | Integrated within Redux for managing server data in Redux state. Best for apps already using Redux or requiring centralized global state. | Dedicated to managing server state with no Redux dependency. Great for apps focused on server state without Redux. |
Caching | Automatic caching with fine-grained cache invalidation through tags. Caches data globally within the Redux store. | Automatic caching with flexible cache control policies. Maintains a separate cache independent of Redux. |
Generated Hooks | Auto-generates hooks for endpoints, allowing mutations and queries using useQuery and useMutation hooks. | Provides hooks (useQuery, useMutation) that work independently from Redux, but require manual configuration of queries and mutations. |
DevTools | Integrated into Redux DevTools, making debugging seamless for Redux users. | Provides its own React Query DevTools, with detailed insight into query states and cache. |
Error Handling | Centralized error handling using Redux middleware. | Error handling within individual queries, with some centralized error-handling options. |
Redux Integration | Built directly into Redux, simplifying usage for Redux-based apps. | Not integrated with Redux by default, although Redux and React Query can be combined if needed. |
在 RTK 查询和 React 查询之间进行选择:
-
使用 RTK 查询如果:
- 您已经在使用 Redux,并且想要一个集成的、简化的数据获取解决方案。
- 您需要在 Redux 中进行集中式错误处理和开发工具集成。
-
使用 React 查询 如果:
- 您想要一个更轻量级的设置,无需 Redux 依赖。
- 您更喜欢单独的服务器状态管理,不需要全局应用程序状态。
本质上,RTK Query 非常适合以 Redux 为中心的应用程序,而 React Query 为没有 Redux 的项目或那些注重本地化服务器状态管理的项目提供了灵活性和简单性。
1. 商店配置和设置
// src/store/store.js import AsyncStorage from '@react-native-async-storage/async-storage'; import { combineReducers, configureStore, isRejectedWithValue } from '@reduxjs/toolkit'; import { setupListeners } from '@reduxjs/toolkit/query'; import { FLUSH, PAUSE, PERSIST, persistReducer, PURGE, REGISTER, REHYDRATE } from 'redux-persist'; import { authApi } from '../api/authApi'; import { postsApi } from '../api/postsApi'; import { usersApi } from '../api/usersApi'; import authSlice from '../features/auth/authSlice'; const persistConfig = { key: 'root', version: 1, storage: AsyncStorage, blacklist: ['auth', postsApi.middleware, usersApi.middleware, authApi.middleware], // these reduce will not persist data (NOTE: blacklist rtk api slices so that to use tags) // whitelist: ['users'], //these reduce will persist data }; const getEnhancers = (getDefaultEnhancers) => { if (process.env.NODE_ENV === 'development') { const reactotron = require('../reactotronConfig/ReactotronConfig').default; return getDefaultEnhancers().concat(reactotron.createEnhancer()); } return getDefaultEnhancers(); }; /** * On api error this will be called */ export const rtkQueryErrorLogger = (api) => (next) => (action) => { // RTK Query uses `createAsyncThunk` from redux-toolkit under the hood, so we're able to utilize these matchers! if (isRejectedWithValue(action)) { console.log('isRejectedWithValue', action.error, action.payload); alert(JSON.stringify(action)); // This is just an example. You can replace it with your preferred method for displaying notifications. } return next(action); }; const reducer = combineReducers({ auth: authSlice, [postsApi.reducerPath]: postsApi.reducer, [usersApi.reducerPath]: usersApi.reducer, [authApi.reducerPath]: authApi.reducer, }); const persistedReducer = persistReducer(persistConfig, reducer); const store = configureStore({ reducer: persistedReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], }, }).concat(postsApi.middleware, usersApi.middleware, authApi.middleware, rtkQueryErrorLogger), enhancers: getEnhancers, }); setupListeners(store.dispatch); export default store;
Redux Store (src/store/store.js):Redux store 是保存应用程序状态的主要结构。在您的设置中,它通过 redux-persist 进行了增强,可以在本地保存 Redux 状态的某些部分,因此即使应用程序重新启动,它们也会持续存在。
-
redux-persist:
- 用途:帮助保持部分 Redux 状态在应用程序会话中保持不变。
- 配置:persistConfig 对象指定 auth、postsApi 和 usersApi 不应被持久化(列入黑名单),这意味着它们的数据会在应用程序重新启动时重置。
- persistReducer 将减速器配置与持久化功能结合起来。
增强器:自定义增强器用于在开发模式下集成Reactotron,这是一个调试 Redux 操作、状态和网络请求的有用工具。这只在开发时激活,使调试更容易,而不影响生产。
-
中间件:
- RTK 查询中间件(postsApi.middleware、usersApi.middleware、authApi.middleware)添加自动缓存管理功能,提高数据获取效率。
- rtkQueryErrorLogger:自定义中间件在 API 调用失败时记录错误。它使用 RTK Query 的 isRejectedWithValue 函数来捕获和处理错误,允许您提醒用户有关问题或采取其他操作。
setupListeners:此功能可以在发生某些事件时自动重新获取数据,例如当应用程序重新获得焦点或从后台恢复时,为用户提供新鲜数据,而无需手动刷新。
2. RTK 查询的 API 定义
RTK Query 通过自动生成 Redux 切片、挂钩和缓存来简化 API 调用。以下是您定义的 API 的详细信息:
// src/store/store.js import AsyncStorage from '@react-native-async-storage/async-storage'; import { combineReducers, configureStore, isRejectedWithValue } from '@reduxjs/toolkit'; import { setupListeners } from '@reduxjs/toolkit/query'; import { FLUSH, PAUSE, PERSIST, persistReducer, PURGE, REGISTER, REHYDRATE } from 'redux-persist'; import { authApi } from '../api/authApi'; import { postsApi } from '../api/postsApi'; import { usersApi } from '../api/usersApi'; import authSlice from '../features/auth/authSlice'; const persistConfig = { key: 'root', version: 1, storage: AsyncStorage, blacklist: ['auth', postsApi.middleware, usersApi.middleware, authApi.middleware], // these reduce will not persist data (NOTE: blacklist rtk api slices so that to use tags) // whitelist: ['users'], //these reduce will persist data }; const getEnhancers = (getDefaultEnhancers) => { if (process.env.NODE_ENV === 'development') { const reactotron = require('../reactotronConfig/ReactotronConfig').default; return getDefaultEnhancers().concat(reactotron.createEnhancer()); } return getDefaultEnhancers(); }; /** * On api error this will be called */ export const rtkQueryErrorLogger = (api) => (next) => (action) => { // RTK Query uses `createAsyncThunk` from redux-toolkit under the hood, so we're able to utilize these matchers! if (isRejectedWithValue(action)) { console.log('isRejectedWithValue', action.error, action.payload); alert(JSON.stringify(action)); // This is just an example. You can replace it with your preferred method for displaying notifications. } return next(action); }; const reducer = combineReducers({ auth: authSlice, [postsApi.reducerPath]: postsApi.reducer, [usersApi.reducerPath]: usersApi.reducer, [authApi.reducerPath]: authApi.reducer, }); const persistedReducer = persistReducer(persistConfig, reducer); const store = configureStore({ reducer: persistedReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], }, }).concat(postsApi.middleware, usersApi.middleware, authApi.middleware, rtkQueryErrorLogger), enhancers: getEnhancers, }); setupListeners(store.dispatch); export default store;
-
authApi (src/api/authApi.js):
- 定义登录突变,将用户凭据(例如用户名、密码)发送到服务器进行身份验证。
- onQueryStarted:登录成功后,它使用 setToken 操作将返回的令牌存储在 Redux 中。这可以实现对其他端点的安全、经过身份验证的请求。
// src/api/authApi.js import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; import { setToken } from '../features/auth/authSlice'; export const authApi = createApi({ reducerPath: 'authApi', baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com/auth/', }), endpoints: (builder) => ({ login: builder.mutation({ query: (credentials) => ({ url: 'login', method: 'POST', body: credentials, }), async onQueryStarted(arg, { dispatch, queryFulfilled }) { try { const { data } = await queryFulfilled; dispatch(setToken(data.accessToken)); // Store the token in Redux } catch (error) { console.error('Login error:', error); } }, }), }), }); export const { useLoginMutation } = authApi;
-
postsApi (src/api/postsApi.js):
-
CRUD 操作:帖子 API 包含多个与帖子交互的端点(获取、创建、更新、删除)。
- getPosts:获取分页帖子,这意味着它以较小的块(页面)检索数据,从而提高性能和加载时间。
- createPost、updatePost 和 deletePost:其中每一个都执行不同的操作(创建、更新或删除帖子)。
- 用于缓存的标签:每个端点都使用标签(例如,{ type: 'Posts', id })自动管理缓存失效和刷新。例如,创建或删除帖子会使缓存失效,从而提示 getPosts 无需手动干预即可获取新数据。
-
CRUD 操作:帖子 API 包含多个与帖子交互的端点(获取、创建、更新、删除)。
// src/store/store.js import AsyncStorage from '@react-native-async-storage/async-storage'; import { combineReducers, configureStore, isRejectedWithValue } from '@reduxjs/toolkit'; import { setupListeners } from '@reduxjs/toolkit/query'; import { FLUSH, PAUSE, PERSIST, persistReducer, PURGE, REGISTER, REHYDRATE } from 'redux-persist'; import { authApi } from '../api/authApi'; import { postsApi } from '../api/postsApi'; import { usersApi } from '../api/usersApi'; import authSlice from '../features/auth/authSlice'; const persistConfig = { key: 'root', version: 1, storage: AsyncStorage, blacklist: ['auth', postsApi.middleware, usersApi.middleware, authApi.middleware], // these reduce will not persist data (NOTE: blacklist rtk api slices so that to use tags) // whitelist: ['users'], //these reduce will persist data }; const getEnhancers = (getDefaultEnhancers) => { if (process.env.NODE_ENV === 'development') { const reactotron = require('../reactotronConfig/ReactotronConfig').default; return getDefaultEnhancers().concat(reactotron.createEnhancer()); } return getDefaultEnhancers(); }; /** * On api error this will be called */ export const rtkQueryErrorLogger = (api) => (next) => (action) => { // RTK Query uses `createAsyncThunk` from redux-toolkit under the hood, so we're able to utilize these matchers! if (isRejectedWithValue(action)) { console.log('isRejectedWithValue', action.error, action.payload); alert(JSON.stringify(action)); // This is just an example. You can replace it with your preferred method for displaying notifications. } return next(action); }; const reducer = combineReducers({ auth: authSlice, [postsApi.reducerPath]: postsApi.reducer, [usersApi.reducerPath]: usersApi.reducer, [authApi.reducerPath]: authApi.reducer, }); const persistedReducer = persistReducer(persistConfig, reducer); const store = configureStore({ reducer: persistedReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], }, }).concat(postsApi.middleware, usersApi.middleware, authApi.middleware, rtkQueryErrorLogger), enhancers: getEnhancers, }); setupListeners(store.dispatch); export default store;
-
usersApi (src/api/usersApi.js):
- 此 API 获取经过身份验证的用户的个人资料,根据 Redux 中的令牌设置授权标头。
- Headers:prepareHeaders 动态地将令牌附加到每个请求(如果可用),从而允许安全且授权的 API 请求。
3. Auth Slice (src/features/auth/authSlice.js)
// src/api/authApi.js import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; import { setToken } from '../features/auth/authSlice'; export const authApi = createApi({ reducerPath: 'authApi', baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com/auth/', }), endpoints: (builder) => ({ login: builder.mutation({ query: (credentials) => ({ url: 'login', method: 'POST', body: credentials, }), async onQueryStarted(arg, { dispatch, queryFulfilled }) { try { const { data } = await queryFulfilled; dispatch(setToken(data.accessToken)); // Store the token in Redux } catch (error) { console.error('Login error:', error); } }, }), }), }); export const { useLoginMutation } = authApi;
- authSlice:Redux 切片管理特定的状态,在本例中为用户身份验证。
- 状态管理:authSlice 保留用户的令牌,用于访问受保护的 API 端点。
-
行动:
- setToken:在 Redux 状态中存储身份验证令牌。
- logout:从 Redux 中清除令牌,有效地将用户注销。
4. 用于调试的Reactotron (src/reactotronConfig/ReactotronConfig.js)
// src/api/postsApi.js import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; // Define the postsApi slice with RTK Query export const postsApi = createApi({ // Unique key for the API slice in Redux state reducerPath: 'postsApi', // Configure base query settings, including the base URL for all requests baseQuery: fetchBaseQuery({ baseUrl: 'https://jsonplaceholder.typicode.com', }), // Define cache tag types for automatic cache invalidation tagTypes: ['Posts'], // Define API endpoints (queries and mutations) endpoints: (builder) => ({ // Query to fetch a paginated list of posts getPosts: builder.query({ // URL and parameters for paginated posts query: ({ page = 1, limit = 10 }) => `/posts?_page=${page}&_limit=${limit}`, // Tagging posts to automatically refresh this cache when needed providesTags: (result) => result ? [...result.map(({ id }) => ({ type: 'Posts', id })), { type: 'Posts', id: 'LIST' }] : [{ type: 'Posts', id: 'LIST' }], }), // Query to fetch a single post by its ID getPostById: builder.query({ // Define query with post ID in the URL path query: (id) => `/posts/${id}`, // Tag individual post by ID for selective cache invalidation providesTags: (result, error, id) => [{ type: 'Posts', id }], }), // Mutation to create a new post createPost: builder.mutation({ // Configure the POST request details and payload query: (newPost) => ({ url: '/posts', method: 'POST', body: newPost, }), // Invalidate all posts (paginated list) to refresh after creating a post invalidatesTags: [{ type: 'Posts', id: 'LIST' }], }), // Mutation to update an existing post by its ID updatePost: builder.mutation({ // Define the PUT request with post ID and updated data in the payload query: ({ id, ...updatedData }) => ({ url: `/posts/${id}`, method: 'PUT', body: updatedData, }), // Invalidate cache for both the updated post and the paginated list invalidatesTags: (result, error, { id }) => [ { type: 'Posts', id }, { type: 'Posts', id: 'LIST' }, ], }), // Mutation to delete a post by its ID deletePost: builder.mutation({ // Define the DELETE request with post ID in the URL path query: (id) => ({ url: `/posts/${id}`, method: 'DELETE', }), // Invalidate cache for the deleted post and the paginated list invalidatesTags: (result, error, id) => [ { type: 'Posts', id }, { type: 'Posts', id: 'LIST' }, ], }), }), }); // Export generated hooks for each endpoint to use them in components export const { useGetPostsQuery, // Use this when you want data to be fetched automatically as the component mounts or when the query parameters change. useLazyGetPostsQuery, // Use this when you need more control over when the query runs, such as in response to a user action (e.g., clicking a button), conditional fetching, or specific events. useGetPostByIdQuery, useCreatePostMutation, useUpdatePostMutation, useDeletePostMutation, } = postsApi;
- Reactotron:Reactotron 是一个调试工具,有助于跟踪 Redux 状态更改、监控 API 请求和检查日志。
- Setup:配置为捕获 console.log 输出和 Redux 操作。在开发模式下,此设置提供了一种强大的调试方法,无需添加额外的代码或改变生产性能。
5. 主要应用组件
// src/api/usersApi.js import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; export const usersApi = createApi({ reducerPath: 'usersApi', baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com', prepareHeaders: (headers, { getState }) => { // Get the token from the Redux auth state const { token } = getState().auth; // If the token exists, set it in the Authorization header if (token) { headers.set('Authorization', `Bearer ${token}`); } // Optional: include credentials if needed by the API headers.set('credentials', 'include'); return headers; }, }), endpoints: (builder) => ({ // Fetch user profile with token in Authorization header getUserProfile: builder.query({ query: () => '/auth/me', }), }), }); export const { useGetUserProfileQuery } = usersApi;
-
应用程序组件(src/App.js):
- App 组件将整个应用程序包装在 Provider(以使 Redux 可用)和 PersistGate(延迟渲染,直到检索到持久状态)中。
- PersistGate 确保在应用程序显示之前持久加载数据,减少加载时间不一致。
// src/MainApp.js 从 'react' 导入 React, { useEffect, useState }; 进口 { 活动指示器, 按钮, 平面列表, 莫代尔, 刷新控制, 样式表, 文本, 文本输入, 看法, 来自 'react-native'; 从 'react-native-safe-area-context' 导入 { SafeAreaView } ; 从 'react-redux' 导入 { useDispatch, useSelector }; 从 './api/authApi' 导入 { useLoginMutation } ; 进口 { 使用CreatePostMutation, 使用DeletePostMutation, 使用GetPosts查询, 使用LazyGetPosts查询, 使用更新后突变, 来自 './api/postsApi'; 从'./api/usersApi'导入{useGetUserProfileQuery}; 导入{注销}来自“./features/auth/authSlice”; const MainApp = () =>; { const [newPostTitle, setNewPostTitle] = useState(''); const [页面,setPage] = useState(1); const [postsData, setPostsData] = useState([]); const [刷新,setRefreshing] = useState(false); const [isModalVisible, setModalVisible] = useState(false); const 调度 = useDispatch(); const token = useSelector((state) => state.auth.token); // 登录突变 const [login, { isLoading: isLoggingIn }] = useLoginMutation(); // 当令牌可用时获取用户配置文件 const { 数据:userProfile,重新获取:refetchUserProfile } = useGetUserProfileQuery(未定义,{ 跳过:!令牌, }); // 获取分页帖子 常量{ 数据:帖子, 正在加载, 正在获取, 是错误, 重新获取, } = useGetPostsQuery({ 页数, 限制: 10 }); // 当你想在屏幕加载时获取数据时,使用 useQuery 钩子。例如,在个人资料屏幕上获取用户个人资料。 // 使用惰性查询刷新直接获取第1页 const [triggerFetchFirstPage,{ 数据:lazyData }] = useLazyGetPostsQuery(); // useLazyquery 当你想要控制 api 调用时使用,比如按钮点击。 const [createPost] = useCreatePostMutation(); const [updatePost] = useUpdatePostMutation(); const [deletePost] = useDeletePostMutation(); useEffect(() => { 如果(帖子){ setPostsData((prevData) => (页 === 1 ? posts : [...prevData, ...posts])); } }, [帖子, 页]); // 登录处理程序 const handleLogin = async () =>; { 尝试 { const 凭证 = { 用户名:'emilys',密码:'emilyspass' }; 等待登录(凭据); console.log('用户配置文件', 用户配置文件); 重新获取用户配置文件(); } 捕获(错误){ console.error('登录失败:', error); } }; const handleRefresh = async () =>; { 设置刷新(真); 设置页面(1); // 将页面重置为 1 以进行下一个滚动 setPostsData([]); // 清除数据以避免重复 // 显式触发第一页获取 const { 数据 } = 等待触发FetchFirstPage({ 页数: 1, 限制: 10 }); 如果(数据){ 设置帖子数据(数据); // 将帖子数据设置为第一页的结果 } 设置刷新(假); }; // 创建一个新帖子,将其添加到顶部,然后重新获取列表 const handleCreatePost = async () =>; { 如果(新帖子标题){ const { data: newPost } = wait createPost({ title: newPostTitle, body: '新帖子内容' }); 设置新帖子标题(''); setPostsData((prevData) => [newPost, ...prevData]); 重新获取(); } }; // 更新现有帖子并将“HASAN”添加到其标题中 const handleUpdatePost = async (post) =>; { const { 数据:updatePost } = 等待 updatePost({ id:帖子id, 标题: `${post.title} HASAN`, }); setPostsData((prevData) =>; prevData.map((item) => (item?.id === UpdatedPost?.id ?updatedPost : item)) ); }; // 删除帖子并立即将其从 UI 中删除 const handleDeletePost = async (id) =>; { 等待deletePost(id); setPostsData((prevData) => prevData.filter((post) => post.id !== id)); }; // 加载更多帖子以实现无限滚动 const loadMorePosts = () =>; { if (!isFetching) { setPage((上一页) => 上一页 1); } }; // 切换模态可见性 consttoggleModal = () =>; { setModalVisible(!isModalVisible); }; if (isLoading && page === 1) return <text>正在加载...</text>; if (isError) return <text> 获取帖子时出错。</text>; 返回 (
-
MainApp 组件 (src/MainApp.js):
- 状态和挂钩:管理本地状态(例如,用于帖子分页)和诸如 useLoginMutation 之类的挂钩来触发特定事件的操作。
-
登录:
- 使用 useLoginMutation 登录用户,然后触发 refetchUserProfile 加载用户配置文件数据。
- 条件查询:仅在存在有效令牌时才获取用户个人资料(跳过:!token),减少不必要的 API 调用。
-
获取帖子:
- 使用 useGetPostsQuery 获取分页帖子,通过在用户滚动时获取更多数据来支持无限滚动。
- 刷新控件:允许用户刷新帖子列表,对于移动设备上的下拉刷新功能很有用。
-
创建、更新、删除帖子:
- Create:调用createPost,立即更新帖子列表,新帖子位于顶部。
- 更新:更新时将“HASAN”附加到帖子标题。
- 删除:删除帖子并更新 UI,无需重新加载页面,这要归功于 deletePost 的缓存失效。
-
用户界面元素:
- 模态显示用户个人资料。仅当加载用户配置文件数据时才会出现配置文件按钮,从而增强用户体验。
- FlatList:以可滚动、分页格式显示帖子,增强可用性。
概括:
您的 React Native 应用程序使用 Redux Toolkit (RTK) 查询 来实现高效的数据管理和 API 交互。设置包括:
存储配置:带有 redux-persist 的 Redux 存储,用于跨应用程序会话保存特定数据,用于错误日志记录的自定义中间件,以及用于在开发模式下进行调试的 Reactotron。
-
带 RTK 查询的 API:
- authApi 通过登录突变处理身份验证,将令牌存储在 Redux 中。
- postsApi 为帖子提供 CRUD 操作,在添加、更新或删除帖子时使用缓存标签自动刷新数据。
- usersApi 使用基于动态令牌的授权标头获取用户配置文件。
Auth Slice:管理身份验证令牌并提供在登录/注销时设置或清除令牌的操作。
-
应用程序和主应用程序组件:
- 主应用程序将组件包装在 Provider 和 PersistGate 中,确保在渲染之前加载状态。
- MainApp 管理帖子的获取、创建、更新和删除。它有条件地加载数据(例如,仅当令牌存在时才获取用户配置文件),支持分页和无限滚动
- 使用 FlatList 作为分页帖子列表,使用模式作为个人资料,并使用基本样式来实现干净、有组织的布局。
完整代码->
以上是通过 RTK 查询在 React Native 中高效处理数据的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

JavaScript是现代Web开发的核心语言,因其多样性和灵活性而广泛应用。1)前端开发:通过DOM操作和现代框架(如React、Vue.js、Angular)构建动态网页和单页面应用。2)服务器端开发:Node.js利用非阻塞I/O模型处理高并发和实时应用。3)移动和桌面应用开发:通过ReactNative和Electron实现跨平台开发,提高开发效率。

本文展示了与许可证确保的后端的前端集成,并使用Next.js构建功能性Edtech SaaS应用程序。 前端获取用户权限以控制UI的可见性并确保API要求遵守角色库

我使用您的日常技术工具构建了功能性的多租户SaaS应用程序(一个Edtech应用程序),您可以做同样的事情。 首先,什么是多租户SaaS应用程序? 多租户SaaS应用程序可让您从唱歌中为多个客户提供服务

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。
