React学习教程,从入门到精通,React 前后端交互技术详解(29)

React 前后端交互技术详解

1. Promise 对象

1.1 Promise 简介

Promise 是 JavaScript 中用于处理异步操作的对象,它代表一个异步操作的最终完成(或失败)及其结果值。

1.2 Promise 的原理

Promise 有三种状态:

  • pending:初始状态,既不是成功,也不是失败状态
  • fulfilled:操作成功完成
  • rejected:操作失败

状态一旦改变,就不会再变。

1.3 Promise 的使用方法

创建 Promise
javascript 复制代码
// 创建 Promise 对象
const myPromise = new Promise((resolve, reject) => {
  // 异步操作
  const success = true; // 模拟操作结果
  
  if (success) {
    resolve("操作成功!"); // 成功时调用 resolve
  } else {
    reject("操作失败!"); // 失败时调用 reject
  }
});
Promise 的基本使用
javascript 复制代码
// 使用 then、catch 处理 Promise
myPromise
  .then((result) => {
    console.log("成功:", result); // 处理成功结果
  })
  .catch((error) => {
    console.log("失败:", error); // 处理错误
  })
  .finally(() => {
    console.log("操作完成"); // 无论成功失败都会执行
  });
Promise 链式调用
javascript 复制代码
// 模拟异步函数
function asyncTask1() {
  return new Promise((resolve) => {
    setTimeout(() => resolve("任务1完成"), 1000);
  });
}

function asyncTask2(data) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`${data} -> 任务2完成`), 1000);
  });
}

function asyncTask3(data) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = Math.random() > 0.5;
      if (success) {
        resolve(`${data} -> 任务3完成`);
      } else {
        reject("任务3失败");
      }
    }, 1000);
  });
}

// 链式调用
asyncTask1()
  .then(result => {
    console.log(result);
    return asyncTask2(result); // 返回新的 Promise
  })
  .then(result => {
    console.log(result);
    return asyncTask3(result);
  })
  .then(result => {
    console.log("最终结果:", result);
  })
  .catch(error => {
    console.error("链式调用出错:", error);
  });
Promise 静态方法
javascript 复制代码
// Promise.all - 所有 Promise 都成功时返回结果数组
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3])
  .then((values) => {
    console.log(values); // [3, 42, "foo"]
  });

// Promise.race - 第一个完成的 Promise 的结果
Promise.race([promise1, promise3])
  .then((value) => {
    console.log(value); // 3(promise1 立即完成)
  });

// Promise.allSettled - 所有 Promise 完成后返回状态和结果
Promise.allSettled([promise1, Promise.reject("错误")])
  .then((results) => {
    console.log(results);
    // [{status: "fulfilled", value: 3}, {status: "rejected", reason: "错误"}]
  });

2. HTTP 客户端 - Axios

2.1 Axios 简介

Axios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 Node.js。

2.2 安装和引入

bash 复制代码
npm install axios
javascript 复制代码
import axios from 'axios';

2.3 Axios 基本使用

GET 请求
javascript 复制代码
// 基本的 GET 请求
axios.get('https://jsonplaceholder.typicode.com/posts')
  .then(response => {
    console.log('响应数据:', response.data);
    console.log('状态码:', response.status);
    console.log('状态文本:', response.statusText);
    console.log('响应头:', response.headers);
  })
  .catch(error => {
    console.error('请求失败:', error);
  });

// 带参数的 GET 请求
axios.get('https://jsonplaceholder.typicode.com/posts', {
  params: {
    userId: 1,
    _limit: 5
  }
})
  .then(response => {
    console.log('用户1的前5篇文章:', response.data);
  });

// 使用 async/await
async function fetchPosts() {
  try {
    const response = await axios.get('https://jsonplaceholder.typicode.com/posts/1');
    console.log('文章详情:', response.data);
  } catch (error) {
    console.error('获取文章失败:', error);
  }
}
POST 请求
javascript 复制代码
// 创建新文章
const newPost = {
  title: '新的文章标题',
  body: '这是文章内容',
  userId: 1
};

axios.post('https://jsonplaceholder.typicode.com/posts', newPost)
  .then(response => {
    console.log('创建成功:', response.data);
  })
  .catch(error => {
    console.error('创建失败:', error);
  });

// 带配置的 POST 请求
axios.post('https://jsonplaceholder.typicode.com/posts', newPost, {
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your-token'
  },
  timeout: 5000 // 5秒超时
})
  .then(response => {
    console.log('响应:', response);
  });
