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 上分享摘要。
![]()
诺德学校@nordschool
2019年10月23日上午8:38
更好的代码周一通讯
你可能也会喜欢我的新闻通讯。我的目标是每周一分享 3 个 Web 开发技巧。
我的目标是提升写作能力,并尽可能地分享知识。目前为止,已有数百名开发者订阅,他们似乎很喜欢。
鏂囩珷鏉ユ簮锛�https://dev.to/nordschool/5-react-data-fetching-patterns-6ci