react1-创建样式、基本语法、样式、常用事件、useState()、计算器案例及购物车案例
react
创建项目
npm create vite@latest 选择react、JavaScript
npm i 创建依赖包
npm run dev 运行
项目架构

删除CSS文件、public目录、assets目录
第一个react项目
JSX:javascript xml 不是真正的 HTML,而是一种语法糖,最终会被编译成普通的 JavaScript
删除App.jsx中的所有代码,重写:
import Greet from "./components/greet"
const App = () => {
return (
<div>
<Greet/>
</div>
)
}
export default App
const Greet = () => {
return <h1>yaunyu</h1>
}
export default Greet
插件
ES7 React/Redux/GraphQL/React-Native
安装后通过:rafce可以快速写出jsx的代码模板
语法
基础语法
-
一定记得export否则报错
-
函数的第一个字母必须大写
-
通过return只能返回一个父HTML元素
-
return的元素必须闭合
-
jsx的属性是驼峰:
-
类不是class而是className
-
form表单的label不是for而是htmlFor
-
-
{ }中的内容是表达式,可进行计算,如{ 2 + 2 }显示的是4,可调用方法
不仅可以写在标签内,也可以写在className内
-
()中的内容是html标签
渲染数组
渲染数组时,标签中必须使用key来标识
const App = () => {
const userInfo = [
{
name: 'Jack',
age: 12,
country: 'China'
},
{
name: 'Mick',
age: 16,
country: 'China'
},
{
name: 'Jone',
age: 19,
country: 'China'
}
]
return (
<div>
{
userInfo.map(({name, age, country}) =>
(
<ul key={Math.random()}>
<li>{name}</li>
<li>{age}</li>
<li>{country}</li>
</ul>
)
)
}
</div>
)
}
export default App
条件渲染
// 父组件
import Password from './components/Password'
const App = () => {
return (
<Password isValid = {true}/>
)
}
export default App
// 子组件①
const valid = 'This is valid password.'
const invalid = 'This is invalid password.'
const Password = ({isValid}) => {
return (
<div>
{
isValid ? <h1>{valid}</h1> : <h1>{invalid}</h1>
}
</div>
)
}
export default Password
// 子组件②
const valid = 'This is valid password.'
const invalid = 'This is invalid password.'
const Password = ({isValid}) => {
if (isValid) return <h1> {valid} </h1>
else return <h1> {invalid} </h1>
}
export default Password
const App = () => {
const item = ['wireless earbuds', 'power bank', 'new ssd', 'hoddies']
return (
<>
<h1>Cart🛒</h1>
{item.length > 0 && <h2>You have {item.length} items in your cart.</h2>}
<h1>products👇</h1>
<ul>
{
item.map(i => (
<li key={Math.random()}>{i}</li>
))
}
</ul>
</>
)
}
export default App
props传递参数
允许父组件使用props向子组件传递参数
// 父组件
import User from './components/User'
const App = () => {
return (
<User
img = 'https://i.cetsteam.com/imgs/2025/08/21/7af16499253b4c06.jpg'
name = 'yuanyu'
age = { 24 }
isMarried = { false }
hobbies = {['sleeping', 'playing', 'reading']}
/>
)
}
export default App
// 子组件
const User = (props) => {
return (
<div>
<img src = {props.img} alt = { props.name } />
<h2>name: { props.name }</h2>
<h2>age: { props.age }</h2>
<h2>isMarried: { props.isMarried }</h2>
<h2>hobbies: { props.hobbies }</h2>
</div>
)
}
export default User
// 子组件解构
const User = ({img, name, age, isMarried, hobbies}) => {
return (
<div>
<img src = {img} alt = { name } width={200}/>
<h2>name: { name }</h2>
<h2>age: { age }</h2>
<h2>isMarried: { isMarried }</h2>
<h2>hobbies: { hobbies }</h2>
</div>
)
}
export default User
样式
行内样式
语法:style = {{ 属性名: '属性值' }}
const App = () => {
return <> <h1 style = {{ color:'red' }}>hello</h1> </>
}
export default App
内部样式
语法:xxstyle = { 属性名: '属性值' } <标签 style = {xxstyle }>
const separateStyle = {color:'red', backgroundColor: 'skyblue', padding: '20px'}
const App = () => {
return <> <h1 style = {separateStyle}>Separate Style</h1> </>
}
export default App
外部样式
import './index.css'
const App = () => {
return (
<>
<h1>Outside Style</h1>
</>
)
}
export default App
*{
margin: 0;
padding: 0;
}
body{
background-color: antiquewhite;
}
h1{
color:blueviolet
}
icon
网址:React Icons
安装:npm install react-icons --save
使用:
import { BsFillBalloonHeartFill } from "react-icons/bs";
const IconComponent = () => {
return (
<div>
<BsFillBalloonHeartFill size={60} color="pink"/>
</div>
)
}
export default IconComponent
事件
const handleCopy = () => {
console.log('thief!')
}
const Copy = () => {
return (
<div onCopy={handleCopy}>
春江潮水连海平,海上明月共潮生。
滟滟随波千万里,何处春江无月明。
江流宛转绕芳甸,月照花林皆似霰。
空里流霜不觉飞,汀上白沙看不见。
江天一色无纤尘,皎皎空中孤月轮。
江畔何人初见月?江月何年初照人?
人生代代无穷已,江月年年望相似。
不知江月待何人,但见长江送流水。
白云一片去悠悠,青枫浦上不胜愁。
谁家今夜扁舟子?何处相思明月楼?
可怜楼上月裴回,应照离人妆镜台。
玉户帘中卷不去,捣衣砧上拂还来。
此时相望不相闻,愿逐月华流照君。
鸿雁长飞光不度,鱼龙潜跃水成文。
昨夜闲潭梦落花,可怜春半不还家。
江水流春去欲尽,江潭落月复西斜。
斜月沉沉藏海雾,碣石潇湘无限路。
不知乘月几人归,落月摇情满江树。
</div>
)
}
export default Copy
import Copy from './components/Copy'
const App = () => {
return (
<>
<Copy/>
</>
)
}
export default App
常用事件
| 事件 | 触发条件 |
|---|---|
| onClick | 鼠标点击元素时触发 |
| onDoubleClick | 鼠标双击元素时触发 |
| onMouseEnter / onMouseLeave | 鼠标进入 / 离开元素时触发(不冒泡) |
| onMouseDown / onMouseUp | 鼠标按下 / 松开时触发 |
| onKeyDown / onKeyUp | 按下 / 松开键盘按键时触发 |
| onChange | 表单元素值发生变化时触发 |
| onSubmit | 表单提交时触发 |
| onFocus / onBlur | 元素获取 / 失去焦点时触发 |
| onScroll | 元素滚动时触发 |
| onCopy | 复制时触发 |
hooks
state:用于储存和管理数据
react16.8的新功能,可以在不编写类组件的情况下使用state
允许在函数组件中管理状态
useState()
可以在组件中追踪数据和属性
const [data, changeData] = useState() ‘
- data:初始值
- changeData:改变值的函数

