React实战演练项⽬一需求分析及项目初始化
需求分析
刚学完React,开始找项目进行上手练习!
页面组件拆分:
头部:导航tab、搜索框、登录注册
中间:分类导航、轮播图、新人福利、高单价产品导航
课程分类列表、底部内容、登陆提示浮层、登录/注册弹窗
项目搭建
项目git初始化
git init
本地项目设置远程仓库地址
git remote add origin xxxx
通过npm创建react项目
使用vscode打开项目,然后安装相关依赖,运行项目
npm install
npm run dev
运行成功
删除相关文件,创建首页文件,初始化项目,目录结构
创建首页项目,引入首页
配置Ant-Design+Unocss
安装antDesign
npm install antd
引入并使用组件
什么是Unocss
unocss是一个即时的原子CSS引擎,它可以让你用简短的类名来控制元素的样式,而不需要写复杂的CSS代码。
当然,原子样式也有很多选择,最著名的就是 Tailwind。但由于Tailwind 会生成大量样式定义,会导致全量的 CSS
文件往往体积会多至数 MB,从而有性能上有一些不足。
unocss的优点
- 它可以让你快速地开发和原型设计,而不需要考虑CSS的细节。
- 它可以让你的CSS文件更小,因为它只生成你用到的工具类。
- 它可以让你的CSS更一致,因为它遵循一套预设的规则和变量。
- 它可以让你的CSS更灵活,因为它支持自定义工具类,变体,指令和图标
- 它可以让你的CSS更易于维护,因为它避免了样式冲突和重复代码
官网:https://unocss.dev/integrations/vite
安装Unocss
npm install -D unocss
vite配置
- 再src同级目录下创建unocss.config.tsx,写入:
import { defineConfig, presetAttributify, presetUno } from "unocss";
export default defineConfig({
presets: [
presetAttributify(),
presetUno(),
],
});
2.配置vite.config.tsx
import { defineConfig } from 'vite'
import UnoCss from "unocss/vite"
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), UnoCss()],
})
- main.tsx中引入virtual:uno.css
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import "virtual:uno.css"
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
搜索其他的样式使⽤
https://www.tailwindcss.cn/
安装自动引入插件
说明
unplugin-auto-import 这个插件是为了解决在开发中的导入问题,比如经常不清楚相对路径的问题,这个插件就是解决这个问题,这个插件会在根目录生成一个auto-import.d.ts,这个文件会将所有的插件导入到global中,这样在使用的时候直接就可以使用了。
安装
npm i -D unplugin-auto-import
配置文件vite.config.ts
import { defineConfig } from 'vite'
import UnoCss from "unocss/vite"
import react from '@vitejs/plugin-react'
import AutoImport from "unplugin-auto-import/vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
UnoCss(),
AutoImport({
dts: 'src/types/auto-imports.d.ts',
imports: ['react'],
dirs: ['./src/hooks']
}),
],
})
结果:
开始开发
首页顶部开发准备工作
- 引入首页重置的css
html
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
a {
text-decoration: none;
color: inherit;
}
ul {
list-style: none;
}
button {
border: none;
background: none;
cursor: pointer;
}
.ant-carousel .index-arrow-left,
.ant-carousel .index-arrow-left:hover,
.ant-carousel .index-arrow-left:focus {
font-size: 30px;
left: 20px;
z-index: 2;
color: #4e4d53;
}
.ant-carousel .index-arrow-right,
.ant-carousel .index-arrow-right:hover,
.ant-carousel .index-arrow-right:focus {
font-size: 30px;
right: 30px;
z-index: 2;
color: #4e4d53;
}
main.tsx进行引入
import "../src/assets/react.css"
- 创建commponents文件夹,再创建heard.tsx组件
海报图
海报图链接:https://file.xdclass.net/video/2023/banner/618/QD.gif
设置宽度100%,超出隐藏
高阶函数组件渲染导航条
- 使用高阶组件,来循环遍历产生头部tab导航,传入参数,title,href,以及chilren组件。
import { Input } from "antd"
import { DownOutlined } from "@ant-design/icons";
//tab每一项的接口定义 高阶函数
interface HeaderItemProps {
href: string;
title: string;
children: React.ReactNode
}
// 高阶组件(组件作为参数,返回组件)
const HeaderItem = (props: HeaderItemProps) => {
return (
<a href={props.href} className="c-#4f55d hover:c-#f38e48">
{props.children ?? props.title}
</a>
)
}
// 头部导航组件
const Heard = () => {
// 每项tab的配置
const tabList = [
{ title: "首页", href: "#" },
{ title: "课程中心", href: "#" },
{
title: "超级会员", href: "#", children: (
<div className="flex items-center justify-center">
<img src="https://front.cdn.xdclass.net/images/vip_icon.webp" className="w-22px h-20px" />
<span className="ml-4px">超级会员</span>
</div>
)
},
{ title: "工具", href: "#" },
{
title: "自学路线", href: "#", children: (
<div>
<span>自学路线</span>
<DownOutlined className="ml-4px"></DownOutlined>
</div>
)
},
{
title: "一对一辅导", href: "#", children: (
<div>
<span>一对一辅导</span>
<DownOutlined className="ml-4px"></DownOutlined>
</div>
)
},
{
title: "", href: "#", children: (
<div>
<Input.Search placeholder="请输入搜索内容" className="w-220px"></Input.Search>
</div>
)
},
{ title: "兑换码", href: "#" },
{ title: "云服务器", href: "#" },
]
return (
<div className="flex items-center gap-2">
{/* 官网图表 */}
<img src="https://front.cdn.xdclass.net/images/logo.webp" className="w-138px h-64px" alt="" />
{/* tab导航项 */}
<div className="flex items-center justify-between flex-1">
{
tabList.map((item, index) => (
<HeaderItem key={index} href={item.href} title={item.title} >
{
item?.children
}
</HeaderItem>
))
}
</div>
{/* 注册登录 */}
<div className="flex items-center justify-center gap-4 children:cursor-pointer">
<span className="ml-8px">登录</span>
<span className="bg-#4d555d c-#fff h-30px leading-26px w-60px p-2px text-center" >注册</span>
</div>
</div>
)
}
export default Heard
顶部tab导航栏效果:
注册登录开发
输入账号和密码,注册成功后保存账号密码,保存在localStorage里面
完成效果图:
安装React状态管理库 Zustand
作为React开发者,我们经常需要处理组件之间的状态管理。在大型应用程序中,这可能会变得相当复杂和繁琐。为了简化这个过程,许多状态管理库被开发出来,其中一个非常受欢迎的选择是Zustand。Zustand是一个简单易用的JavaScript库,它提供了一种简洁的方式来管理React应用程序的状态。
Zustand的设计理念是将状态管理的复杂性降到最低,同时提供了一个强大的工具集来处理状态。它采用了一个类似于React Hook的API,使得状态的定义和使用非常直观和简单。
npm install zustand
- 创建hooks/modal.ts,使用zustand进行状态管理
import { create } from "zustand";
// 弹窗状态定义窗口
interface ModalState{
regVisible:boolean,
switchRegVisible:()=>void
}
export const useModal = create<ModalState>((set)=>({
// 注册弹窗状态
regVisible:false,
switchRegVisible:()=>set((state)=>({regVisible:!state.regVisible}))
}));
- 由于上面已经安装了自动引入插件,所以hooks不需要引入,直接使用
Header.tsx使用 - 创建Register.jsx并Header.jsx中引入Register.jsx
const {switchRegVisible} = useModal()
- 直接使用switchRegVisible修改注册弹窗状态方法
<span className="bg-#4d555d c-#fff h-30px leading-26px w-60px p-2px text-center" onClick={switchRegVisible}>注册</span>
- 创建Register.jsx并引入
import { Modal, Form, Input, Button, message } from "antd";
export function Register() {
// 账号
const [account, setAccount] = useState("");
// 密码
const [password, setPassword] = useState("");
// 确认密码
const [rePassword, setRePassword] = useState("");
// 全局公共个⼈状态
const { register, users } = useUser();
// 全局公共注册弹窗状态
const { regVisible, switchRegVisible } = useModal();
// 注册提交按钮
function handleFinish() {
// 密码验证;
if (password !== rePassword) {
message.warning("两次密码不⼀致");
return;
} else {
// 账号验证
if (users.some((user) => user.account === account)) {
message.warning("该账号已存在");
} else {
// 注册
register(account, password);
// 关闭注册弹窗
switchRegVisible();
message.success("注册成功");
}
}
}
return (
<Modal
width="400px"
open={regVisible}
footer={null}
className="relative"
onCancel={switchRegVisible}
>
<h1 className="text-center c-#404040 text-22px font-normal my-8">
密码登录
</h1>
<div className="pb-44px flex items-center justify-center w-full">
<Form
name="basic"
style={{ width: "300px" }}
initialValues={{ remember: true }}
autoComplete="off"
onFinish={handleFinish}
>
{/* 账号 */}
<Form.Item name="account">
<Input
placeholder="请输⼊账号"
value={account}
onChange={(e) => setAccount(e.target.value)}
/>
</Form.Item>
{/* 密码 */}
<Form.Item name="password">
<Input.Password
placeholder="请输⼊密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</Form.Item>
{/* 确认密码 */}
<Form.Item name="re-password">
<Input.Password
placeholder="请再次输⼊密码"
value={rePassword}
onChange={(e) => setRePassword(e.target.value)}
/>
</Form.Item>
{/* 注册 */}
<Form.Item>
<Button
type="primary"
htmlType="submit"
className="flex w-full items-center justify-center bg-#444b52 textwhite rounded-full"
>
<span>⽴即注册</span>
</Button>
</Form.Item>
</Form>
</div>
{/* 跳转登录 */}
<div className="absolute w-full h-44px bottom-0 left-0 bg-
[rgba(77,85,93,0.1)] flex items-center justify-center">
<span>已有账号?</span>
<span className="text-blue-400 cursor-pointer">⽴即登录</span>
</div>
</Modal>
);
}
import Register from "./Register";
- 创建hooks/user.ts,使用zustand进行状态管理
import { create } from "zustand";
// 接口定义
interface UserState {
users: {
account: string;
password: string;
}[];
register: (account: string, password: string) => void;
}
export const useUser = create<UserState>((set) => ({
// 用户信息状态
users: localStorage.getItem("xdclass_react_users")
? JSON.parse(localStorage.getItem("xdclass_react_users") || "[]")
: [],
// 注册更新用户信息
register: (account, password) =>
set((state) => {
// 复制原数据
const users = [...state.users];
users.push({
account,
password,
});
// 数据缓存
localStorage.setItem("xdclass_react_users", JSON.stringify(users));
return {
users,
};
}),
}));
完成效果:
未完待续~