1.Rdux集中状态管理工具
<button id="decrement">-</button>
<span id="count">0</span>
<button id="increment">+</button>
<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
<script>
// 1. 定义reducer函数
// 作用: 根据不同的action对象,返回不同的新的state
// state: 管理的数据初始状态
// action: 对象 type 标记当前想要做什么样的修改
function reducer (state = { count: 0 }, action) {
// 数据不可变:基于原始状态生成一个新的状态
if (action.type === 'INCREMENT') {
return { count: state.count + 1 }
}
if (action.type === 'DECREMENT') {
return { count: state.count - 1 }
}
return state
}
// 2. 使用reducer函数生成store实例
const store = Redux.createStore(reducer)
// 3. 通过store实例的subscribe订阅数据变化
// 回调函数可以在每次state发生变化的时候自动执行
store.subscribe(() => {
console.log('state变化了', store.getState())
document.getElementById('count').innerText = store.getState().count
})
// 4. 通过store实例的dispatch函数提交action更改状态
const inBtn = document.getElementById('increment')
inBtn.addEventListener('click', () => {
// 增
store.dispatch({
type: 'INCREMENT'
})
})
const dBtn = document.getElementById('decrement')
dBtn.addEventListener('click', () => {
// 减
store.dispatch({
type: 'DECREMENT'
})
})
// 5. 通过store实例的getState方法获取最新状态更新到视图中
</script>
2. Reduc与React-环境准备
图文并茂五分钟搞懂react中的reducer
counterStore.js文件
// 引入toolkit中的creatSlice方法
import { createSlice } from "@reduxjs/toolkit"
// reducer切片
// createSlice函数的作用:生成分片的reducer
// 内部调用的市createAction和createReducer
// creatSlice可以帮助我们用更少的代码去生成配套的reducer和action,而且有很好的维护性
const counterStore = createSlice({
name: 'counter',
// 初始化state
initialState: {
count: 0
},
// 修改状态的方法 同步方法 支持直接修改
reducers: {
// 指定state的各种操作,直接就可以在对象中添加方法
add(state) {
// 可以通过不同的方法修来指定对state的不同的操作
// state,action
// 可以直接修改state => state 是一个代理对象 之前的复制一份,返回一个新的对象
state.count++
},
reduce(state) {
state.count--
}
}
})
// action解构出来actionCreater函数
const { add, reduce } = counterStore.actions
// 获取reducer
const reducer = counterStore.reducer
// 以按需导出的方式导出actionCreater
export { add, reduce }
// 以默认导出的方式导出reducer
export default reducer
store/index.js文件
import { configureStore } from "@reduxjs/toolkit";
import counterStore from "./modules/counterStore";
const store = configureStore({
reducer: {
counter: counterStore
}
})
export default store
src/index.js
// 项目的入口,从这里开始运行
// React必要的两个核心包
import React from 'react';
import ReactDOM from 'react-dom/client';
// 导入项目的根组件
import App from './App';
import store from './store';
import { Provider } from 'react-redux';
// 把App根组件渲染到id为root的dom节点上
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
import { useSelector } from "react-redux";
function App() {
const { count } = useSelector(state => state.counter)
return (
<div className="App">
{count}
</div>
);
}
export default App;
import { useSelector, useDispatch } from "react-redux";
import { add, reduce } from "./store/modules/counterStore"
function App() {
const { count } = useSelector(state => state.counter)
const dispatch = useDispatch()
return (
<div className="App">
<button onClick={()=>dispatch(add())}>+</button>
{count}
<button onClick={()=>dispatch(reduce())}>-</button>
</div>
);
}
export default App;
2.1Redux与React-提交action传参
2.2Redux与React-异步状态操作
channelStore.js文件
// 导入必要的模块
import { createSlice } from '@reduxjs/toolkit'
import axios from 'axios'
// 使用 createSlice 创建 Redux 切片
const channelStore = createSlice({
name: 'channel',
initialState: {
channelList: []
},
reducers: {
// 定义 reducer 函数,用于更新 channelList
setChannelList(state, action) {
state.channelList = action.payload
}
}
})
// 从切片中导出 actionCreators 中的 setChannelList
const { setChannelList } = channelStore.actions
// 定义 API 地址
const url = 'http://geek.itheima.net/v1_0/channels'
// 封装异步 actionCreator 函数,用于获取频道列表并更新状态
const fetchChannelList = () => {
return async (dispatch) => {
// 发起异步请求获取频道列表数据
const res = await axios.get(url)
// 使用 dispatch 触发 setChannelList action,将获取的数据作为 payload 传递
dispatch(setChannelList(res.data.data.channels))
}
}
// 导出异步 actionCreator 函数 fetchChannelList
export { fetchChannelList }
// 从切片中导出 reducer 函数
const channelReducer = channelStore.reducer
export default channelReducer
index.js文件
import { configureStore } from "@reduxjs/toolkit";
import counterStore from "./modules/counterStore";
import channelReducer from "./modules/channelStore";
const store = configureStore({
reducer: {
counter: counterStore,
channel:channelReducer
}
})
export default store
App.js
// 导入 React 相关模块
import { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
// 导入异步 actionCreator 函数 fetchChannelList
import { fetchChannelList } from './store/channelStore'
// 定义 App 组件
function App() {
// 使用 useSelector 从 Redux store 中获取频道列表数据
const { channelList } = useSelector(state => state.channel)
// 使用 useDispatch 获取 dispatch 函数
const dispatch = useDispatch()
// 使用 useEffect 钩子,在组件加载时触发异步请求获取频道列表数据
useEffect(() => {
// 调用异步 actionCreator 函数 fetchChannelList,触发数据请求和状态更新
dispatch(fetchChannelList())
}, [dispatch])
// 渲染组件
return (
<div className="App">
{/* 列出频道列表数据 */}
<ul>
{channelList.map(task => <li key={task.id}>{task.name}</li>)}
</ul>
</div>
)
}
// 导出 App 组件
export default App
2.3Redux调试-devtools
3.美团案例-环境准备
3.1美团案例-分类和商品列表渲染
启动后端服务
启动前端服务
4. ReactRouter路由
// 项目的入口,从这里开始运行
// React必要的两个核心包
import React from 'react';
import ReactDOM from 'react-dom/client';
// 导入项目的根组件
import App from './App';
import store from './store';
import { Provider } from 'react-redux';
//导入react-router
import { createBrowserRouter,RouterProvider } from 'react-router-dom';
//创建router实例对象并且配置路由对应关系
const router=createBrowserRouter([
{
path:'/login',
element:<div>我是登录</div>
},
{
path:'/personal',
element:<div>我是个人</div>
}
])
// 把App根组件渲染到id为root的dom节点上
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<RouterProvider router={router}></RouterProvider>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
4.1抽象路由模块
新建页面文件
const Login = () => {
return <div>我是登录</div>
}
export default Login
新建router文件
import { createBrowserRouter } from 'react-router-dom'
import Login from '../pages/Login'
import Personal from "../pages/Personal"
const router = createBrowserRouter([
{
path: '/Lgoin',
element: <Login></Login>
},
{
path: '/Personal',
element: <Personal></Personal>
}
])
export default router
在index中导入router文件
// 项目的入口,从这里开始运行
// React必要的两个核心包
import React from 'react';
import ReactDOM from 'react-dom/client';
// 导入项目的根组件
// import store from './store';
// import { Provider } from 'react-redux';
//导入react-router
import { RouterProvider } from 'react-router-dom';
//导入路由router
import router from './router/router'
// 把App根组件渲染到id为root的dom节点上
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<RouterProvider router={router}></RouterProvider>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
4.2路由导航跳转
Login.js文件
import { Link } from "react-router-dom"
const Login = () => {
return (
<div>我是登录
<Link to="/Personal">我是个人</Link></div>
)
}
export default Login
Login.js文件
import { Link, useNavigate } from "react-router-dom"
const Login = () => {
const navigate = useNavigate()
return (
<div>我是登录
{/* 声明式写法 */}
<Link to="/Personal">我是个人</Link>
{/* 命令式写法 */}
<button onClick={() => navigate('/Personal')}>跳转个人</button>
</div>
)
}
export default Login
4.3路由跳转传参
searchParams传参
Login.js文件
import { Link, useNavigate } from "react-router-dom"
const Login = () => {
const navigate = useNavigate()
return (
<div>我是登录
{/* 声明式写法 */}
<Link to="/Personal">我是个人</Link>
{/* 命令式写法 */}
<button onClick={() => navigate('/Personal')}>跳转个人</button>
<button onClick={() => navigate('/Personal?id=3426&name=zhuman')}>跳转个人传参</button>
</div>
)
}
export default Login
Personal.js文件
import { useSearchParams } from "react-router-dom"
const Personal = () => {
const [params] = useSearchParams()
const id = params.get('id')
return <div>我是个人{id}</div>
// return <div>我是个人</div>
}
export default Personal
Params传参
Login.js文件
import { Link, useNavigate } from "react-router-dom"
const Login = () => {
const navigate = useNavigate()
return (
<div>我是登录
{/* 声明式写法 */}
<Link to="/Personal">我是个人</Link>
{/* 命令式写法 */}
<button onClick={() => navigate('/Personal')}>跳转个人</button>
<button onClick={() => navigate('/Personal?id=3426&name=zhuman')}>跳转个人传参</button>
<button onClick={() => navigate('/Personal/3426')}>跳转个人传参</button>
</div>
)
}
export default Login
src/router/router.js文件
import { createBrowserRouter } from 'react-router-dom'
import Login from '../pages/Login/Login'
import Personal from "../pages/Personal/Personal"
const router = createBrowserRouter([
{
path: '/Login',
element: <Login></Login>
},
{
path: '/Personal/:id',
element: <Personal></Personal>
}
])
export default router
Personal.js文件
import { useParams } from "react-router-dom"
const Personal = () => {
const params = useParams()
const id = params.id
return <div>我是个人 {id}</div>
}
export default Personal
4.4路由嵌套配置
src/router/router.js文件
import { createBrowserRouter } from 'react-router-dom'
import Login from '../pages/Login/Login'
import Personal from "../pages/Personal/Personal"
import About from "../pages/About/About"
import Layout from "../pages/Layout/Layout"
import Board from "../pages/Board/Board"
const router = createBrowserRouter([
{
path: '/',
element: <Layout></Layout>,
children: [
{
path: 'Board',
element: <Board></Board>
},
{
path: 'About',
element: <About></About>
}
]
},
{
path: '/Login',
element: <Login></Login>
},
{
path: '/Personal/:id',
element: <Personal></Personal>
}
])
export default router
src/pages/Layout/Layout.js文件
import { Outlet,Link } from "react-router-dom"
const Layout = () => {
return (
<div>我是一级路由
<Link to="/Board">面板</Link>
<Link to="/About">关于</Link>
{/* // 配置二级路由的出口 */}
<Outlet />
</div>
)
}
export default Layout
src/pages/About/About.js文件 -----pages/Board/Board.js文件同理
const About = () =>{
return <div>我是2级路由About</div>
}
export default About
4.5默认二级路由
src/router/router.js文件
import { createBrowserRouter } from 'react-router-dom'
import Login from '../pages/Login/Login'
import Personal from "../pages/Personal/Personal"
import About from "../pages/About/About"
import Layout from "../pages/Layout/Layout"
import Board from "../pages/Board/Board"
const router = createBrowserRouter([
{
path: '/',
element: <Layout></Layout>,
children: [
{
// 设置为默认二级路由,一级路由访问的时候,它也能得到渲染
index:true,
element: <Board></Board>
},
{
path: 'About',
element: <About></About>
}
]
},
{
path: '/Login',
element: <Login></Login>
},
{
path: '/Personal/:id',
element: <Personal></Personal>
}
])
export default router
src/pages/Layout/Layout.js文件
import { Outlet,Link } from "react-router-dom"
const Layout = () => {
return (
<div>我是一级路由
<Link to="/">面板</Link>
<Link to="/About">关于</Link>
{/* // 配置二级路由的出口 */}
<Outlet />
</div>
)
}
export default Layout
4.6 404路由配置
src/pages/Layout/Layout.js文件
import { createBrowserRouter } from 'react-router-dom'
import Login from '../pages/Login/Login'
import Personal from "../pages/Personal/Personal"
import About from "../pages/About/About"
import Layout from "../pages/Layout/Layout"
import Board from "../pages/Board/Board"
import NotFound from "../pages/NotFound/NotFound"
const router = createBrowserRouter([
{
path: '/',
element: <Layout></Layout>,
children: [
{
// 设置为默认二级路由,一级路由访问的时候,它也能得到渲染
index:true,
element: <Board></Board>
},
{
path: 'About',
element: <About></About>
}
]
},
{
path: '/Login',
element: <Login></Login>
},
{
path: '/Personal/:id',
element: <Personal></Personal>
},
// 404页面配置
{
path:'*',
element:<NotFound></NotFound>
}
])
export default router
src/pages/NotFound/NotFound.js文件
const NotFound = () => {
return (
<div>页面找不到了</div>
)
}
export default NotFound
4.7两种路由模式
hash模式
import { createBrowserRouter , createHashRouter} from 'react-router-dom'
import Login from '../pages/Login/Login'
import Personal from "../pages/Personal/Personal"
import About from "../pages/About/About"
import Layout from "../pages/Layout/Layout"
import Board from "../pages/Board/Board"
import NotFound from "../pages/NotFound/NotFound"
//Hash模式
const router = createHashRouter([
{
path: '/',
element: <Layout></Layout>,
children: [
{
// 设置为默认二级路由,一级路由访问的时候,它也能得到渲染
index:true,
element: <Board></Board>
},
{
path: 'About',
element: <About></About>
}
]
},
{
path: '/Login',
element: <Login></Login>
},
{
path: '/Personal/:id',
element: <Personal></Personal>
},
// 404页面配置
{
path:'*',
element:<NotFound></NotFound>
}
])
export default router
5.记账本项目案例
5.1别名路径配置
const path = require('path')
module.exports = {
webpack:{
alias:{
'@':path.resolve(__dirname,'src')
}
}
}
5.2数据Mock实现
5.3整体路由设计
5.4mobile主题定制
全局生效:
5.5Redux管理账目列表
// 账单列表相关store
import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";
const billStore = createSlice({
name:'bill',
// 数据状态state
initialState: {
billList: []
},
reducers: {
setBillList(state, action) {
state.billList = action.payload
}
}
})
// 解构actionCraeter函数
const { setBillList } = billStore.actions
// 编写异步
const getBillList = () => {
return async (dispatch)=>{
//编写异步请求
const res = await axios.get('http://localhost:8888/ka')
// 触发同步reducer
dispatch(setBillList(res.data))
}
}
export {getBillList}
// 导出reducer
const reducer = billStore.reducer
export default reducer
import { Button } from "antd-mobile"
import { Outlet } from "react-router-dom"
import { useDispatch } from "react-redux"
import { getBillList } from "@/store/billStore/billStore"
import { useEffect } from "react"
const Layout = () => {
const dispatch = useDispatch()
useEffect(()=>{
dispatch(getBillList())
},[dispatch])
return (
<div>
<Outlet></Outlet>
Layout
<Button color="primary">测试全局</Button>
</div>
)
}
export default Layout
5.6 TabBar功能实现
UIAnt Design of React - Ant Design
安装scss命令:npm i -D sass
Layout.js文件
import { TabBar } from "antd-mobile"
import { useEffect } from "react"
import { Outlet, useNavigate } from "react-router-dom"
import { useDispatch } from 'react-redux'
import { getBillList } from "@/store/billStore/billStore"
import './Layout.scss'
import {
BillOutline,
CalculatorOutline,
AddCircleOutline
} from 'antd-mobile-icons'
const tabs = [
{
key: '/month',
title: '月度账单',
icon: <BillOutline />,
},
{
key: '/new',
title: '记账',
icon: <AddCircleOutline />,
},
{
key: '/year',
title: '年度账单',
icon: <CalculatorOutline />,
},
]
const Layout = () => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(getBillList())
}, [dispatch])
// 切换菜单跳转路由
const navigate = useNavigate()
const swithRoute = (path) => {
console.log(path)
navigate(path)
}
return (
<div className="layout">
<div className="container">
<Outlet />
</div>
<div className="footer">
<TabBar onChange={swithRoute}>
{tabs.map(item => (
<TabBar.Item key={item.key} icon={item.icon} title={item.title} />
))}
</TabBar>
</div>
</div>
)
}
export default Layout
Layout.scss文件
.layout {
.container {
position: fixed;
top: 0;
bottom: 50px;
width: 100%;
}
.footer {
position: fixed;
bottom: 0;
width: 100%;
}
}