import { useState } from "react"
const App = () => {
const [count, setCount] = useState(0)
const increment = () => setCount(count + 1)
const decrement = () => setCount(count - 1)
return <>
<h1>{count}</h1>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</>
}
export default App
使用set方法改变的值会使得应用重新渲染,如果直接改变值不会重新渲染
import { useState } from "react"
const App = () => {
const [games, setGames] = useState(['一梦江湖', '逆水寒', '燕云十六声'])
const addNew = () => setGames([...games,'以闪亮之名'])
const moveGame = () => setGames(games.slice(0,-1))
const updateGame = () => setGames(['一梦江湖1', '逆水寒', '燕云十六声'])
return <>
{
games.map(game => {
return <li key={Math.random()}>{game}</li>
})
}
<button onClick={addNew}>add new game</button>
<button onClick={moveGame}>move a game</button>
<button onClick={updateGame}>update a game</button>
</>
}
export default App
父子组件通信
import { useState } from "react"
import CountOne from "./components/CountOne"
import CountTwo from "./components/CountTwo"
const App = () => {
const [count, setCount] = useState(0)
return <>
<CountOne count={count} onClickHandler={() => setCount(count + 1)}/>
<CountTwo count={count} onClickHandler={() => setCount(count + 1)}/>
</>
}
export default App
const CountOne = ({count, onClickHandler}) => {
return (
<div>
<h1>{count}</h1>
<button onClick={onClickHandler}>increment</button>
</div>
)
}
export default CountOne
注意:setCount中只能写count+1而不是count++
count + 1是表达式,返回的是count加 1 后的新值(不会修改原变量)
例如:count = 0时,count + 1返回1,count本身仍为0count ++是自增语句,特点是先返回count的当前值,再将count加 1(会修改原变量)
双向绑定
import { useState, useEffect } from "react"
const CountTwo = () => {
const [name, setName] = useState(() => {
const saveName = localStorage.getItem('name') || ''
saveName ? JSON.parse(saveName) : ''
})
useEffect(()=>{
localStorage.setItem('name',JSON.stringify(name))
},[name])
const changeHandler = (e) => {
setName(e.target.value)
}
const clearHandler = () => {
setName('')
localStorage.removeItem('name')
}
return (
<div>
<h1>your name: {name}</h1>
<input type="text" value={name} onChange={changeHandler} placeholder="enter your name"/>
<button onClick={clearHandler}>clear name</button>
</div>
)
}
export default CountTwo
import CountTwo from "./components/CountTwo"
const App = () => {
return <>
<CountTwo/>
</>
}
export default App

