D

Dev.to 的 ReactJS 克隆:使用 React Hooks

2025-06-09

Dev.to 的 ReactJS 克隆:使用 React Hooks

GitHub repo 链接现已开放。请在本文底部查看。您也可以查看GitHub Pages
上托管的实时网站

最近,我需要准备一份关于如何在 React 项目中使用 REST API 的讲座材料,所以我决定写一篇关于 dev.to API 的文章——可以在https://docs.forem.com/api/获取。以下是我在做这个项目时整理的内容。

介绍

API,即应用程序编程接口 (API),是一组定义应用程序或设备如何相互连接和通信的规则。REST API 是符合 REST(表述性状态转移架构风格)设计原则的 API。什么是 REST API?| IBM

React 是 Facebook 拥有的开源 JavaScript 库,用于开发响应式和轻量级的 UI。

Dev.to 是一个开发者博客网站,被描述为软件开发者的建设性和包容性的社交网络。

您应该知道

为了能够理解本教程,您应该具备 JavaScript 和 React 的基本知识。

您将在本部分中学习什么

1. 使用 npm 或 yarn 创建一个新的 React 应用

添加此部分作为复习

在开始构建 React 应用之前,您需要在开发计算机上安装最新版本的 Node.js。npmnpx与 Node.js 安装程序捆绑在一起。从官方网站下载 Node.js -下载 | Node.js

安装并设置好环境后,您应该能够从命令行 (CLI) 运行以下命令 - 。有关详细说明,npx create-react-app my-awesome-app请参阅React 官方文档Create React App 网站。

欲了解详情yarn,请参阅yarn官方文档

现在你已经创建了你的应用,是时候了cd my-awesome-app。好!你现在在你的应用目录中。

2. 使用 React Hooks

导航到你的项目文件夹并打开src目录,例如 C:/path/to/my-awesome-app/src,然后用你最喜欢的编辑器打开该index.js文件。我使用 SublimeText 或 VSCode。

你的 html 索引文件位于 C:/path/to/my-awesome-app/public/index.html。稍后我们准备推送到 GitHub 页面时会用到这个文件。

如果您在浏览器中打开该 html 文件,您会看到一个空白页。因此,要启动您的应用,请运行以下命令:npm start或者yarn start等待开发服务器在您的默认浏览器中启动您的应用。

到目前为止,您的 CLI 命令如下所示

    > npx create-react-app my-awesome-app
    > cd my-awesome-app
    > npm start
Enter fullscreen mode Exit fullscreen mode

服务器启动后,您将看到默认的 React 应用已加载。现在是时候通过编辑index.js我们之前打开的文件来构建您自己的项目了。其他文件暂时保留在 src 目录中。我们稍后会删除不需要的文件。

删除 index.js 文件的全部内容并输入以下内容:

