5 种 React 数据获取模式

2025-06-10

5 种 React 数据获取模式

迟早你需要从 API 中获取数据,让我们看看如何在 React 中处理数据!👌

在本教程中,我们将介绍 React 中最常见的数据获取模式。

最初发布于Nordschool

听见了吗?我们一起行动吧!💪

概述

让我们先看一下总体情况,然后再深入挖掘。

我们将涵盖的模式:

项目结构

我创建了一个小型 React 项目来展示不同的数据获取模式。该项目已初始化为 create-react-app,并拥有标准结构。👇

├── README.md
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── actions
│   │   ├── api.js
│   │   ├── index.js
│   │   └── types.js
│   ├── hooks
│   │   ├── UseDataApi.js
│   ├── components
│   │   ├── HOC.js
│   │   ├── Standalone.js
│   │   ├── PostsList.js
│   │   ├── RenderProps.js
│   │   ├── WithCustomMiddleware.js
│   │   ├── WithCustomHook.js
│   │   └── WithHooks.js
│   ├── index.css
│   ├── index.js
│   ├── middleware
│   │   └── api.js
│   ├── reducers
│   │   └── index.js
│   ├── serviceWorker.js
│   └── store
│       └── index.js
└── yarn.lock

我们将处理组件

主根组件如下所示

// App.js
import React from 'react';
import './App.css';

import Standalone from './components/Standalone';
import HOC from './components/HOC';
import WithHooks from './components/WithHooks';
import WithCustomHook from './components/WithCustomHook';
import RenderProps from './components/RenderProps';
import WithCustomMiddleware from './components/WithCustomMiddleware';
import PostsList from './components/PostsList';

function App() {
  return (
    <div className="App">
      <h3>Standalone</h3>
      <Standalone />
      <h3>HOC</h3>
      <HOC />
      <h3>WithHooks</h3>
      <WithHooks />
      <h3>With Custom Hook</h3>
      <WithCustomHook />
      <h3>Render Props</h3>
      <RenderProps children={PostsList} />
      <h3>With Custom Middleware</h3>
      <WithCustomMiddleware />
    </div>
  );
}

export default App;

让我们从最紧凑的模式开始……

独立

这个独立组件负责获取和呈现数据。

// components/Standalone.js
import React, { Component } from 'react';
import axios from 'axios';

import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';

// API END POINT
const POSTS_SERVICE_URL = 'https://jsonplaceholder.typicode.com/posts';

class Standalone extends Component {
  state = {
    // Initial state.
    isFetching: false,
    posts: []
  };