三处可实现响应式
数组
import { useState } from "react"
const TodoList = () => {
const [item, setItem] = useState('')
const [todoList, setTodoList] = useState([])
const addHandler = (e) => {
e.preventDefault()
if (item.trim()) {
setTodoList([...todoList, item])
setItem('')
}
}
const changeHandler = (e) => {
setItem(e.target.value)
}
return (
<div>
<form action="#" onSubmit={addHandler}>
代办项输入:<input type="text" value={item} onChange={changeHandler} />
<button type="submit">增加代办项</button>
</form>
<h1>代办项展示</h1>
<ul>
{
todoList.map((todoItem, index) => {
return <li key={index}>{todoItem}</li>
})
}
</ul>
</div>
)
}
export default TodoList

对象
import { useState } from 'react'
const Profile = () => {
const [info, setInfo] = useState({
name: '',
age: ''
})
const handleChange = (e) => {
const {name, value} = e.target
setInfo({
...info,
[name]: value,
})
}
return (
<div>
<h1>profile information</h1>
<div>
<label htmlFor="name">Name:
<input type="text" value={info.name} name='name' onChange={handleChange}/>
</label>
</div>
<div>
<label htmlFor="age">Age:
<input type="text" value={info.age} name='age' onChange={handleChange}/>
</label>
</div>
<div>
<h2>Name: {info.name}</h2>
<h2>Age: {info.age}</h2>
</div>
</div>
)
}
export default Profile

数组对象
import { useState } from 'react'
const ShoppingList = () => {
const [shoppingList, setShoppingList] = useState([])
const [name, setName] = useState('')
const [quantity, setQuantity] = useState('')
const handleSubmit = (e) => {
e.preventDefault()
setShoppingList([...shoppingList,{name, quantity}])
}
const handleChange = (e) => {
const {name, value} = e.target
if (name === 'name') setName(value)
if (name === 'quantity') setQuantity(value)
}
return (
<div>
<form action="#" onSubmit={handleSubmit}>
<label htmlFor="name">Name:
<input type="text" value={name} name='name' onChange={handleChange}/>
</label>
<label htmlFor="quantity" style={{margin:'0 10px'}}>Quantity:
<input type="text" value={quantity} name='quantity' onChange={handleChange}/>
</label>
<button type='submit'>Add</button>
</form>
<h1>Shopping List</h1>
<ul>
{
shoppingList.map((item, index) => {
return <li key={index}>Name: {item.name} Quantity:{item.quantity}</li>
})
}
</ul>
</div>
)
}
export default ShoppingList