文件:index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(
  <React.StrictMode>{/* This component will notify us of potential problems */}
      <App />
  </React.StrictMode>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

在处理 App.js 文件之前,我想先创建一些组件,所以先从导航栏开始吧。你需要在笔记本电脑上访问 dev.to 主页才能看到导航栏。

导航栏视图位于 >= 640px 处导航栏视图位于 < 640px 处
开发导航栏

移动开发导航

文件:Navbar.js

import React from 'react';
import {Link} from 'react-router-dom';//npm install react-router-dom

function Navbar({query, onChange}) {
    return (
        <header className="flex header justify-between items-center p-2 px-3 m:p-0 m:px-0 m:pb-2">
            <h1 className="crayons-subtitle-2">Posts</h1>

            <nav className="crayons-tabs hidden s:flex" aria-label="View posts by">
                <ul className="crayons-tabs__list">
                    <li>
                        <Link data-text="Feed" to="/" className={"crayons-tabs__item" + (query === "/" ? ' crayons-tabs__item--current' : '')}>Feed</Link>
                    </li>
                    <li>
                        <Link data-text="Week" to="/top/week" className={"crayons-tabs__item" + (query === "/top/week" ? ' crayons-tabs__item--current' : '')}>Week</Link>
                    </li>
                    <li>
                        <Link data-text="Month" to="/top/month" className={"crayons-tabs__item" + (query === "/top/month" ? ' crayons-tabs__item--current' : '')}>Month</Link>
                    </li>
                    <li>
                        <Link data-text="Year" to="/top/year" className={"crayons-tabs__item" + (query === "/top/year" ? ' crayons-tabs__item--current' : '')}>Year</Link>
                    </li>
                    <li>
                    <Link data-text="Infinity" to="/top/infinity" className={"crayons-tabs__item" + (query === "/top/infinity" ? ' crayons-tabs__item--current' : '')}>Infinity</Link>
                    </li>
                    <li>
                        <Link data-text="Latest" to="/latest" className={"crayons-tabs__item" + (query === "/latest" ? ' crayons-tabs__item--current' : '')}>Latest</Link>
                    </li>
                </ul>
            </nav>

            <select className="right s:hidden f-16 pd5 b-r5 sh-focus" value={query} onChange={onChange}>
                <option value="/">Feed</option>
                <option value="/top/week">Week</option>
                <option value="/top/month">Month</option>
                <option value="/top/year">Year</option>
                <option value="/top/infinity">Infinity</option>
                <option value="/latest">Latest</option>
            </select>
        </header>
    )
}

export default Navbar;
Enter fullscreen mode Exit fullscreen mode

Navbar 组件接受两个 props:query 和 onChange。propsquery 保存的是当前获取的文章类别的值。文章类别共有 6 个:feed、week、month、year、infinity 和 latest。

onChange prop指向每次我们使用 select 元素更改文章类别时运行的回调。

请注意,Navbar 组件包含两个功能元素,分别是navselect。这两个元素在网站上的任何地方都会一起使用,并且它们都作用于相同的信息,即当前文章类别,因此无需将它们提取到单独的组件中。

为导航栏组件设置样式
:为了简洁起见,本文将省略所有 CSS 代码,除非它提供任何功能。完整代码可在 GitHub 上的项目仓库中找到。

响应性
Dev.to 有 4 个断点,即:

  1. 0 - 639 [小型到中型移动设备]
  2. 640 - 767 [大型移动设备]
  3. 768 - 1023 [平板设备]
  4. 1024 - 1280 及以上 [笔记本电脑]

Dev.to 的设计遵循移动优先的理念,断点规则可以通过如下代码进行声明:

*, *:before, *:after {
    /* Your general styles here */
    /* Styles for extra small devices */
}

@media screen and (min-width: 640px) {
    /* Takes care of small to medium devices */
}

@media screen and (min-width: 768px) {
    /* Takes care of tablet devices */
}

@media screen and (min-width: 1024px) {
    /* Takes care of laptop devices */
}
Enter fullscreen mode Exit fullscreen mode

导航栏功能:
我们使用了 中的 Link 组件来react-router-dom处理链接。请不要忘记npm install react-router-dom稍后我们将了解这样做的必要性。我们还onChange为元素添加了一个事件监听器<select>来处理操作。

现在,我们来编写导航栏控制器。我们会将此控制器添加到我们的App.js文件中。

文件:App.js

import React, {useState, useEffect} from 'react';
import {Route, Switch, useHistory, useLocation} from 'react-router-dom';
// import Home from './Home';
// import Article from './Article';
// import Search from './Search';

function App() {
  const location = useLocation();// provided by the router bundle
  const history = useHistory();// provided by the router bundle

  const [posts, setPosts] = useState([]);
  const [failure, setFailure] = useState(false);
  const [query, setQuery] = useState(location.pathname);
  const [isShowing, setIsShowing] = useState(false);// for the Hamburger
  //
  function handleChange(event) {
    history.push(event.target.value); // adds a new entry to the history object
    // event.target.value could be one of "/, /top/week, /top/month, /top/year, /top/infinity, /latest"
  }
  function SideNavToggler() {// Hamburger Menu is the Side Navigator
    setIsShowing(isShowing => !isShowing);
  } // we use this state to decide whether the side menu should be revealed or hidden during the next click of the trigger element.
            //
  useEffect(() => {
    // 1. to avoid creating new object each time the component re-renders, we have to define this within the useEffect.
    // 2. if it was passed in a dependency, React will create new object each time, causing the effect hook to run repeatedly for every effect.
    // 3a. We are transforming the location pathname to something that the dev.to API understands, but we need to keep the path name SEO friendly.
    // 3b. dev.to/api/articles?top=7 gets us the articles created over the week, but we want to make it appear as https://dev-to-blog/top/week => https://dev.to/top/week - hence, the need for this mapping.
    const mymap = {
      '/': 0,
      '/top/week': 7,
      '/top/month': 30,
      '/top/year': 365,
      '/top/infinity': 366,
      '/latest': 1
    }
    const qpath = mymap[location.pathname]; // returns 0 for / and 7 for week...
    const fetchArticles = async () => {
      try {
        setFailure(false);
        setPosts([]);
        //
        const url = 'https://dev.to/api/articles' + (qpath ? '?top=' + qpath : '');
        const api_response = await fetch(url);
        const data = await api_response.json();

        if (api_response.status !== 200) {
          throw Error(api_response.error);
        }
        // console.log(data);
        setQuery(location.pathname); // update this after a successful API request
        setPosts(data);
      } catch (error) {
        // console.log(error);
        setFailure(true);
        setQuery(''); // do this to allow new request without change in location
      }
    }
    !isNaN(qpath) && fetchArticles();
  }, [location]) // the effect hook will only run when there is a change in the location's pathname, or after a failed request

  const navState = {SideNavToggler, isShowing};
  const data = {query, failure, posts, handleChange, ...navState};

  return (
    <div className="App">
      {/* <Switch>
        <Route path="/" render={() => <Home data={data} />} exact />
        <Route path="/top" render={() => <Home data={data} />} />
        <Route path="/latest" render={() => <Home data={data} />} />
        <Route path="/search" component={Search} />

        <Route render={() => <Article data={navState} />} />
      </Switch> */}
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

使用此控制器,如果用户点击链接nav,位置将被更新,并且由于我们已将位置作为依赖项添加到 useEffet 钩子中,我们确信会向 API 后端发出新请求,并且 UI 将在请求成功后重新渲染。

如果您想了解有关 useState 钩子的更多信息,您可以阅读我的文章,其中我演示了钩子的useState用法useReducer

概括

在本部分的前两节中,我们学习了如何创建一个新的 React 应用,以及如何使用useEffect钩子向服务器发出异步请求。我们还学习了如何使用useState钩子来管理应用的内部状态。

我们了解了如何使用带有 useEffect 钩子的 React Router 包来更新浏览器历史记录以激活服务器请求,我们还研究了如何使用媒体查询在我们的应用程序中设置断点以实现响应式设计。

下一步是什么?

在本文的第 2 部分中,我们将深入探讨 React Router 的 SPA 导航世界,以及如何配置我们的应用程序以符合 github 页面导航模式。

如果您喜欢这篇文章并希望在下次更新时收到通知,您可以点击Save按钮将其添加到您的阅读列表中,或者您也可以关注我的帐户。

谢谢 ;)

GitHub 上的源代码链接

鏂囩珷鏉ユ簮锛�https://dev.to/maswerdna/dev-to-blog-a-reactjs-clone-of-dev-to-5960
PREV
11 个开源项目将让您的简历飞速提升🚀(在 2024 年开启您的职业生涯!🌟✨)TL;DR ✨ 功能文档支持的客户端入门 - Docker Compose SWIRL AI Connect pgvector Logstash🎛️ GO 功能标志更改查询安全开源通知框架,使开发人员能够轻松授权产品团队
NEXT
你可以在 JavaScript 中使用的 3 个 TypeScript 技巧