前言
前端时间,笔者在掘金上写了一篇文章了,适合react新手入门,希望真的有帮助到一部分人哦,这段时间学习了神三元大佬的项目,我把redux加入到项目中,所有的数据请求和状态都放在redux里,顺便继续完善了一下项目,期间了碰到了很多问题,redux确实比较难学,同时开始学,我男朋友就比我的学的通透哈~,我就姗姗来迟,但咱不能输了气势!
项目介绍 ( 貌美如花+赚钱养家)
上篇文章就说了这个项目是仿猫眼电影小程序,我写了两个页面,首页和赛事页面,首页做一个简易的购物车,进入电影详情页,还有一个留言板,赛事页面主打搜索功能,可按内容,地点,类型搜索。总而言之就是首页负责貌美如花,赛事负责赚钱养家~
1. 项目展示
项目在线预览 Vite App (gchhcg.github.io)
首页
赛事页面
2. 工具库
- antd : antd 是基于 Ant Design 设计体系的 React UI 组件库
- axiso:axios是一个用于发送Ajax请求的http库,本质上时对Ajax的封装,而且支持Promise操作,让我们无需再 使用传统的callback方式进行异步编程
- style-components :styled-components 是一个常用的 css in js 类库。和所有同类型的类库一样,通过 js 赋能解决了原生 css 所不具备的能力,比如变量、循环、函数等
- font-awesome —— 图标字体库
- CSSTransition:过渡动画效果
- redux 全家桶 —— 不用我说了哈
- prop-types : 严格控制父子组件传值的类型合理性
- react-lazyload :懒加载 优化用户第一次进站体验
- classnames —— 动态添加类名,实现可操控的样式方法
- memo: React自带 页面渲染性能优化 让你的网页选择性渲染需要更渲染资源的组件
3. 项目 src文件夹
3.1 配置文件 vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
base: './',
resolve: {
alias: {
"@":path.resolve(__dirname,'src')
}
}
})
详细介绍
1. 首页
- 背景色渐变,颜色随着轮播图和 tab 切换
2. 配置全局主题样式文件
- golbal-style.js
// 全局风格定义 是最重要的
// 老板 + 设计师 风格是样式的灵魂
export default {
"theme-color": "rgb(255,0,0)",
"theme-color-s":'#5db3d7',
"theme-color-m":'#d6c193',
"theme-color-l":''
}
3. 轮播图
export default function Banners() {
let swiper = null;
useEffect(() => {
// swiper 不能多次实例化
if (swiper) {
return
}
new Swiper('.btn-banners', {
loop: true,
autoplay: {
delay: 3000
},
pagination: {
el: '.swiper-pagination'
}
})
}, [])
<BannersWrapper>
<div className="btn-banners swiper-container">
<div className="swiper-wrapper">
<div className="swiper-slide">
<p>
<img width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/059513e0f8e54f5288c961b1017a68ee~tplv-k3u1fbpfcp-zoom-1.image" />
</p>
</div>
<div className="swiper-slide">
<p>
<img width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f9af6f327ed9412e93a25171148235de~tplv-k3u1fbpfcp-zoom-1.image" />
</p>
</div>
<div className="swiper-slide">
<p>
<img width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/93b3b74dec1a47babd8c49da14151834~tplv-k3u1fbpfcp-zoom-1.image" />
</p>
</div>
</div>
<div className="swiper-pagination"></div>
</div>
</BannersWrapper>
1. 首页界面
2. 电影详情页
- 头部导航栏固定,position:fiexd
- 引入 antd 组件
<PageHeader/>
<PageHeader
className="site-page-header"
onBack={() => setShow(false)}
subTitle="详情"
/>
- 点击“ 想看” “已看” 互不影响 阻止默认事件
const [visited1, setVisited1] = useState(false)
const [visited2, setVisited2] = useState(false)
const clickdown1 = (e) =>{
e.preventDefault();
e.stopPropagation();
setVisited1(!visited1);
}
const clickdown2 = (e) =>{
e.preventDefault();
e.stopPropagation();
setVisited2(!visited2)
}
useEffect(()=> {
setShow(true)
window.scrollTo(0,0)
},[])
3. 演员详情页
4. 滑动轮播图 + 评论
5. 购票清单
赛事界面
在上篇文章,我已经介绍过了,没有做任何界面上的变化,想看的小伙伴,附上链接哦!
Redux 来了 !!!!
Redux 是什么
- Redux 是 JavaScript 状态容器,提供可预测化的状态管理。 (如果你需要一个 WordPress 框架,请查看 Redux Framework。)可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。不仅于此,它还提供 超爽的开发体验
Redux 原理
- redux原理是将整个应用状态存储到一个地方上称为store,里面保存着一个状态树store tree,组件可以派发(dispatch)行为(action)给store,而不是直接通知其他组件,组件内部通过订阅store中的状态state来刷新自己的视图。state是只读的,唯一改变state的方法就是触发action。
创建仓库
分仓库
- 数据管理和组件,在有了 redux 后,变成了平级关系 /store /page
- 模块化数据管理,每个模块 reducer+action 下放到页面级路由模块中,方便管理
- 每个模块都提供 index.js , 方便统一管理 store, 所有的 reducer,action,constans 都一起 export,作为清单文件
主仓库
-
用于统一管理各个分仓数据,并给根组件提供Provider功能的store,和state树根
- index.js
import {combineReducers} from 'redux'
//引入为Count组件服务的reducer
import Events from '@/pages/Events/store/reducer'
//引入为Person组件服务的reducer
import Home from '@/pages/Home/store/reducer'
//汇总所有的reducer变为一个总的reducer
export default combineReducers({
Events,
Home
})
- store.js
import { createStore, compose, applyMiddleware } from 'redux';
// 组件 中间件redux-thunk 数据
import thunk from 'redux-thunk' // 异步数据管理
import reducer from './index'
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer,
// 组合函数
// devtool
composeEnhancers(
// 异步
applyMiddleware(thunk)
)
)
export default store;
- main.js
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<HashRouter>
<App />
</HashRouter>
</Provider>
)
分仓库
home目录下
home 下store 有一个简易版的购物车
- 购物车redux设计思路
- redux 大点项目 数据管理 财务管理 数 据统计不能出错 算账技巧
- redux 核心使命是 数据管好
- 计算正确 初始状态 + reducer 重新运算 和页面状态正确对应 MVVM
- 所有的状态 留下来 不用被引用式赋值影响 便于 react-dev-tools logger
redux 的状态迁移可以被追溯的
- { ...state, stataA:1 }
- Object.assign({} ,state ,{list:[...list]})
- ImmutableJS 优化
-
reducer 设计完成 store 就基本完成了 财务数据管理
- 提供商品的默认值 default 商品 添加到购物车 check:true
- 选中 不选 case CHECK_GOODS goodId 不选 =》 选中 goodsNum = 1
- CHANGE_GOODS_NUM
goodId status 不好分析
- 0 check false
- CHANGE_ALL_GOODS
-
actionCreactors.js (负责统一管理数据状态改变的函数执行,给reducer分配相应的action:状态类型,数据)
import * as actionTypes from './constans'
import {getactors,getmoviesRequest} from '@/api/request'
export const changeactorslist = (data) => ({
type:actionTypes.CHANGE_ACTORS,
data
})
export const getactorslist = () => {
return (dispatch) => {
getactors()
.then(item => {
// console.log(item.data, '////')
const action = changeactorslist(item.data);
dispatch(action)
})
}
}
export const changemovienum = (data) => ({
type: actionTypes.CHNAGE_MOVIES_NUM,
data: data
})
export const checkmovie = (id) => ({
type: actionTypes.CHECK_MOVIE,
data: id
})
export const changemovie = (data) => ({
type:actionTypes.CHANGE_MOVIE_LIST,
data
})
export const getmovies = () => {
return (dispatch) => {
// console.log('12')
getmoviesRequest()
.then(item => {
// console.log(item.data, '////')
const action = changemovie(item.data);
dispatch(action)
})
}
}
- constants.js 常量
export const CHANGE_ACTORS = 'CHANGE_ACTORS'
export const CHANGE_MOVIE_LIST = 'CHANGE_MOVIE_LIST'
export const CHECK_MOVIE = 'CHECK_MOVIE'
export const CHNAGE_MOVIES_NUM = 'CHNAGE_MOVIES_NUM'
- reducer.js (负责根据action值,做相应操作,以实现数据流管理)
import * as actionTypes from './constans'
const defaultState = {
movieslist:[],
actorslist:[]
}
export default (state=defaultState,action) => {
switch (action.type) {
case actionTypes.CHANGE_MOVIE_LIST:
return {
...state,
movieslist:action.data
}
case actionTypes.CHECK_MOVIE:
// 在reducer 重新计算前的状态 ? 旧状态
let checkList = state.movieslist;
checkList.map(item => {
if (item.id == action.data) {
item.check = !item.check
console.log(item.check)
// 0 1 -
item.count == '0' ? item.count = '1' : ''
}
})
// 新状态
return Object.assign({}, state, {
movieslist: [...checkList]
})
break;
case actionTypes.CHNAGE_MOVIES_NUM:
let changeList = state.movieslist;
// + - 指定商品 action type CHNAGE_GOODS_NUM
// data: {id:id, status:'add|minus' }
changeList.map((item) => {
if (item.id == action.data.id ) {
action.data.status == 'add'? item.count++: item.count--;
item.count == '0' ? item.check = false : ''
// -1 UI 去做 item.goodsNum> 0 && <button>-</button>
}
})
return Object.assign({}, state, {movieslist: [...changeList]})
break;
case actionTypes.CHANGE_ACTORS:
return {
...state,
actorslist:action.data
}
default:
return state
}
}
- events 目录下
events 下的store 难度比较大是,把搜索,定位城市和激活状态的tab作为对象参数全部传给action,在action里面过滤,得到符合条件的赛事列表。
- 工具库函数 fetchTodos
export { debounce };
export const fetchTodos = params => {
// console.log(params)
let { query, tab, cityName, result} = params;
// console.log(query, tab);
console.log(result,'..')
// result 存放获取赛事信息的数据
if(tab) {
switch(tab) {
case "全部":
result = result.filter(todo => todo.pos.includes(cityName))
// 数组filter方法,过滤,筛选得到符合条件的信息
break;
case "电竞赛事":
result = result.filter(todo => todo.type == '电竞赛事' && todo.pos.includes(cityName))
break;
case "体育赛事":
result = result.filter(todo => todo.type == '体育赛事' && todo.pos.includes(cityName))
default:
break;
}
}
if(query) {
result = result.filter(todo => todo.text.includes(query)||todo.pos.includes(query))
}
// Promise 类 resolve 静态方法
// Promise.all 返回一个fullfiled 的 promise 实例
return result
}
- 工具库函数 防抖
const debounce = (func, delay) => {
let timer;
return function (...args) {
if(timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
func.apply(this, args);
clearTimeout(timer);
}, delay);
};
};
export { debounce };
- actionCreators.js
import {
getCities,
getEvent,
} from '@/api/request'
import {fetchTodos} from '@/utils/utils'
import * as actionTypes from './constants'
export const changecitiesList = (data) => ({
type: actionTypes.CHANGECITIES_LIST,
data: data
})
export const changeEventsList = (data) => ({
type:actionTypes.CHANGE_EVENTS_LIST,
data: data
})
// api请求 一定放在action中
export const getcitieslist = () => {
return (dispatch) => {
console.log('|||||||||||||||')
getCities()
.then(item => {
// console.log(item.data, '////')
const action = changecitiesList(item.data);
dispatch(action)
})
}
}
export const getEventslist = (data) => {
return (dispatch) => {
getEvent()
.then(item => {
const result = [...item.data]
//console.log(result,'猪')
let a = fetchTodos({...data, result})
// const action = changeEventsList(item.data);
dispatch(changeEventsList(a))
})
}
}
- constants.js
export const CHANGECITIES_LIST ='CHANGECITIES_LIST'
export const CHANGE_EVENTS_LIST = 'CHANGE_EVENTS_LIST'
- reducer.js
import * as actionTypes from './constants'
const defaultState = {
citieslist: [],
eventslist: [],
}
export default (state = defaultState, action) => {
switch(action.type) {
case actionTypes.CHANGECITIES_LIST:
return {
...state,
citieslist: action.data
}
case actionTypes.CHANGE_EVENTS_LIST:
// console.log('----------')
return {
...state,
eventslist: action.data,
}
default:
return state
}
}
来看看我的项目初始状态
让你看看这个项目的store tree,是不是很清晰呢!
项目优化
-
memo
- import { memo } from 'react'
- export default memo(xxxx)
- 就可以实现减少渲染重复未变数据
-
Lazy 图片占位 懒加载
<LazyLoad
placeholder={
<img width="100%" height="100%"
src={waitImg}
/>}>
<img src = {url}/>
</LazyLoad>
-
路由懒加载
- import { lazy, Suspense } from "react"
- const XXX = lazy(() => import('@/pages/XXX'))
- 类似于下方 👇
const Cities = lazy(() => import('@/pages/Cities'))
const Events = lazy(() => import('@/pages/Events'))
const Eventdetail = lazy(() => import('@/pages/Events/Eventdetail'))
const Movie = lazy(() => import('@/pages/Movie'))
const Yanchu = lazy(() => import('@/pages/Yanchu'))
const Mine = lazy(() => import('@/pages/Mine'))
到这里讲解就结束了,小伙伴想看更多的👇
项目在线预览 Vite App (gchhcg.github.io)
源码地址 cat-movie: 仿猫眼电影搜索组件react实战第一个项目 仿猫眼电影搜索组件react实战第一个项目 (gitee.com)
- 有问题和可优化点,欢迎大佬评论区讨论指正
- 求 点赞 收藏 评论指正! 不要挥一挥衣袖不带走一片云彩哦,留下你们的赞哦 ~爱你们哦 ~ !!!
- 持续更新,持续学习,希望对你所有帮助~