nodejs reac express 前后端入门

如何快速入门? 快速从0到平均水平? enter image description here

基本概念接触

虚拟 DOM 是 React 用于优化与浏览器交互的一种技术

在 React 出现之前,许多现有框架都会在每次更改时直接操作 DOM。

首先,什么是 DOM?

DOM(文档对象模型)是页面的树形表示,从标签开始,一直到每个子节点,这些子节点称为节点。

它保存在浏览器内存中,并直接链接到您在页面中看到的内容。 DOM 有一个 API,您可以使用它来遍历它、访问每个节点、过滤它们、修改它们。

如果您没有使用 jQuery 和朋友提供的抽象 API,该 API 就是您可能多次见过的熟悉语法:

document.getElementById(id)
document.getElementsByTagName(name)
document.createElement(name)
parentNode.appendChild(node)
element.innerHTML
element.style.left
element.setAttribute()
element.getAttribute()
element.addEventListener()
window.content
window.onload
window.dump()
window.scrollTo()

React 保留 DOM 表示的副本,用于 React 渲染:虚拟 DOM

虚拟 DOM 详解

每次 DOM 发生变化时,浏览器都必须执行两个密集操作:重新绘制(对元素进行视觉或内容更改,但不影响相对于其他元素的布局和定位)和重排(重新计算页面部分布局 - 或整个页面布局)。

React 使用虚拟 DOM 来帮助浏览器在需要在页面上进行更改时使用更少的资源。

当你调用setState()组件并指定与之前不同的状态时,React 会将该组件标记为dirty。这是关键:只有当组件明确更改状态时,React 才会更新。

接下来发生的事情是:

  • React 会根据标记为 dirty 的组件来更新虚拟 DOM(并进行一些额外的检查,比如触发shouldComponentUpdate())
  • 运行差异算法来协调更改
  • 更新真实 DOM

虚拟 DOM 为何有用:批处理

关键在于,React 通过同时更改所有需要更改的元素,批量处理大部分更改并对真实 DOM 执行唯一的更新,因此浏览器为呈现更改而必须执行的重绘和重排只需执行一次。

虚拟DOM概念没什么特别,这个和游戏显示的double buffer一样的思路

React中的单向数据流

使用 React 时,你可能会遇到术语“单向数据流”。它是什么意思?

单向数据流并不是 React 独有的概念,但作为 JavaScript 开发人员,这可能是你第一次听说它。

一般来说,这个概念意味着数据只有一种方式可以传输到应用程序的其他部分。

在 React 中这意味着:

  • 状态传递给视图和子组件
  • 操作由视图触发
  • 动作可以更新状态
  • 状态改变被传递给视图和子组件

enter image description here 视图是应用程序状态的结果。状态只有在操作发生时才会改变。当操作发生时,状态就会更新。

由于单向绑定,数据不能以相反的方向流动(例如,双向绑定就会发生这种情况),这具有一些关键优势:

  • 由于你可以更好地控制数据,因此不容易出错
  • 调试起来更容易,因为你知道从哪里来的东西
  • 它更有效率,因为库已经知道系统每个部分的边界 一个状态始终由一个组件拥有。受此状态影响的任何数据只能影响其下方的组件:其子组件。

改变组件的状态永远不会影响它的父组件、兄弟组件或应用程序中的任何其他组件:只会影响它的子组件。

这就是状态经常在组件树中向上移动的原因,以便它可以在需要访问它的组件之间共享。

React 概念:声明式

当你读到 React 是声明式的时,这意味着什么

您会看到一些文章将 React 描述为一种构建 UI 的声明式方法。

React 的“声明式方法”非常流行且直观,因此它与 React 一起渗透到了前端世界。

这实际上不是一个新概念,但是 React 构建 UI 的方式比使用 HTML 模板更具声明性:

你甚至无需直接接触 DOM 就可以构建 Web 界面 您可以拥有一个事件系统,而无需与实际的 DOM 事件进行交互。 声明式的反义词是命令式。命令式方法的一个常见示例是使用 jQuery 或 DOM 事件在 DOM 中查找元素。您告诉浏览器确切要做什么,而不是告诉它您需要什么。

React 声明式方法为我们抽象了这一点。我们只需告诉 React 我们希望以特定方式呈现组件,并且我们无需与 DOM 交互即可在以后引用它。

在 React 中,“声明式”编程意味着我们通过描述“我们希望 UI 看起来怎样”来构建用户界面,而不是通过命令浏览器执行具体的步骤。这种方法对比“命令式”方法更加直观和高效。让我们更深入理解这些概念:

声明式 vs 命令式

声明式:你描述“做什么”,而不关心“怎么做”。例如,使用 React 组件时,你定义组件的状态和它应该如何渲染。React 会自动管理 DOM 更新和渲染。 命令式:你描述具体的步骤来实现目标。使用 jQuery 时,你会手动查找 DOM 元素并逐步更新它们。 示例代码对比

