使用React Hooks Strapi V4.0和Prism构建一个博客
Strapi节省了大量的开发时间,同时让开发者可以自由使用他们喜欢的工具和框架。Strapi,是一个无头的内容管理系统,它是一个只有后端的管理器,允许通过API访问内容,在任何设备上显示。
此外,将Strapi与Angular、React、Nuxt或Vue等多个框架集成,可以简化博客等项目的构建。
通过我们的博客实例,我们将向你展示如何使用状态钩、Strapi和Prism建立一个博客。我们从Strapi传递每一个内容,而博客将启用markdown。这是一个美丽的旅程,我们希望你能跟随我们走完每一步。
前提条件
要继续学习本教程,读者应满足以下要求。
- 有关于React.js的中级到专业的知识。
- 必须熟练掌握CSS。
- 使用Node.js构建过项目。
- 了解状态钩子。
目标
我们不是要建立一个完整的博客,而是要建立一个包含几篇文章的预览区和一个包含我们支持标记内容的页面的壳。
我们想告诉你如何。
- 在Strapi中存储内容。
- 使用状态钩子从Strapi获取数据。
- 使用Strapi和Prism上的内容创建一个支持markdown的博客。
起步
初始化strapi
在你的编辑器上(在我们的例子中是VsCode),在我们的计算机中创建一个根文件夹,称为base-project ,并在你的终端中使用命令cd base-project 。
接下来,我们在终端中键入以下命令。
npx create-strapi-app my-project
上述命令将利用node package manager 来创建我们的Strapi项目。它将创建一个名为my-project 的文件夹并安装必要的Strapi节点模块。
为了启动开发服务器,我们在终端运行以下命令。
npm run develop
运行上述命令会在我们的浏览器中打开一个注册区域。这个注册区是用来注册第一个管理员用户的。不幸的是,由于我们已经注册了第一个管理员,所以我们不能给你看。
通过完成这个表格,你就成为Strapi应用程序的第一个管理员用户。

添加内容
在这一节中,我们要向Strapi添加我们想要的内容。为了开始,我们按照下面的步骤进行。
- 在插件部分点击
content-type builder。 - 接下来,在集合类型下拉菜单中选择
create new collection type。 - 一个如下的模式应该弹出。使用你选择的任何显示名称,Strapi会将其复数化。

- 在我们新创建的集合类型中,我们添加了五个新字段(标题、评级、正文、英雄、URL)。

- 之后,点击侧边栏上的
content manager,选择你的收藏类型。接下来,点击右上角的create new entry,会被带到一个看起来像这样的页面。

每个新条目都包含先前创建的字段的条目。接下来,我们添加所需的文章标题、评级、文章内容(正文)、英雄和URL(英雄图片链接),然后点击保存和发布。
- 要从Strapi阅读内容,我们前往设置部分,在
users and permissions plugin部分,我们选择角色。

- 接下来,点击
public,滚动到权限。接下来,点击审查,在权限区选择find和findone。接下来,向下滚动到上传,做同样的事情,选择上传。