PUT 和 DELETE 请求
javascript 复制代码
// PUT 请求 - 更新资源
const updatedPost = {
  id: 1,
  title: '更新后的标题',
  body: '更新后的内容',
  userId: 1
};

axios.put('https://jsonplaceholder.typicode.com/posts/1', updatedPost)
  .then(response => {
    console.log('更新成功:', response.data);
  });

// DELETE 请求 - 删除资源
axios.delete('https://jsonplaceholder.typicode.com/posts/1')
  .then(response => {
    console.log('删除成功:', response.data);
  });

2.4 Axios 配置

全局配置
javascript 复制代码
// 设置全局配置
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
axios.defaults.timeout = 10000;
axios.defaults.headers.common['Authorization'] = 'Bearer your-token';
axios.defaults.headers.post['Content-Type'] = 'application/json';

// 现在可以直接使用相对路径
axios.get('/posts')
  .then(response => {
    console.log(response.data);
  });
创建实例配置
javascript 复制代码
// 创建自定义 Axios 实例
const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  }
});

// 添加请求拦截器
apiClient.interceptors.request.use(
  (config) => {
    // 在发送请求之前做些什么
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    console.log('发送请求:', config);
    return config;
  },
  (error) => {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

// 添加响应拦截器
apiClient.interceptors.response.use(
  (response) => {
    // 对响应数据做点什么
    console.log('收到响应:', response);
    return response;
  },
  (error) => {
    // 对响应错误做点什么
    if (error.response.status === 401) {
      // 处理未授权错误
      localStorage.removeItem('token');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);
并发请求
javascript 复制代码
// 同时发送多个请求
function getUserAndPosts(userId) {
  return axios.all([
    axios.get(`/users/${userId}`),
    axios.get(`/posts?userId=${userId}`)
  ]);
}

getUserAndPosts(1)
  .then(axios.spread((userResponse, postsResponse) => {
    console.log('用户信息:', userResponse.data);
    console.log('用户文章:', postsResponse.data);
  }))
  .catch(error => {
    console.error('获取数据失败:', error);
  });

3. React 中的前后端交互

3.1 在 React 组件中使用 Axios

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

function PostsComponent() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  // 组件挂载时获取数据
  useEffect(() => {
    const fetchPosts = async () => {
      try {
        setLoading(true);
        const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
        setPosts(response.data);
        setError(null);
      } catch (err) {
        setError('获取文章失败: ' + err.message);
        setPosts([]);
      } finally {
        setLoading(false);
      }
    };

    fetchPosts();
  }, []); // 空依赖数组表示只在组件挂载时执行

  // 创建新文章
  const createPost = async (postData) => {
    try {
      const response = await axios.post('https://jsonplaceholder.typicode.com/posts', postData);
      setPosts(prevPosts => [response.data, ...prevPosts]);
      return response.data;
    } catch (err) {
      setError('创建文章失败: ' + err.message);
      throw err;
    }
  };

  // 删除文章
  const deletePost = async (postId) => {
    try {
      await axios.delete(`https://jsonplaceholder.typicode.com/posts/${postId}`);
      setPosts(prevPosts => prevPosts.filter(post => post.id !== postId));
    } catch (err) {
      setError('删除文章失败: ' + err.message);
    }
  };

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误: {error}</div>;

  return (
    <div>
      <h1>文章列表</h1>
      <button onClick={() => createPost({ title: '新文章', body: '内容', userId: 1 })}>
        创建新文章
      </button>
      {posts.map(post => (
        <div key={post.id} style={{ border: '1px solid #ccc', margin: '10px', padding: '10px' }}>
          <h3>{post.title}</h3>
          <p>{post.body}</p>
          <button onClick={() => deletePost(post.id)}>删除</button>
        </div>
      ))}
    </div>
  );
}

export default PostsComponent;

3.2 自定义 Hook 封装 API 调用

jsx 复制代码
import { useState, useEffect } from 'react';
import axios from 'axios';

// 自定义 Hook 用于 API 调用
function useApi(url, options = {}) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  const fetchData = async () => {
    try {
      setLoading(true);
      setError(null);
      const response = await axios(url, options);
      setData(response.data);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, [url]); // 当 URL 变化时重新获取数据

  return { data, loading, error, refetch: fetchData };
}

// 使用自定义 Hook 的组件
function UserProfile({ userId }) {
  const { data: user, loading, error, refetch } = useApi(
    `https://jsonplaceholder.typicode.com/users/${userId}`
  );

  if (loading) return <div>加载用户信息...</div>;
  if (error) return <div>错误: {error} <button onClick={refetch}>重试</button></div>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>邮箱: {user.email}</p>
      <p>电话: {user.phone}</p>
    </div>
  );
}

4. 综合案例

4.1 完整的博客应用示例

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

// 配置 Axios 实例
const api = axios.create({
  baseURL: 'https://jsonplaceholder.typicode.com',
  timeout: 10000,
});

// 博客应用主组件
function BlogApp() {
  const [posts, setPosts] = useState([]);
  const [users, setUsers] = useState([]);
  const [selectedUser, setSelectedUser] = useState('');
  const [newPost, setNewPost] = useState({ title: '', body: '' });
  const [loading, setLoading] = useState(false);

  // 获取所有用户
  useEffect(() => {
    const fetchUsers = async () => {
      try {
        const response = await api.get('/users');
        setUsers(response.data);
      } catch (error) {
        console.error('获取用户失败:', error);
      }
    };
    fetchUsers();
  }, []);

  // 根据选择的用户获取文章
  useEffect(() => {
    if (selectedUser) {
      fetchUserPosts(selectedUser);
    }
  }, [selectedUser]);

  const fetchUserPosts = async (userId) => {
    setLoading(true);
    try {
      const response = await api.get(`/posts?userId=${userId}`);
      setPosts(response.data);
    } catch (error) {
      console.error('获取文章失败:', error);
    } finally {
      setLoading(false);
    }
  };

  const handleCreatePost = async (e) => {
    e.preventDefault();
    if (!newPost.title || !newPost.body || !selectedUser) {
      alert('请填写完整信息');
      return;
    }

    try {
      const postData = {
        ...newPost,
        userId: parseInt(selectedUser)
      };

      const response = await api.post('/posts', postData);
      
      // 模拟添加新文章(JSONPlaceholder 不会真正创建)
      setPosts(prevPosts => [response.data, ...prevPosts]);
      setNewPost({ title: '', body: '' });
      alert('文章创建成功!');
    } catch (error) {
      console.error('创建文章失败:', error);
      alert('创建文章失败');
    }
  };

  const handleDeletePost = async (postId) => {
    if (!window.confirm('确定要删除这篇文章吗?')) return;

    try {
      await api.delete(`/posts/${postId}`);
      setPosts(prevPosts => prevPosts.filter(post => post.id !== postId));
      alert('文章删除成功!');
    } catch (error) {
      console.error('删除文章失败:', error);
      alert('删除文章失败');
    }
  };

  return (
    <div style={{ padding: '20px', maxWidth: '800px', margin: '0 auto' }}>
      <h1>博客管理系统</h1>
      
      {/* 用户选择 */}
      <div style={{ marginBottom: '20px' }}>
        <label>选择用户: </label>
        <select 
          value={selectedUser} 
          onChange={(e) => setSelectedUser(e.target.value)}
          style={{ marginLeft: '10px', padding: '5px' }}
        >
          <option value="">请选择用户</option>
          {users.map(user => (
            <option key={user.id} value={user.id}>
              {user.name}
            </option>
          ))}
        </select>
      </div>

      {/* 创建新文章表单 */}
      <form onSubmit={handleCreatePost} style={{ marginBottom: '30px', padding: '20px', border: '1px solid #ddd' }}>
        <h3>创建新文章</h3>
        <div style={{ marginBottom: '10px' }}>
          <input
            type="text"
            placeholder="文章标题"
            value={newPost.title}
            onChange={(e) => setNewPost({...newPost, title: e.target.value})}
            style={{ width: '100%', padding: '8px', marginBottom: '10px' }}
          />
        </div>
        <div style={{ marginBottom: '10px' }}>
          <textarea
            placeholder="文章内容"
            value={newPost.body}
            onChange={(e) => setNewPost({...newPost, body: e.target.value})}
            style={{ width: '100%', padding: '8px', height: '100px', marginBottom: '10px' }}
          />
        </div>
        <button type="submit" disabled={!selectedUser}>
          发布文章
        </button>
      </form>

      {/* 文章列表 */}
      <div>
        <h2>文章列表 {selectedUser && `- 用户${selectedUser}`}</h2>
        {loading ? (
          <div>加载中...</div>
        ) : (
          <div>
            {posts.length === 0 ? (
              <div>暂无文章</div>
            ) : (
              posts.map(post => (
                <div key={post.id} style={{ 
                  border: '1px solid #ccc', 
                  margin: '10px 0', 
                  padding: '15px',
                  borderRadius: '5px'
                }}>
                  <h3>{post.title}</h3>
                  <p>{post.body}</p>
                  <div style={{ marginTop: '10px' }}>
                    <button 
                      onClick={() => handleDeletePost(post.id)}
                      style={{ 
                        backgroundColor: '#ff4444', 
                        color: 'white', 
                        border: 'none', 
                        padding: '5px 10px',
                        borderRadius: '3px',
                        cursor: 'pointer'
                      }}
                    >
                      删除
                    </button>
                  </div>
                </div>
              ))
            )}
          </div>
        )}
      </div>
    </div>
  );
}

export default BlogApp;

4.2 错误处理和加载状态优化

jsx 复制代码
import React from 'react';

// 高阶组件:处理加载和错误状态
function withApiStatus(WrappedComponent) {
  return function EnhancedComponent({ loading, error, ...props }) {
    if (loading) {
      return (
        <div style={{ textAlign: 'center', padding: '20px' }}>
          <div>加载中...</div>
        </div>
      );
    }

    if (error) {
      return (
        <div style={{ textAlign: 'center', padding: '20px', color: 'red' }}>
          <div>错误: {error}</div>
          <button onClick={props.onRetry}>重试</button>
        </div>
      );
    }

    return <WrappedComponent {...props} />;
  };
}

// 使用高阶组件的示例
const PostListWithStatus = withApiStatus(({ posts, onDelete }) => (
  <div>
    {posts.map(post => (
      <div key={post.id} style={{ border: '1px solid #ccc', margin: '10px', padding: '10px' }}>
        <h3>{post.title}</h3>
        <p>{post.body}</p>
        <button onClick={() => onDelete(post.id)}>删除</button>
      </div>
    ))}
  </div>
));

5. 最佳实践和注意事项

5.1 错误处理最佳实践

javascript 复制代码
// 统一的错误处理函数
function handleApiError(error) {
  if (error.response) {
    // 服务器响应了错误状态码
    switch (error.response.status) {
      case 401:
        // 未授权,跳转到登录页
        window.location.href = '/login';
        break;
      case 403:
        // 禁止访问
        alert('没有权限执行此操作');
        break;
      case 404:
        // 资源不存在
        alert('请求的资源不存在');
        break;
      case 500:
        // 服务器内部错误
        alert('服务器内部错误,请稍后重试');
        break;
      default:
        alert(`请求失败: ${error.response.status}`);
    }
  } else if (error.request) {
    // 请求已发出但没有收到响应
    alert('网络错误,请检查网络连接');
  } else {
    // 其他错误
    alert(`请求配置错误: ${error.message}`);
  }
  
  // 记录错误日志
  console.error('API Error:', error);
}

5.2 取消请求

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

function SearchComponent() {
  const [results, setResults] = useState([]);
  const [query, setQuery] = useState('');

  useEffect(() => {
    // 创建取消令牌
    const source = axios.CancelToken.source();

    const search = async () => {
      if (!query) {
        setResults([]);
        return;
      }

      try {
        const response = await axios.get(`/api/search?q=${query}`, {
          cancelToken: source.token
        });
        setResults(response.data);
      } catch (error) {
        if (!axios.isCancel(error)) {
          console.error('搜索失败:', error);
        }
      }
    };

    // 防抖搜索
    const timeoutId = setTimeout(search, 300);

    // 清理函数:取消未完成的请求
    return () => {
      clearTimeout(timeoutId);
      source.cancel('取消之前的搜索请求');
    };
  }, [query]);

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="搜索..."
      />
      <ul>
        {results.map(result => (
          <li key={result.id}>{result.name}</li>
        ))}
      </ul>
    </div>
  );
}

这份详细的指南涵盖了 React 前后端交互的核心概念和实践,从基础的 Promise 到复杂的 Axios 配置和 React 集成,提供了完整的代码示例和最佳实践。

相关推荐
天天进步20152 小时前
React Server Components详解:服务端渲染的新纪元
开发语言·前端·javascript
lvchaoq2 小时前
react的依赖项数组
前端·javascript·react.js
月盈缺2 小时前
学习嵌入式的第三十九天——ARM——汇编
汇编·arm开发·学习
许___2 小时前
基于 @antv/x6 实现流程图
vue.js·antv/x6
qq_10055170753 小时前
WordPress给指定分类文章添加一个自动化高亮(一键复制)功能
运维·前端·自动化·php
打小就很皮...3 小时前
React实现文本markdownit形式
前端·react.js·前端框架
charlie1145141913 小时前
精读《C++20设计模式》:重新理解设计模式系列
学习·设计模式·c++20·攻略
excel3 小时前
为什么要使用 TypeScript:TS 相比 JavaScript 的优势
前端
এ᭄请你吃糖℘3 小时前
html原生表格,实现左侧列固定
前端·html