声明式 React 代码:

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

在这个例子中,我们描述了一个按钮和文本,并定义了按钮点击时的行为。我们不需要告诉 React 如何将元素插入 DOM,React 会自动在状态变化时重新渲染 UI。

命令式 jQuery 代码:

let count = 0;

function updateDOM() {
  $('#count').text(`Clicked ${count} times`);
}

$('#button').on('click', function() {
  count++;
  updateDOM();
});

在这个 jQuery 示例中,我们手动更新 DOM 并绑定事件处理程序。每当按钮被点击时,我们需要指定如何修改 DOM,告诉浏览器每一步该怎么做。

为什么声明式更好?

更高的可读性:代码更清晰,因为你只需描述界面状态。 更少的出错机会:React 处理了底层的 DOM 操作和重渲染逻辑,减少了手动操作可能带来的错误。 易于维护:更容易理解组件的行为并修改它们,因为你只需更新组件的状态或属性,React 会自动进行相应的重渲染。 通过声明式编程,我们专注于“描述预期结果”,让 React 处理繁琐的 DOM 更新和状态管理。

React 概念:不变性

什么是不变性?它如何融入 React 世界?

在使用 React 编程时你可能会遇到的一个概念是不可变性(以及它的对立面,可变性)。

这是一个有争议的话题,但无论你如何看待不变性的概念,React 及其大部分生态系统都强制这样做,所以你至少需要了解为什么它如此重要以及它的含义。

在编程中,如果变量创建后其值就无法改变,那么该变量就是不可变的。

当你操作字符串时,你已经在不知不觉中使用了不可变变量。默认情况下,字符串是不可变的,当你实际更改它们时,你会创建一个新的字符串并将其分配给相同的变量名。

不可变变量永远无法改变。要更新其值,请创建一个新变量。

这同样适用于对象和数组。

要添加新项,您无需更改数组,而是通过连接旧数组和新项来创建新数组。

对象永远不会被更新,而是在改变之前进行复制。

这在 React 的很多地方都适用。

例如,您永远不应该state直接改变组件的属性,而只能通过setState()方法改变。

在Redux中,您永远不会直接改变状态,而只能通过 Reducer(即函数)来改变状态。

问题是,为什么?

原因有多种,其中最重要的是:

变异可以集中化,就像 Redux 的情况一样,这可以提高您的调试能力并减少错误源。 代码看起来更简洁易懂。你永远不会期望函数在你不知情的情况下改变某些值,这为你提供了可预测性。当函数不改变对象而只是返回一个新对象时,它被称为纯函数。 该库可以优化代码,因为例如,当将旧对象引用交换为全新对象时,JavaScript 的速度会比改变现有对象更快。这可以提高性能。

在 React 中,不变性(immutability)是指在更改对象或数组时,不直接修改原对象或数组,而是创建它们的副本并进行修改。这种方法有助于保持代码的可预测性和简洁性,尤其在处理 React 的状态更新时。

为什么不变性重要? 提高性能:React 的 shouldComponentUpdate 方法和 React.memo 等性能优化技术依赖于不变性来有效比较前后状态。如果对象被修改而不是创建新对象,比较将变得困难和低效。 避免副作用:直接修改对象或数组可能导致意外的副作用,影响代码的可预测性和调试。 可读性和维护性:代码更清晰,因为它更容易理解和维护。 代码示例 1. 使用可变方法的反例 以下代码展示了如何在数组中使用“可变”方法:

const numbers = [1, 2, 3, 4];
numbers.push(5); // 直接修改了原数组
console.log(numbers); // 输出 [1, 2, 3, 4, 5]

在这种情况下,numbers 数组被直接修改了,这会导致潜在的副作用,尤其是在处理 React 状态时。

  1. 使用不变方法的例子 在处理不变性时,我们会创建数组或对象的新副本:

    const numbers = [1, 2, 3, 4]; const newNumbers = [...numbers, 5]; // 使用展开运算符创建新数组 console.log(newNumbers); // 输出 [1, 2, 3, 4, 5] console.log(numbers); // 原数组保持不变 [1, 2, 3, 4]

这样可以确保 numbers 不被修改,新数组 newNumbers 是一个新的实例。

React 中的应用

在 React 中,不变性尤其重要,主要体现在 useState 钩子和 Redux 等状态管理工具中。来看一个修改 React 状态的示例:

import { useState } from 'react';

function ListComponent() {
  const [items, setItems] = useState([1, 2, 3]);

  const addItem = () => {
    // 不变性方法:创建新数组并更新状态
    setItems([...items, 4]);
  };

  return (
    <div>
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
      <button onClick={addItem}>Add Item</button>
    </div>
  );
}

注意 直接修改 React 状态,如 items.push(4) 然后 setItems(items) 是不可取的。这种方法修改了 items 数组的原始引用,React 可能无法检测到变化,从而导致渲染问题。

对象不变性

