全球新闻系统发布 -- 项目启动环节
项目搭建和环境配置
和之前学的一样,创建一个新项目,科文老师用的是CRA,我这暂时用Vite,因为CRA我这有点容易卡掉
样式相互影响,可能会导致覆盖
可以把.css改成.module.css
加上class之后,需要加一个变量才可以使用:
.list{
background-color: blueviolet;
}
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import style from './child.module.css'
function App() {
return <div>
app
<li className={style.list}></li>
<li ></li>
<li ></li>
</div>
}
export default App
这个猫叫选择器对类有效,标签是力不从心的
项目sass与反向代理
安装sass
npm i --save sass
在Vite中使用sass需要再安装sass-embedded,当项目中没有安装它时,无法正常的编译SCSS文件 ,所以需要安装一下:
npm install -D sass-embedded
然后可以使用语法编写.scss:
我承认我已经忘了啥语法了
首先是可以使用变量:
// 定义变量
$primary-color: #007bff;
$font-size: 16px;
// 使用变量
body {
color: $primary-color;
font-size: $font-size;
}
还可以在选择器内嵌套其他的选择器:
nav {
ul {
list-style: none;
margin: 0;
padding: 0;
li {
display: inline-block;
margin-right: 10px;
a {
color: $primary-color;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
}
可以使用@import指令将多个SCSS合并成一个CSS文件
// _variables.scss 文件
$primary-color: #007bff;
// main.scss 文件
@import 'variables';
body {
color: $primary-color;
}
tips:下划线开头的文件被视为部分文件,不会被单独编译成CSS文件
混合器(可以定义重复使用的代码样式块):
// 定义混合器
@mixin border-radius($radius) {
-webkit-border-radius: $radius;
-moz-border-radius: $radius;
border-radius: $radius;
}
// 使用混合器
.button {
@include border-radius(5px);
}
继承:
.button-base {
padding: 10px 20px;
border: none;
cursor: pointer;
}
.button-primary {
@extend .button-base;
background-color: $primary-color;
color: white;
}
SCSS支持基本的数学运算:
$base-width: 100px;
.box {
width: $base-width * 2;
height: $base-width + 50px;
margin: ($base-width / 2);
}
还可以使用条件语句。。循环语句:
$type: 'alert';
.message {
@if $type == 'success' {
background-color: green;
} @else if $type == 'warning' {
background-color: yellow;
} @else if $type == 'alert' {
background-color: red;
} @else {
background-color: gray;
}
}
$colors: red, blue, green;
@each $color in $colors {
.bg-#{$color} {
background-color: $color;
}
}
@for $i from 1 through 3 {
.item-#{$i} {
width: 20px * $i;
}
}
我的代码:
$width:300px;
ul{
.list{
width: $width;
background-color: blueviolet;
}
}
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import style from './child.module.scss'
function App() {
return (
<div>
app
<ul>
<li className={style.list}></li>
<li></li>
<li></li>
</ul>
<li>111</li>
<li>111</li>
</div>
)
}
export default App
安装axios
npm i axios
安装后在外面找一个没跨域问题的接口,比如猫眼
但是就算是支持跨域,也需要相应的响应字段(后端干的)
科文老师是自己配置的反向代理,然后通过拦截的方式到目标服务器
但是我用的是Vite所以改的文件可能有一些不一样,在尝试了headers里面添加cookie,继续配置更细致的代理,切换协议等方式均不能取得数据的时候我更换了一种策略:
也就是使用Express/Koa代理
首先是安装Express:
npm install express axios cors
第二步创建server.js:
import express from 'express';
import axios from 'axios';
import cors from 'cors';
const app = express();
app.use(cors());
app.get('/api/*', async (req, res) => {
try {
const url = `https://m.maoyan.com${req.originalUrl.replace('/api', '')}`;
const response = await axios.get(url, {
headers: {
Referer: 'https://m.maoyan.com/',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
},
});
res.json(response.data);
} catch (error) {
res.status(500).json({ error: '请求失败', details: error.message });
}
});
app.listen(3001, () => console.log('代理服务器运行在 http://localhost:3001'));
在上一次尝试中还出现了差错:packag.json里面有:
"type": "module"
导致node.js把server.js作为ES模块解析,而 require()
语法是 CommonJS(CJS) 的,不能直接使用。
所以改成了import语法
下一步就是启动代理服务器:
node server.js
以及修改Reactdiamante,启用本地的代理:
useEffect(() => {
axios
.get('http://localhost:3001/api/mmdb/movie/v3/list/hot.json?ct=西安&ci=42&channelId=4')
.then((res) => {
console.log(res.data);
})
.catch((error) => {
console.error('请求出错:', error);
});
}, []);
这个和科文老师的代理的配置各有优缺点:
相当于是如果你只需要本地开发,Vite代理就够了
如果想要支持生产环境,需要修改请求头。处理CORS,则用Node.js代理更好
当使用server.js作为代理时,核心机制是中间人代理
前端请求 -> 代理服务器 -> 目标服务器
作用就是:拦截前端请求,然后代替前端去请求目标服务器,再把结果返回给前端(绕过CORS限制)
工作流程:
前端请求代理服务器(
http://localhost:3001/api/movies
)。代理服务器拦截请求:
解析
req
,检查路径/api/movies
。修改请求头(如
Referer
、User-Agent
)。代理服务器向目标服务器发送请求:
请求
https://m.maoyan.com/mmdb/movie/v3/list/hot.json
。代理服务器获取数据:
接收
m.maoyan.com
的数据,并返回给前端。前端收到代理返回的数据:
以为是自己的后端返回的数据,不会触发 CORS 错误。
核心作用:
绕过 CORS 限制:
目标服务器
m.maoyan.com
不允许直接跨域访问。代理服务器不受跨域限制,它可以直接访问
m.maoyan.com
,然后把数据返回给前端。隐藏 API Key / 修改请求头:
如果目标 API 需要特定的
Referer
或User-Agent
,代理可以伪装请求,让目标服务器以为请求来自合法客户端。屏蔽敏感信息:
你可以过滤、修改数据,避免泄露
API Key
或其他敏感信息。缓存 & 降低目标服务器压力:
代理服务器可以缓存热门请求,减少对
m.maoyan.com
的访问,提高性能。
CORS机制是一种浏览器安全机制,用来限制跨域请求,防止恶意网站访问你的数据
当前端代码尝试从不同源(域名、端口、协议不同)获取数据的时候,如果服务器没有正确的设置CORS头部,浏览器会拒绝请求,导致CORS错误
浏览器为了安全性,默认禁止跨域请求
如何解决跨域问题呢?
方案一:后端允许跨域(最优解)
如果你控制后端代码,可以在服务器上添加 CORS 头部,允许跨域访问。
方案二:使用代理服务器:
-
前端请求代理服务器(localhost:3001) ✅ 不跨域
-
代理服务器请求目标 API(m.maoyan.com) ✅ 没有浏览器 CORS 限制
-
代理服务器返回数据给前端
如果项目部署子啊服务器上,还可以使用Nginx反向代理实现类似代理服务器的功能
CORS不是bug,而是一种安全机制,防止恶意网站在后台偷偷读取你的数据
项目启动:路由架构
要授权才可以访问后面的那些东西,否则你刚进去都会到Login组件里面(未授权重定向)
路由也可以直接写到根组件里,但是都说是组件化开发了,还是单独新建一个文件夹写路由比较好
欲使用,先安装:
npm i --save-dev react-router-dom
令人感到好笑的是,科文老师在项目阶段才讲到了他用的rcc和rfc的插件
Switch
组件是 react-router-dom
v5 及之前的版本中的一个重要组件,用于匹配和渲染第一个符合条件的路由。它的作用是保证一次只渲染一个 Route
组件
从上到下检查Route组件的path,渲染第一个匹配的Route,忽略后面的Route
如果要是不加上Switch的话,在访问/login的时候,/也是匹配的(因为/login以/开头)
Login和NewsSandBox都会被渲染,可能导致页面 错误
科文老师的写法是react-router-dom
v5
以下的写法是适用于react-router-dom
v6
import React from "react";
import { HashRouter, Routes, Route } from "react-router-dom";
import NewsSandBox from "../views/sandbox/NewsSandBox";
export default function IndexRouter() {
return (
<HashRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/*" element={<NewsSandBox />} />
</Routes>
</HashRouter>
);
}
但是还没有实现带拦截的功能,所以我们要实现带拦截并且重定向
重定向还是用了新的,从render到element:
import React from "react";
import { HashRouter, Routes, Route, Navigate } from "react-router-dom";
import NewsSandBox from "../views/sandbox/NewsSandBox";
import Login from '../views/login/Login';
export default function IndexRouter() {
return (
<HashRouter>
<Routes>
{/* 登录页 */}
<Route path="/login" element={<Login />} />
{/* 受保护路由:如果没有 token,重定向到 /login */}
<Route path="/*" element={
localStorage.getItem("token") ?
<NewsSandBox /> :
<Navigate to="/login" replace />
} />
</Routes>
</HashRouter>
);
}
使用Navigate来代替Redirect,用于进行页面跳转
replace表示替换当前URL,不会留下浏览器历史记录
而render是每次访问路由的时候都会执行的一个函数
v6就不支持render了,用element接收组件JSX,而不会每一次都执行函数
改进的原因:
减少不必要函数执行(很影响性能)
语法更加的直观,更符合JSX的语法
还可以简化API的设计
项目启动-搭建路由
进行路由的搭建:
import React from 'react'
import SideMenu from '../../components/sandbox/SideMenu'
import TopHeader from '../../components/sandbox/TopHeader'
import {
BrowserRouter as Router,
Routes,
Route,
Navigate,
} from 'react-router-dom'
import Home from './home/Home'
import RightList from './right-manage/RightList'
import RoleList from './right-manage/RoleList'
import UserList from './user-manage/UserList'
export default function NewsSandBox() {
return (
<div>
<SideMenu></SideMenu>
<TopHeader></TopHeader>
<Router>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/user-manage/list" element={<UserList />} />
<Route path="/right-manage/role/list" element={<RoleList />} />
<Route path="/right-manage/right/list" element={<RightList />} />
<Route path="/" element={<Navigate to="/home" />} />
</Routes>
</Router>
</div>
)
}
项目目录:
还要加一个额外的处理:
import React from 'react'
import SideMenu from '../../components/sandbox/SideMenu'
import TopHeader from '../../components/sandbox/TopHeader'
import {
BrowserRouter as Router,
Routes,
Route,
Navigate,
} from 'react-router-dom'
import Home from './home/Home'
import RightList from './right-manage/RightList'
import RoleList from './right-manage/RoleList'
import UserList from './user-manage/UserList'
import Nopermisson from './nopermisson/Nopermisson'
export default function NewsSandBox() {
return (
<div>
<SideMenu></SideMenu>
<TopHeader></TopHeader>
<Router>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/user-manage/list" element={<UserList />} />
<Route path="/right-manage/role/list" element={<RoleList />} />
<Route path="/right-manage/right/list" element={<RightList />} />
<Route path="/" element={<Navigate to="/home" />} />
<Route path='*' element={<Nopermisson/>}/>
</Routes>
</Router>
</div>
)
}
Antd引入
自己写页面不如用别人写好的
站在巨人的肩膀上
我一向是秉承着求己不如求人的观念的
Ant Design - 一套企业级 UI 设计语言和 React 组件库https://ant-design.antgroup.com/index-cn安装Antd:
npm install antd --save
使用的时候就是按需引入
引入的时候抄它代码就好