计算器案例
import { useState } from 'react'
import '../style.css'
const Calculate = () => {
const [value, setValue] = useState('')
const calculate = () => {
const ans = eval(value)
setValue(ans)
}
return (
<div className='container'>
<div className="show">
<p>{value}</p>
</div>
<div className="calConatiner">
<p className='clear' onClick={() => setValue('')}>c</p>
<p className='btn' onClick={() => setValue(value + '/')}>/</p>
<p className='btn' onClick={() => setValue(value + '*')}>*</p>
<p className='btn' onClick={() => setValue(value + '7')}>7</p>
<p className='btn' onClick={() => setValue(value + '8')}>8</p>
<p className='btn' onClick={() => setValue(value + '9')}>9</p>
<p className='btn' onClick={() => setValue(value + '-')}>-</p>
<p className='btn' onClick={() => setValue(value + '4')}>4</p>
<p className='btn' onClick={() => setValue(value + '5')}>5</p>
<p className='btn' onClick={() => setValue(value + '6')}>6</p>
<p className='plus' onClick={() => setValue(value + '+')}>+</p>
<p className='btnPosition1' onClick={() => setValue(value + '1')}>1</p>
<p className='btnPosition2' onClick={() => setValue(value + '2')}>2</p>
<p className='btnPosition3' onClick={() => setValue(value + '3')}>3</p>
<p className='btn' onClick={() => setValue(value + '0')}>0</p>
<p className='btn' onClick={() => setValue(value + '00')}>00</p>
<p className='btn' onClick={() => setValue(value + '.')}>.</p>
<p className='btn' onClick={calculate}>=</p>
</div>
</div>
)
}
export default Calculate
body{
margin: 0;
padding: 0;
width: 100vw;
height: 100vh;
}
#root{
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.container{
width: 320px;
height: 550px;
background-color: #fff;
border: 1px solid black;
font-size: 25px;
text-align: center;
line-height: 80px;
}
.show{
height: 150px;
display: flex;
align-items: center;
justify-content: flex-end;
}
.calConatiner{
height: 400px;
display: flex;
flex-wrap: wrap;
cursor: pointer;
}
.clear{
width: 160px;
height: 80px;
background-color: rgb(250, 162, 0);
margin: 0;
color: #fff;
}
.btn{
width: 80px;
height: 80px;
background-color: rgb(41, 41, 41);
color: #fff;
margin: 0;
}
.plus{
width: 80px;
height: 160px;
margin: 0;
background-color: rgb(41, 41, 41);
color: #fff;
line-height: 160px;
}
.btnPosition1{
position: absolute;
bottom: 132px;
width: 80px;
height: 80px;
background-color: rgb(41, 41, 41);
color: #fff;
margin: 0;
}
.btnPosition2{
position: absolute;
bottom: 132px;
left: 332px;
width: 80px;
height: 80px;
background-color: rgb(41, 41, 41);
color: #fff;
margin: 0;
}
.btnPosition3{
position: absolute;
bottom: 132px;
left: 412px;
width: 80px;
height: 80px;
background-color: rgb(41, 41, 41);
color: #fff;
margin: 0;
}
.btn:hover,
.plus:hover,
.btnPosition1:hover,
.btnPosition2:hover
.btnPosition3:hover{
background-color: rgb(250, 162, 0);
}

购物车案例
语法糖:如果在 JSX 中直接放置一个数组时,React 会自动遍历这个数组,并将数组中的每个元素依次渲染到页面上
例:
父组件:
const Products = ({result}) => {
return (
<section className='product'>
{result}
</section>
)
}
export default Products
子组件:直接解构,无需再次遍历循环
export default function card({ img, title, reviews, prePrice, newPrice }) {
return (
<div>
<img src={img} alt={title} />
<p>{title}</p>
<span>{reviews}</span>
<p>{prePrice}</p>
<p>{newPrice}</p>
</div >
)
}
目录结构:

展示:



App.jsx
import { useState } from 'react'
import Navigation from './Navigation/Nav'
import Products from './Products/Products'
import Reconmended from './Reconmended/Reconmended'
import SideBar from './SideBar/SideBar'
import './index.css'
import products from './db/data'
import Card from './components/Card.jsx'
const App = () => {
const [selectedCate, setSelectedCate] = useState(null)
const [query, setQuery] = useState('')
const handleSearch = (e) => setQuery(e.target.value)
const filterItem = products.filter(product => product.title.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()) !== -1)
const handleRadio = (e) => setSelectedCate(e.target.value)
const filterData = (products, selected, query) => {
let filterPro = products
if (query) filterPro = filterItem
if (selected && selected !== 'all') {
// 保留匹配上的
filterPro = filterPro.filter(({ category, color, company, newPrice, title }) =>
category === selected || color === selected || company === selected || newPrice === selected || title === selected
)
}
return filterPro.map(({ img, title, reviews, prevPrice, newPrice }) => (
<Card key={Math.random()} img={img} title={title} review={reviews} prevPrice={prevPrice} newPrice={newPrice}></Card>
))
}
const result = filterData(products, selectedCate, query)
return <div className='demo'>
<SideBar handleRadio={handleRadio} />
<div className="right">
<Navigation handleSearch={handleSearch} query={query}/>
<Reconmended handleRadio={handleRadio} />
<Products result={result}/>
</div>
</div>
}
export default App
index.css
*{
margin: 0;
padding: 0;
}
.demo{
display: flex;
}
.right{
flex: 1;
}
db/data.js
import { AiFillStar } from "react-icons/ai";
const data = [
{
img: "https://m.media-amazon.com/images/I/6125yAfsJKL._AC_UX575_.jpg",
title: "Nike Air Monarch IV",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "200",
company: "Nike",
color: "white",
category: "sneakers",
},
{
img: "https://m.media-amazon.com/images/I/51+P9uAvb1L._AC_UY695_.jpg",
title: "Nike Waffle One Sneaker",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "200",
company: "Nike",
color: "green",
category: "sneakers",
},
{
img: "https://m.media-amazon.com/images/I/71oEKkghg-L._AC_UX575_.jpg",
title: "Nike Running Shoe",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "200",
company: "Adidas",
color: "black",
category: "sneakers",
},
{
img: "https://m.media-amazon.com/images/I/41M54ztS6IL._AC_SY625._SX._UX._SY._UY_.jpg",
title: "Flat Slip On Pumps",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "200",
company: "Vans",
color: "green",
category: "flats",
},
{
img: "https://m.media-amazon.com/images/I/71zKuNICJAL._AC_UX625_.jpg",
title: "Knit Ballet Flat",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "50",
company: "Adidas",
color: "black",
category: "flats",
},
{
img: "https://m.media-amazon.com/images/I/61V9APfz97L._AC_UY695_.jpg",
title: "Loafer Flats",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "50",
company: "Vans",
color: "white",
category: "flats",
},
{
img: "https://m.media-amazon.com/images/I/71VaQ+V6XnL._AC_UY695_.jpg",
title: "Nike Zoom Freak",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "200",
company: "Nike",
color: "green",
category: "sneakers",
},
{
img: "https://m.media-amazon.com/images/I/61-cBsLhJHL._AC_UY695_.jpg",
title: "Nike Men's Sneaker",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "200",
company: "Adidas",
color: "blue",
category: "sneakers",
},
{
img: "https://m.media-amazon.com/images/I/81xXDjojYKS._AC_UX575_.jpg",
title: "PUMA BLACK-OCE",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "150",
company: "Puma",
color: "green",
category: "sneakers",
},
{
img: "https://m.media-amazon.com/images/I/71E75yRwCDL._AC_UY575_.jpg",
title: "Pacer Future Sneaker",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "150",
company: "Puma",
color: "red",
category: "sneakers",
},
{
img: "https://m.media-amazon.com/images/I/71jeoX0rMBL._AC_UX575_.jpg",
title: "Unisex-Adult Super",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "150",
company: "Puma",
color: "black",
category: "sneakers",
},
{
img: "https://m.media-amazon.com/images/I/61TM6Q9dvxL._AC_UX575_.jpg",
title: "Roma Basic Sneaker",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "150",
company: "Puma",
color: "white",
category: "sneakers",
},
{
img: "https://m.media-amazon.com/images/I/7128-af7joL._AC_UY575_.jpg",
title: "Pacer Future Doubleknit",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "150",
company: "Puma",
color: "black",
category: "sneakers",
},
{
img: "https://m.media-amazon.com/images/I/81xXDjojYKS._AC_UX575_.jpg",
title: "Fusion Evo Golf Shoe",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "100",
company: "Puma",
color: "green",
category: "sneakers",
},
{
img: "https://m.media-amazon.com/images/I/719gdz8lsTS._AC_UX575_.jpg",
title: "Rainbow Chex Skate",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "100",
company: "Vans",
color: "red",
category: "flats",
},
{
img: "https://m.media-amazon.com/images/I/71gpFHJlnoL._AC_UX575_.jpg",
title: "Low-Top Trainers",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "100",
company: "Vans",
color: "white",
category: "sandals",
},
{
img: "https://m.media-amazon.com/images/I/71pf7VFs9CL._AC_UX575_.jpg",
title: "Vans Unisex Low-Top",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "100",
company: "Vans",
color: "blue",
category: "sandals",
},
{
img: "https://m.media-amazon.com/images/I/61N4GyWcHPL._AC_UY575_.jpg",
title: "Classic Bandana Sneakers",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "50",
company: "Nike",
color: "black",
category: "sandals",
},
{
img: "https://m.media-amazon.com/images/I/61bncQ44yML._AC_UX695_.jpg",
title: "Chunky High Heel",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "50",
company: "Vans",
color: "black",
category: "heels",
},
{
img: "https://m.media-amazon.com/images/I/71czu7WgGuL._AC_UY695_.jpg",
title: "Slip On Stiletto High Heel",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "100",
company: "puma",
color: "black",
category: "heels",
},
{
img: "https://m.media-amazon.com/images/I/61men05KRxL._AC_UY625_.jpg",
title: "DREAM PAIRS Court Shoes",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "150",
company: "Nike",
color: "red",
category: "heels",
},
{
img: "https://m.media-amazon.com/images/I/519MRhRKGFL._AC_UX575_.jpg",
title: "Nike Air Vapormax Plus",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "200",
company: "Nike",
color: "red",
category: "sneakers",
},
{
img: "https://m.media-amazon.com/images/I/51PGWTXgf-L._AC_UY625_.jpg",
title: "Low Mid Block Heels",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "200",
company: "Nike",
color: "black",
category: "heels",
},
{
img: "https://m.media-amazon.com/images/I/616sA5XUKtL._AC_UY675_.jpg",
title: "Chunky High Heel",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "50",
company: "Adidas",
color: "black",
category: "heels",
},
{
img: "https://m.media-amazon.com/images/I/71h5+MbEK7L._AC_UY625_.jpg",
title: "Amore Fashion Stilettos",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "150",
company: "Adidas",
color: "white",
category: "heels",
},
{
img: "https://m.media-amazon.com/images/I/61uw5RDxKQL._AC_UY625_.jpg",
title: "Bridal Sandals Glitter",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "50",
company: "Adidas",
color: "black",
category: "heels",
},
{
img: "https://m.media-amazon.com/images/I/71yhoZP0l6L._AC_UY695_.jpg",
title: "Wedding Prom Bridal",
star: '<AiFillStar className="rating-star" />',
reviews: "(123 reviews)",
prevPrice: "$140,00",
newPrice: "50",
company: "Adidas",
color: "black",
category: "flats",
},
];
export default data;
Navigation/Nav.jsx
import './Nav.css'
import { IoMdHeartEmpty } from "react-icons/io";
import { IoCartOutline } from "react-icons/io5";
import { FiUser } from "react-icons/fi";
const Nav = ({handleSearch}) => {
return (
<nav className='navContainer'>
<div className="search">
<input type="text" className='searchInput' placeholder='Enter your search shoes.' onChange={handleSearch}/>
</div>
<div className="profileContainer">
<IoMdHeartEmpty className='navIcon'/>
<IoCartOutline className='navIcon'/>
<FiUser className='navIcon'/>
</div>
</nav>
)
}
export default Nav
Navigation/Nav.css
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
.navContainer{
display: flex;
align-items: center;
padding: 0 100px;
border-bottom: 1px solid #f3f3f3;
height: 50px;
width: 100%;
justify-content: space-between;
}
.searchInput{
outline: none;
border: none;
background-color: #f7f6f6;
padding: 7px 10px;
border-radius: 5px;
}
.navIcon{
color: rgb(41, 41, 41);
font-size: 18px;
margin-right: 20px;
}
Products/Products.jsx
import './Products.css'
import Card from '../components/Card'
const Products = ({result}) => {
return (
<section className='product'>
{result}
</section>
)
}
export default Products
Products/Products.css
*{
margin: 0;
padding: 0;
}
.cardContainer{
margin-top: 50px;
margin-left: 50px;
display: flex;
flex-wrap: wrap;
align-items: center;
}
.product{
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
}
.card{
width: 200px;
height: 175px;
border: 1px solid #ededed;
padding: 10px;
cursor: pointer;
}
.pic{
display: flex;
justify-content: center;
}
img{
width: 120px;
height: 60px;
}
.proTitle{
padding-top: 10px;
font-weight: 700;
font-size: 14px;
height: 40px;
}
.review{
white-space: nowrap;
font-size: 12px;
margin-left: 5px;
}
.starContainer{
display: flex;
align-items: center;
margin: 8px 0;
}
.star{
color: #d5ab55;
}
.price{
display: flex;
font-size: 13px;
}
.oldPrice{
font-size: 13px;
margin-right: 5px;
text-decoration: line-through;
}
.lock{
font-size: 13px;
}
.price_lock{
display: flex;
justify-content: space-between;
align-items: center;
}
Recommended/Recommended.jsx
import './Reconmended.css'
const Reconmended = ({handleRadio}) => {
return (
<section className='recommendedContainer'>
<p className='title'>Recommended</p>
<div className="products">
<button className="item" value='all' onClick={handleRadio}>All Products</button>
<button className="item" value='Nike' onClick={handleRadio}>Nike</button>
<button className="item" value='Adidas' onClick={handleRadio}>Adidas</button>
<button className="item" value='Puma' onClick={handleRadio}>Puma</button>
<button className="item" value='Vans' onClick={handleRadio}>Vans</button>
</div>
</section>
)
}
export default Reconmended
Recommended/Recommended.css
*{
margin: 0;
padding: 0;
}
.recommendedContainer{
margin-top: 20px;
margin-left: 50px;
}
.title{
font-weight: 700;
margin-bottom: 20px;
}
.item{
color: rgb(134, 131, 131);
font-size: 13px;
border: 1px solid rgb(134, 131, 131);
padding: 5px 10px;
border-radius: 5px;
margin-right: 10px;
cursor: pointer;
background-color: #fff;
}
Sidebar/Category/Category.jsx
import './Category.css'
import Radio from '../../components/Radio'
const Category = ({ handleRadio }) => {
return (
<div className="categoryContainer">
<p className='sidebarTitle'>Category</p>
<div className='select'>
<Radio handleRadio={handleRadio} value='all' title='All' name='type' classStyle='radio'/>
<Radio handleRadio={handleRadio} value='sneakers' title='Skneakers' name='type' classStyle='radio'/>
<Radio handleRadio={handleRadio} value='flats' title='Flats' name='type' classStyle='radio'/>
<Radio handleRadio={handleRadio} value='heels' title='Heels' name='type' classStyle='radio'/>
<Radio handleRadio={handleRadio} value='sandals' title='Sandals' name='type' classStyle='radio'/>
</div>
</div>
)
}
export default Category
SideBar/Category/Category.css
*{
margin: 0;
padding: 0;
}
.categoryContainer{
display: flex;
/* justify-content: center; */
flex-wrap: wrap;
}
.select{
margin-left: 82px;
}
.sidebarTitle{
width: 100%;
text-align: center;
margin: 20px 0;
}
.radio{
margin-right: 10px;
outline: none;
cursor: pointer;
}
.cateTitle{
font-size: 14px;
color: rgb(41, 41, 41);
}
SideBar/Colors/Colors.jsx
import './Color.css'
import Radio from '../../components/Radio'
const Colors = ({handleRadio}) => {
return (
<div className='categoryContainer'>
<p className='sidebarTitle'>Colors</p>
<div className='select'>
<Radio handleRadio={handleRadio} value='all' title='All' name='color' classStyle='all' />
<Radio handleRadio={handleRadio} value='black' title='Black' name='color' classStyle='black' />
<Radio handleRadio={handleRadio} value='blue' title='Blue' name='color' classStyle='blue' />
<Radio handleRadio={handleRadio} value='red' title='Red' name='color' classStyle='red' />
<Radio handleRadio={handleRadio} value='green' title='Green' name='color' classStyle='green' />
<Radio handleRadio={handleRadio} value='white' title='White' name='color' classStyle='white' />
</div>
</div>
)
}
export default Colors
SideBar/Colors/Colors.css
.all,.black,.blue,.red,.green,.white{
margin-right: 10px;
outline: none;
position: relative;
cursor: pointer;
}
.all::after,.black::after,.blue::after,.red::after,.green::after,.white::after{
position: absolute;
content: '';
width: 13px;
height: 13px;
border-radius: 50%;
background-color: red;
z-index: 999;
top: 0;
left: 0;
}
.all::after{
background: linear-gradient(blue, crimson)
}
.black::after{
background-color: black;
}
.blue::after{
background-color: blue;
}
.green::after{
background-color: green;
}
.white::after{
background-color: #fff;
}
input[type="radio"]:checked {
transform: scale(1.2);
}
SideBar/Price/Price.jsx
import './Price.css'
import Radio from '../../components/Radio'
const Price = ({handleRadio}) => {
return (
<div className="categoryContainer">
<p className='sidebarTitle'>Price</p>
<div className='select'>
<Radio handleRadio={handleRadio} value='all' title='All' name='price' classStyle='radio' />
<Radio handleRadio={handleRadio} value='50' title='0-$50' name='price' classStyle='radio' />
<Radio handleRadio={handleRadio} value='100' title='$50-$100' name='price' classStyle='radio' />
<Radio handleRadio={handleRadio} value='150' title='$100-$150' name='price' classStyle='radio' />
<Radio handleRadio={handleRadio} value='200' title='over $150' name='price' classStyle='radio' />
</div>
</div>
)
}
export default Price
SideBar/SideBar.jsx
import { GiShoppingCart } from "react-icons/gi";
import './SideBar.css'
import Category from './Category/Category'
import Price from './Price/Price'
import Colors from './Colors/Colors'
const SideBar = ({handleRadio}) => {
return (
<div className="sideBar">
<div className="logo">
<GiShoppingCart className="shoppingCart"/>
</div>
<Category handleRadio={handleRadio}/>
<Price handleRadio={handleRadio}/>
<Colors handleRadio={handleRadio}/>
</div>
)
}
export default SideBar
SideBar/SideBar.css
*{
margin: 0;
padding: 0;
}
.sideBar{
width: 250px;
height: 100vh;
border-right: 1px solid #f3f3f3;
}
.logo{
text-align: center;
border-bottom: 1px solid #f3f3f3;
height: 50px;
padding: 10px 0;
}
.shoppingCart{
font-size: 30px;
}
Components/Card.jsx
import { FaStar } from "react-icons/fa"
import { IoBag } from "react-icons/io5";
export default function card({ img, title, reviews, prevPrice, newPrice }) {
return (
<div div className='cardContainer'>
<div className="card">
<div className="pic">
<img src={img} alt={title} />
</div>
<div className="detail">
<p className='proTitle'>{title}</p>
<div className='starContainer'>
<FaStar className='star' /><FaStar className='star' /><FaStar className='star' /><FaStar className='star' />
<span className='review'>{reviews}</span>
</div>
<div className='price_lock'>
<div className="price">
<p className='oldPrice'>{prevPrice}</p>
<p className='newPrice'>{newPrice}</p>
</div>
<IoBag className='lock' />
</div>
</div>
</div>
</div >
)
}
Components/Radio.jsx
// 代码优化
const Radio = ({handleRadio, value, title, name, classStyle}) => {
return (
<div>
<input type="radio" name={name} className={classStyle} onChange={handleRadio} value={value}/>
<span className='cateTitle'>{title}</span>
</div>
)
}
export default Radio
浙公网安备 33010602011771号