现在我们可以在前端进行操作了。
前端
初始化反应
我们首先使用下面的命令在我们的初始根文件夹中创建一个React项目。
npx create-react-app frontend
上面的代码片段将创建一个名为frontend的文件夹,它包含node_module文件夹中的React包。
要启动开发服务器,使用命令npm start 。上述命令在本地服务器上运行React项目,并在浏览器上显示它。
创建前端
为了开始工作,在前端文件夹中,在src 文件夹中创建三个文件夹。命名为components 、hooks 、pages 。我们将从pages文件夹开始;创建两个新的JavaScript文件,称为Homepage.js 和Contents.js 。
快速提示:在Vscode上,安装ES7插件。它允许你通过快速输入
rfc + Enter,来创建一个React功能组件。
因此,在两个主页和内容文件中键入你的React功能组件。
接下来,回到App.js 文件,做以下工作。
- 清除默认的React模板。
- 输入
rfc + Enter(如果你已经安装了ES7插件)来添加React功能组件。 - 导入pages文件夹中的JavaScript文件。
- 在终端上使用以下命令安装并导入
react-router-dom(通常在我们安装React时默认安装)。
npm install react-router-dom
- 创建一个
div,类名为App,并使用react-router-dom,创建通往导入页面的路由。
下面是对上述解释的实现。
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
//Page and layout imports
import Homepage from './pages/Homepage'
import Contents from './pages/Contents'
function App() {
return (
<Router>
<div className="App">
<Siteheader />
<Routes>
<Route exact path="/" element={<Homepage />}>
</Route>
<Route path="/contents/:id" element={<Contents />}>
</Route>
</Routes>
</div>
</Router>
);
}
export default App;
接下来,我们去看Homepage.js 文件。在我们开始之前,我们需要创建我们的状态钩子。因此,在hooks文件夹内,我们将创建一个名为Usefetch.js 的文件。
在Usefetch.js 文件内,我们做以下工作。
- 从React导入
useEffect和useState。 - 创建
useFetch function,接收值url,并导出useFetch()函数。
import { useEffect, useState } from "react"
const useFetch = (url) => {
}
export default useFetch
注意:钩子接收端点(我们从哪里获得数据)。
- 在该函数中,添加以下代码。
const [data, setData] = useState([])
const [error, setError] = useState(null)
const [loading, setLoading] = useState(true)
- 第一个变量是最终从获取请求中获得的数据,通常被设置为空。同时,
setData,更新从Strapi收到的数据。 - 第二个变量是我们从获取请求中得到的错误,通常被设置为空。
setError,更新从Strapi收到的错误。 - 第三个变量在我们利用钩子使用fetch时将加载状态初始化为
true。一旦我们完成了对数据的获取,它就会使之成为假的。 - 接下来,我们将创建一个
useEffect钩子函数,当组件渲染我们使用这个钩子的任何组件时,它就会工作。
在useEffect ,我们创建另一个函数叫fetchData ,并使之成为async.
- 我们使
setLoading=true,以防在我们试图设置数据时,上面的数据变成假的。 - 我们使用
try and catch语句。然后,我们fetch API,从端点获取数据。下面是对上述解释的实现。
useEffect(() => {
const fetchData = async () => {
setLoading(true)
try {
const res = await fetch(url)
const json = await res.json()
console.log(json)
setData(json);
setLoading(false)
} catch (error) {
setError(error)
setLoading(false)
}
}
fetchData();
}, [url])
- 我们需要在钩子的结尾处返回数值。我们可以使用这段代码。
return { loading, error, data }
我们的useFetch 已经准备好在我们的Homepage.js 文件和Content.js 文件中使用。
回到Homepage.js 文件。博客的这一部分负责文章的标题和预览(一些文章内容、标题和英雄)。
为了实现这一点,我们按照下面的步骤进行。
- 从
react-router-dom中导入Link,从我们的useFetch.js文件中导入useFetch。
import useFetch from '../hooks/useFetch'
import { Link } from 'react-router-dom'
- 在
Homepage函数中,通过添加以下代码插入useFetch组件。
const{loading, error, data} = useFetch('http://localhost:1337/api/reviews')
上面的代码从useFetch 中解构了加载、错误和数据,而url 是Strapi的终端。
- 创建两个
if statements,以返回一个加载信息和一个错误信息(如果有)。
if (loading) return <p>Loading...</p>
if (error) return <p>Error :(</p>
注意:如果加载是
true,或者错误是true,这是有可能的。在这种情况下,加载是true,在我们完成获取之前,它仍然是true.
- 接下来,一旦完成上述工作,我们将需要返回包含我们一些文章内容的模板。我们将使用下面的代码来实现这一点。
return (
<div>
{data.data.map(review => (
<div key={review.attributes.id} className="review-card">
<div className="rating">{review.attributes.rating}</div>
<Link to={`/contents/${review.id}`}>
<img src={review.attributes.url}/>
<h2>{review.attributes.title}</h2>
</Link>
<small>console list</small>
<p>{review.attributes.body.substring(0, 200)}...</p>
</div>
))}
</div>
)
我们使用data.data.map 函数对从Strapi收到的数据进行映射,我们通过使用一个名为的函数得到对数组中每一项的访问。review.
- 然后我们返回一个
div模板,其中的key属性有一个动态值review.attributes.id。之所以如此,是因为React需要地图内的父元素有一个key属性,以跟踪Strapi中所有的elements。 - 我们给
div一个类名review-card,以便以后对其进行样式设计。
注意:关于CSS,我们将不作解释,因为我们相信读者在进入这个阶段之前已经有了很好的背景知识。
- 使用先前从
react-router-dom中导入的{Link},我们创建一个指向内容页的链接标签。然后我们在链接中加入图片和标题。
下面是主页目前的样子。

对于Content.js 文件,我们首先。
- 从
useFetch.js中导入useFetch,从react-router-dom中导入useParams。
import { useParams } from 'react-router-dom'
import useFetch from '../hooks/useFetch'
useParams 是一个用于抓取单一记录的钩子,在这种情况下,我们要从Strapi抓取记录。
- 接下来,在
export default function,中,我们创建一个常量,并将我们想要的参数名称(id)去结构化,这相当于useParams。
const { id } = useParams()
注意:我们称它为id,因为它在我们App.js文件的路由中被命名为id。
- 通过添加下面的代码插入
useFetch组件。
const{loading, error, data} = useFetch('http://localhost:1337/api/reviews' + id)
上面的代码从useFetch 中解构了加载、错误和数据,而url 是Strapi的端点,id 是我们的解构参数。
- 创建两个
if statements,如果有的话,返回加载信息和错误信息。
if (loading) return <p>Loading...</p>
if (error) return <p>Error :(</p>
- 接下来,一旦上述工作完成,我们将需要返回包含所选文章内容的模板。我们使用下面的代码来完成。
return (
<div className="review-card">
<div className="rating">{data.data.attributes.rating}</div>
<h2>{data.data.attributes.title}</h2>
<small>console list</small>
</div>
)

我们必须回到之前创建的组件文件夹,使其启用markdown功能。
在该文件夹中,我们创建一个名为Codeblock.js的文件,并添加以下代码。
import React from "react"
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import {dracula} from 'react-syntax-highlighter/dist/cjs/styles/prism';
const CodeBlock = {
code({node, inline, className, children, ...props}) {
const match = /language-(\w+)/.exec(className || '')
return !inline && match ? (
<SyntaxHighlighter
style={dracula}
language={match[1]}
PreTag="div" {...props}>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
)
}
}
export default CodeBlock
- 使用上面的代码时,有几个先决条件。
- 我们需要安装
react-syntax-highlighter.,我们使用下面的代码来完成。
npm install react-syntax-highlighter
-
接下来,我们从
react-syntax-highlighter模块中导入Prism和Dracula。 -
为了将其用于我们内容的主体,我们将回到
Content.js文件。 -
在
content.js文件中,我们将需要从markdown。react-markdown. -
要安装 react-markdown,使用下面的代码。
npm install react-markdown
- 接下来,我们需要从组件文件夹中导入
Codeblock.js(包含我们的语法高亮器的文件)。
例子
import Markdown from 'react-markdown'
import CodeBlock from '../components/CodeBlock'
- 最后,我们将在我们的
div中添加文章的正文,并将其封装在一个markdown标签中,同时将Codeblock.js中的语法作为markdown标签中的一个组件属性。
例子
<Markdown components={CodeBlock}>{data.data.attributes.body}</Markdown>
这里是用于博客的整个CSS的链接。
正确地执行上面的代码可以得到下面的结果。


我们的博客看起来很好,而且可以正常使用了。
总结
在这篇文章中,我们演示了如何使用状态钩子、Strapi和Prism来创造一个杰作。然而,我们相信你可以更进一步,用这个项目扩大你的范围。