对于对象的更新,也要避免直接修改。示例如下:

const person = { name: 'Alice', age: 25 };

// 错误:直接修改对象
person.age = 26;

// 正确:创建新对象
const updatedPerson = { ...person, age: 26 };

在 React 中,这样的模式确保了状态更新能被 React 识别,并正确触发重新渲染。

总结:在 React 中使用不变性可以提升代码的可靠性和性能,确保状态的更新是可预测的,便于调试和维护。

创建一个应用,使用Azure Devops RestAPI获取一些xxx组织xxxproject下面的git仓库

涉及到PAT(personal access token)我想使用一个后端负责处理这些环境变量加载以及实际访问Azure Devops RestAPI;前端简单构建一个i哦界面,将api请求转发到后端服务 - 前端 - 后端

先把后端创建一下

node.js express.js

make azure-devops-repos-app
cd azure-devops-repos-app
mkdir backend
cd backend
  • 创建server.js处理前端的api请求:
    // server.js
    require('dotenv').config();
    const express = require('express');
    const axios = require('axios');
    const path = require('path');

    const app = express();
    const PORT = process.env.PORT || 5000;

    app.get('/api/repos', async (req, res) => {
      const { organization, project } = req.query;
      const url = `https://dev.azure.com/${organization}/${project}/_apis/git/repositories?api-version=6.0`;

      try {
        const response = await axios.get(url, {
          headers: {
            'Authorization': `Basic ${Buffer.from(`:${process.env.PERSONAL_ACCESS_TOKEN}`).toString('base64')}`
          }
        });
        res.setHeader('Content-Type', 'application/json');
        res.json(response.data);
      } catch (error) {
        res.status(500).json({ error: 'Failed to fetch data', details: error.message });
      }
    });

    // Serve static files from React app
    app.use(express.static(path.join(__dirname, 'client/build')));

    app.listen(PORT, () => {
      console.log(`Server is running on http://localhost:${PORT}`);
    });
  • 创建一个.env文件,保存PAT

    PERSONAL_ACCESS_TOKEN=your_personal_access_token_here

前端代码

    npx create-react-app frontend

使用react app框架,创建一个前端web应用原型

  • 创建一个组件,负责抓取devops数据,显示数据
    // src/GitRepoFetcher.js
    import React, { useState } from 'react';

    function GitRepoFetcher() {
      const [organization, setOrganization] = useState('');
      const [project, setProject] = useState('');
      const [repos, setRepos] = useState([]);
      const [loading, setLoading] = useState(false);
      const [error, setError] = useState(null);

      const handleSubmit = async (e) => {
        e.preventDefault();
        setLoading(true);
        setError(null);
        setRepos([]);

        try {
          const response = await fetch(`/api/repos?organization=${organization}&project=${project}`);
          if (!response.ok) {
            throw new Error('Failed to fetch data');
          }

          const data = await response.json();
          setRepos(data.value); // 假设后端返回的数据中包含 `value` 数组
        } catch (err) {
          setError(err.message);
        } finally {
          setLoading(false);
        }
      };

      return (
        <div>
          <h1>Azure DevOps Git Repositories</h1>
          <form onSubmit={handleSubmit}>
            <input
              type="text"
              placeholder="Organization"
              value={organization}
              onChange={(e) => setOrganization(e.target.value)}
              required
            />
            <input
              type="text"
              placeholder="Project"
              value={project}
              onChange={(e) => setProject(e.target.value)}
              required
            />
            <button type="submit">Submit</button>
          </form>
          {loading && <p>Loading...</p>}
          {error && <p style={{ color: 'red' }}>{error}</p>}
          <ul>
            {repos.map((repo) => (
              <li key={repo.id}>{repo.name}</li>
            ))}
          </ul>
        </div>
      );
    }

    export default GitRepoFetcher;
  • react 入口 App.js里面添加上面的组件
    // src/App.js
    import React from 'react';
    import GitRepoFetcher from './GitRepoFetcher';

    function App() {
      return (
        <div className="App">
          ***<GitRepoFetcher />***
        </div>
      );
    }

    export default App;

代理设置问题(如果使用 React 开发环境) 如果你在 React 中使用 create-react-app,你可能需要配置代理来确保前端请求被正确转发到后端 API。

解决方案: 在 client/package.json 中添加 proxy 字段,将其设置为后端服务器地址:

    "proxy": "http://localhost:5000"

这会将所有前端请求(如 /api/repos)代理到 http://localhost:5000,从而使得前端能够向后端 API 发起请求。

确保 server.js 中静态文件的路由放在 API 路由之后:

    app.get('/api/repos', async (req, res) => {
      // 处理请求
    });

    // Serve static files after all API routes
    app.use(express.static(path.join(__dirname, 'client/build')));

启动程序

在backend,frontend目录下各自运行:

  • 启动后端 node server.js

  • 启动前端

npm start

运行界面: enter image description here 显示数据: enter image description here

评论