  render() {
    return (
      <Paper>
        <Table aria-label="simple table">
          <TableHead>
            <TableRow>
              <TableCell>Id </TableCell>
              <TableCell>Title</TableCell>
              <TableCell>Body</TableCell>
            </TableRow>
          </TableHead>
          <TableBody>
            {this.state.posts.map(row => (
              <TableRow key={row.id}>
                <TableCell component="th" scope="row">
                  {row.id}
                </TableCell>
                <TableCell>{row.title}</TableCell>
                <TableCell>{row.body}</TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
        <p>{this.state.isFetching ? 'Fetching posts...' : ''}</p>
      </Paper>
    );
  }

  componentDidMount() {
    this.fetchPosts();
  }

  async fetchPostsAsync() {
    try {
      this.setState({ ...this.state, isFetching: true }); // Sets loading state.
      const response = await axios.get(POSTS_SERVICE_URL);
      this.setState({
        ...this.state,
        isFetching: false,
        posts: response.data.slice(0, 5) // Take first 5 posts only
      });
    } catch (e) {
      console.log(e);
      this.setState({ ...this.state, isFetching: false });
    }
  }

  fetchPosts = this.fetchPostsAsync;
}

export default Standalone;

让我们看看是否可以将视图与数据获取分开🤓...

HOC

高阶组件 (HOC) 模式在 React 中很常见。这类组件有时也称为容器组件

这个想法很简单,数据获取与数据呈现分开。

// HOC.js
import React, { Component } from 'react';
import Simple from './Simple';

import axios from 'axios';

const POSTS_SERVICE_URL = 'https://jsonplaceholder.typicode.com/posts';

class HOC extends Component {
  state = {
    isFetching: false,
    posts: []
  };

  render = () => (
    <Simple data={this.state.posts} isFetching={this.state.isFetching}></Simple>
  );

  componentDidMount() {
    this.fetchPosts();
  }

  async fetchPostsAsync() {
    try {
      this.setState({ ...this.state, isFetching: true });
      const response = await axios.get(POSTS_SERVICE_URL);
      this.setState({
        ...this.state,
        isFetching: false,
        posts: response.data.slice(0, 5)
      }); // Take first 5 posts only
    } catch (e) {
      console.log(e);
      this.setState({ ...this.state, isFetching: false });
    }
  }

  fetchPosts = this.fetchPostsAsync;
}

export default HOC;

演示组件看起来是这样的:

// PostsList.js
import React from 'react';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';

const PostsList = props => {
  return (
    <Paper>
      <Table aria-label="simple table">
        <TableHead>
          <TableRow>
            <TableCell>Id </TableCell>
            <TableCell>Title</TableCell>
            <TableCell>Body</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {props.data.map(row => (
            <TableRow key={row.id}>
              <TableCell component="th" scope="row">
                {row.id}
              </TableCell>
              <TableCell>{row.title}</TableCell>
              <TableCell>{row.body}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
      <p>{props.isFetching ? 'Fetching posts...' : ''}</p>
    </Paper>
  );
};

export default PostsList;

但如果我告诉你,也许还有更好的方法呢?😁

让我们看一下如何使用 Hooks 来实现它。

带钩

这种模式与 HOC 类似,但它将功能组件与钩子一起使用。

// WithHooks.js
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Simple from './Simple';

const POSTS_SERVICE_URL = 'https://jsonplaceholder.typicode.com/posts';

function WithHooks() {
  const [data, setData] = useState({ posts: [], isFetching: false });

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        setData({ ...data, isFetching: true });
        const response = await axios.get(POSTS_SERVICE_URL);
        setData({
          ...data,
          posts: response.data.slice(0, 5),
          isFetching: false
        });
      } catch (e) {
        console.log(e);
        setData({ ...data, isFetching: false });
      }
    };
    fetchUsers();
  }, []); // Runs once

  return <Simple data={data.posts} isFetching={data.isFetching} />;
}

export default WithHooks;

我们可以更进一步,甚至创建一个通用钩子来从任何 API 获取数据。

通用数据获取钩子的简单版本:

// hooks/UseDataApi.js
import { useEffect, useState } from 'react';
import axios from 'axios';

const useDataApi = url => {
  // This is just for demo purposes, you probably want to separate the data from loading state and potentially add other states such as failures, etc..
  const [dataState, setDataState] = useState({ data: [], isFetching: false }); 
  const [endpointUrl] = useState(url);

  useEffect(() => {
    const fetchDataFromApi = async () => {
      try {
        setDataState({ ...dataState, isFetching: true });
        const response = await axios.get(endpointUrl);
        setDataState({
          ...dataState,
          data: response.data,
          isFetching: false
        });
      } catch (e) {
        console.log(e);
        setDataState({ ...dataState, isFetching: false });
      }
    };
    fetchDataFromApi();
  }, []); // Runs once

  return [dataState];
};

export default useDataApi;

一旦你的钩子准备好了,就可以像这样使用......

// components/WithCustomHook.js
import React from 'react';
import UseDataApi from '../hooks/UseDataApi';
import PostsList from './PostsList';

const POSTS_SERVICE_URL = 'https://jsonplaceholder.typicode.com/posts';

function WithHooks() {
  const [dataState] = UseDataApi(POSTS_SERVICE_URL);

  return (
    <PostsList
      data={dataState.data.slice(0, 5)}
      isFetching={dataState.isFetching}
    />
  );
}

export default WithHooks;

在大多数情况下,我们推荐使用此模式!此模式可重用,并且可以很好地实现关注点分离。

好的,到目前为止一切都很好。

但是,如果您有许多显示相同数据的演示组件怎么办?

一般来说,Hooks 可以覆盖大部分逻辑封装的情况。但是,它可能存在一些局限性

渲染道具

对于这种用例,您可以使用钩子代替渲染道具,但渲染道具是另一种可行的选择。

渲染道具充当不同演示组件的可重用包装器。

// RenderProps.js
import { Component } from 'react';
import axios from 'axios';

const POSTS_SERVICE_URL = 'https://jsonplaceholder.typicode.com/posts';

class RenderProps extends Component {
  state = {
    isFetching: false,
    data: []
  };

  render = () => this.props.children(this.state);

  componentDidMount() {
    this.fetchPosts();
  }

  async fetchPostsAsync() {
    try {
      this.setState({ ...this.state, isFetching: true });
      const response = await axios.get(POSTS_SERVICE_URL);
      this.setState({
        ...this.state,
        isFetching: false,
        data: response.data.slice(0, 5)
      }); // Take first 5 posts only
    } catch (e) {
      console.log(e);
      this.setState({ ...this.state, isFetching: false });
    }
  }

  fetchPosts = this.fetchPostsAsync;
}

export default RenderProps;

这就是 Vanilla React 模式的全部内容!🙌

好吧,Redux 看起来怎么样?让我先睹为快。

Redux 自定义中间件

这是一个使用自定义中间件的简单示例。

// components/WithCustomMiddleware.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import PostsList from './PostsList';
import { fetchPosts } from '../actions';

class WithCustomMiddleware extends Component {
  state = {};

  componentDidMount() {
    this.props.fetchPosts();
  }

  render = () => (
    <PostsList
      data={this.props.data}
      isFetching={this.props.isFetching}
    ></PostsList>
  );
}

const mapStateToProps = ({ data = [], isFetching = false }) => ({
  data,
  isFetching
});

export default connect(
  mapStateToProps,
  { fetchPosts }
)(WithCustomMiddleware);

不确定 Redux 中间件如何工作?查看这个关于如何创建自定义 redux 中间件的简短教程。

就是这样,现在你知道如何在 React 中处理数据获取了!✋


支持

喜欢这篇文章吗?请在 Twitter 上分享摘要。


更好的代码周一通讯

你可能也会喜欢我的新闻通讯。我的目标是每周一分享 3 个 Web 开发技巧。

我的目标是提升写作能力,并尽可能地分享知识。目前为止,已有数百名开发者订阅,他们似乎很喜欢。

要了解我分享的内容,请查看以前的新闻通讯订阅

鏂囩珷鏉ユ簮锛�https://dev.to/nordschool/5-react-data-fetching-patterns-6ci
PREV
在 React 中编写 CSS 样式的 5 种方法
NEXT
Socket.io | 显示访客数量 Socket.io 显示访客数量