React快速上手到项目实战总篇

React核心价值与前置知识

时刻保持对知识的渴望 家人们 开学!!!

核心价值

  • 组件化(易开发易维护)

  • 数据驱动视图 :定义好数据和ui的显示规则 即UI=f(state)

    • 只关注业务数据修改,不在操作DOM 增加开发效率

使用vite创建Recat项目

image-20241127191701363

开发规范

使用 prettier & eslint 规范开发

  • eslint 检查语法语义
  • prettier 检查代码风格
#eslint :
npm install eslint@typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev

#prettier:
npm install prettier eslint-config-prettier eslint-plugin-prettier --save-dev

vite和 webpack的区别

webpack是一个非常流行的前端打包工具 比较经典 Create-React-App 是使用webpack作为打包工具的

vite 既是构建工具 又是打包工具

vite的特点:

  1. Vite打包项目 在启动和代码更新时更快
  2. vite使用了 es Module 语法(仅开发环境)

React JSX语法

内容 :

  1. JSX语法
  2. 组件和props
  3. 实战: 列表页

JSX特点:

  1. JSX是js的扩展 写在js代码里面 组件的ui结构
  2. 语法和html很相似
  3. 不只是React独有

标签

  • 首字母大小写的区别 , 大写字母是自定义组件
  • 标签必须闭合 如<input>在jsx是非法的
  • 每段JSX中只有一个根节点

image-20241127200934798

属性

和html基本相似

  • class要改为 className
  • style要使用js对象 不能是string 而且key需要使用驼峰写法

如下

image-20241127201256661

在JSX中插入js变量

  • 使用{}可以插入JS变量 函数 表达式
  • 可以插入文本 属性
  • 可以用于注释

代码案例

条件判断

​ 常见的if else 可以通过{}的方式实现,但是在JSX中代码一多就显得不够实用了 以下三种方法可以解决:

  • 使用&&
  • 使用三元表达式
  • 使用函数来判断

比如这样:反之如果flag等于false 就不会出现hello

image-20241205141921850

效果:

image-20241205142005694

三元运算符:flag为判断条件 来控制标签的显示

image-20241205142326966

效果:

image-20241205142357552

函数:

function isShowHello(){
  if (flag)return <p>show hello</p>
  return <p>defaultHello</p>
}

image-20241205142731794

效果 :

image-20241205142625910

循环

  • 使用map来循环
  • 每一个循环项(item)都要有key
  • key需要具有唯一性

实现

const list = [
  {username:'zhangsan', name:"张三"},
  {username:'shuangyue', name:"双月"},
  {username:'lisi', name:"李四"},
]

     {/*循环*/}
        <div>
          {list.map(user=>{
            const {username,name} = user
           return <li key={username}>{name}</li>
          })}
        </div>

效果:

image-20241205143625839

PS : 不建议使用 index 如 :

image-20241205143728414

因为我们的key 需要具有唯一性

小结实战 列表页

开发一个列表页

image-20241205144041914

调整一下显示的jsx

image-20241205144105516

保证这个代码结构简洁 ,然后就可以开始开发了

import React from 'react';
import './App1.css';

function App() {
    const questionList = [
        {id: 'q1', title: '问卷1', isPublished: true},
        {id: 'q2', title: '问卷2', isPublished: true},
        {id: 'q3', title: '问卷3', isPublished: true},
        {id: 'q4', title: '问卷4', isPublished: false}
    ]

    function edit(id) {
        console.log('edit', id);
    }

    return (<div>
        <h1>列表详情页</h1>
        <div>
            {questionList.map(question => {
                const {id, title, isPublished} = question;
                return <div key={id} className="list-item">
                    &nbsp;
                    <strong>{title}</strong>
                    &nbsp;
                    {isPublished ? <span style={
  
  {color: "green"}}>已发布</span> : <span>未发布</span>}
                    &nbsp;
                    <button onClick={() => edit(id)}>编辑问卷</button>
                </div>
            })}
        </div>
    </div>)

}

export default App;

css

.list-item {
   
   
    border: 1px solid #ccc;
    padding: 10px;
    margin-bottom: 16px;
    display: flex;
    justify-content: center;
}

效果

组件

react 一切皆是组件

  • 组件拥有一个ui片段
  • 拥有独立的逻辑和显示
  • 可大可小 可以嵌套

组件拆分的价值和意义

  • 组件嵌套来组织的 ui 结构 和 html 一样没有学习成本
  • 良好的拆分组件利于代码维护和多人协同开发
  • 封装公共组件或者直接使用第三方组件复用代码

好的组件化 逻辑是清晰的 更能提升开发效率并且更加的美观易读

image-20241205150607929

我们可以将组件理解成一个一个的函数

使用我们之前的列表页代码 拆分成组件 list1

image-20241205151215018

然后用improt的方式 引入到listdemo中

image-20241205151236675

这样我们的总框架就没有那么多的代码冗余 需要修改对应的代码 只需要寻找对应的组件文件即可

属性 props

  • 组件可以嵌套 有层级关系
  • 父组件可以向子组件传递数据
  • props是只读对象

props 其实就是实现差异化组件信息传递的一种手段

实践

将之前循环内显示数据的div拆出来抽象成组件:QuestCard.tsx 。 CSS还是和之前的内容一样

使用 ts主要是方便传入泛型

QuestCard.tsx

import React, {FC} from "react";
import './QuestCard.css'

type proptype = {
    id: string,
    title: string,
    isPublished: boolean
}
export const QuestCard: FC<proptype> = (props) => {
    const {id, title, isPublished} = props;

    function edit(id) {
        console.log('edit', id);
    }

    return (
        <div key={id} className="list-item">
            &nbsp;
            <strong>{title}</strong>
            &nbsp;
            {isPublished ? <span style={
  
  {color: "green"}}>已发布</span> : <span>未发布</span>}
            &nbsp;
            <button onClick={() => edit(id)}>编辑问卷</button>
        </div>)
}

image-20241205152744672

改造list1.jsx 这样就将显示问卷卡片抽取出来为一个独立的组件了

import React from "react";
import './list1.css';
import {QuestCard} from "./QuestCard";

export const List1 = () => {
    const questionList = [
        {id: 'q1', title: '问卷1', isPublished: true},
        {id: 'q2', title: '问卷2', isPublished: true},
        {id: 'q3', title: '问卷3', isPublished: true},
        {id: 'q4', title: '问卷4', isPublished: false}
    ]


    return (
        <div>
            <h1>列表详情页</h1>
            <div>
                {questionList.map(question => {
                    const {id, title, isPublished} = question;
                    return <QuestCard key={id} id={id} title={title} isPublished={isPublished}/>
                })}
            </div>
        </div>)

}

小结:

  • 如何定义和使用组件
  • props-父组件给子组件传递数据
  • 重构列表页 抽象出QuestionCard

效果

image-20241210135546835

children

场景: 当我们把内容签到在子组件标签中时,父组件会自动的在名为 children的prop中接受内容

image-20241210140715462

子组件传递父组件

顾名思义 其实就是子组件给父组件传递信息

function Son({onGetSonMsg}) {
//     son 中的数据
    const sonMsg = 'this is son msg';
    return <div>this is son
        <button onClick={() => onGetSonMsg(sonMsg)}>sendMsg</button>
    </div>
}

function AppDemo() {
    const [msg, setMsg] = useState('')
    const getMsg = (msg) => {
        console.log(msg)
        // msg = '我是信息'  这么改是无效的
        setMsg(msg)
    }
    return <div>
        this is APP Son send msg =>{msg}
        <Son onGetSonMsg={getMsg}/>
    </div>
}

兄弟组件传递

使用状态提升实现兄弟组件通信

  • 其实就是有共同父组件的两个子组件传递信息
  • a 传递给父组件 然后由父组件 传递给 b

image-20241210142620094

代码

import {useState} from "react";

function A({onGetAName}) {
    const name = "a name"
    return <div>this is A
        <button onClick={() => onGetAName(name)}>send</button>
    </div>
}

function B({pushAName}) {
    return <div>this is B
        {pushAName}
    </div>
}

function AppDemo() {
    const [aName, setAName] = useState('');
    const getAName = (name) => {
        console.log(name)
        setAName(name)
    }
    return <div>
        this is app
        <A onGetAName={getAName}/>
        <B pushAName={aName}/>
    </div>
}


export default AppDemo;
function A({onGetAName}) {
    const name = "a name"
    return <div>this is A
        <button onClick={() => onGetAName(name)}>send</button>
    </div>
}

function B({pushAName}) {
    return <div>this is B
        {pushAName}
    </div>
}

function AppDemo() {
    const [aName, setAName] = useState('');
    const getAName = (name) => {
        console.log(name)
        setAName(name)
    }
    return <div>
        this is app
        <A onGetAName={getAName}/>
        <B pushAName={aName}/>
    </div>
}

效果

image-20241210143414793

React 拓展

React.memo

允许组件在Props没有改变的情况下 跳过渲染

react组件默认的渲染机制 : 父组件重新渲染的时候子组件也会重新渲染

import React, {
   
   useState} from 'react';

function Son() {
   
   
    console.log('子组件被重新渲染了')
    return <div>this is son</div>
}

const ReactMemoDemo = () => {
   
   
    const [, forceUpdate] = useState()
    console.log('父组件重新渲染了')
    return (
        <>
            <Son/>
            <button onClick={
   
   () => forceUpdate(Math.random())}>update</button>
        </>
    )
};

export default ReactMemoDemo;

image-20250105160106041

这个时候使用 memo包裹住组件 就可以避免 但是 注意 只考虑props变化才能使用\

import React, {
   
   memo, useState} from 'react';

// function Son() {
   
   
//     console.log('子组件被重新渲染了')
//     return <div>this is son</div>
// }

const MemoSon = memo(function Son() {
   
   
    console.log("我是子组件 我被渲染了")
    return <div>this is son</div>
})
const ReactMemoDemo = () => {
   
   
    const [, forceUpdate] = useState()
    console.log('父组件重新渲染了')
    return (
        <>
            <MemoSon/>
            <button onClick={
   
   () => forceUpdate(Math.random())}>update</button>
        </>
    )
};

export default ReactMemoDemo;

image-20250105160353905

React.memo 比较机制

React会对每一个prop进行 object.is比较 返回true 表示没有变化

PS: 对于引用类型 React只关心引用是否变化

HOOKS

useState

这是React 中的一个hook 函数 它允许我们向组件添加一个状态变脸,从而控制组件的渲染结果

image-20241210141807465

 const [msg, setMsg] = useState('')
  1. useState是一个函数 返回值是一个数组
  2. 数组中的第一个参数是状态变量,第二个参数是set函数用于修改状态
  3. useState的参数将作为状态变量的初始值

修改规则

在React 中 状态被认为是只读的 我们应该替换而不是修改 直接修改状态不会得到视图的更新

    const [msg, setMsg] = useState('')
    const getMsg = (msg) => {
        console.log(msg)
        // msg = '我是信息'  这么改是无效的
        setMsg(msg)
    }
    
    //如果是对象作为参数
      const [msg, setMsg] = useState({id:'122ds'})
    const getMsg = (msg) => {
        console.log(msg)
        // msg = '我是信息'  这么改是无效的
        setMsg({
            ...msg,
        	id:'123'})
    }

useContext 组件通信

  1. 使用createContext 方法创建一个上下文对象 ctx=
  2. 在顶层组件 app 中 通过 ctx.Provider提供数据
  3. 在底层组件 通过 useContext钩子函数获取消费数据

案例 :

image-20241210143845871

我们需要将app的消息传递到b

const MsgContext = createContext()

function A() {
    return <div>this is A
        <B/>
    </div>
}

function B() {
    const msg = useContext(MsgContext)
    return <div>this is B from APP:{msg}
    </div>
}

function AppDemo() {
    const msg = "this is app msg"
    return (<div>
        <MsgContext.Provider value={msg}>
            this is app
            <A/>
        </MsgContext.Provider>
    </div>)
}

useEffect

这是React中的一个 hook 函数 ,用于在React 中创建不是由事件引起而是由渲染本身引起的操作,比如发送 AJAX请求 更改DOM等

image-20241210144624854

基础使用

需求: 在组件渲染完毕后,从服务器获得列表数据展示

语法:

useEffect(()=>{},[])
  1. 参数1是一个函数,可以把它叫做副作用函数,函数内部可以放置要执行的操作
  2. 参数2是一个数组 ,数组里放置依赖项,不同依赖项会影响第一个参数的执行,当该参数是一个空数组的时候,副作用函数只会在组件渲染完毕后执行一次
import {useEffect, useState} from "react";

const URL = 'http://geek.itheima.net/v1_0/channels'

function AppDemo() {
    const [list, setList] = useState([]);
    useEffect(() => {
        async function getList() {
            const res = await fetch(URL)
            const jsonRes = await res.json()
            console.log(jsonRes)
            setList(jsonRes.data.channels)
        }

        getList()
        console.log("list", list)
    }, []);
    return (<div>
        this is app
        <ul>
            {list.map(item => <li key={item.id}>{item.name}</li>)}
        </ul>
    </div>)
}


export default AppDemo;

效果

image-20241210150832714

依赖项参数

image-20241210150906961

function AppDemo() {
    /*1. 没有依赖项*/
    const [count, setCount] = useState(0);
    // useEffect(() => {
    //     console.log("副作用函数执行了")
    // });
    /*2 传入空数组依赖*/
    // useEffect(() => {
    //     console.log("副作用函数执行了")
    // }, []);
    useEffect(() => {
        console.log("副作用函数执行了")
    }, [count]);
    return <div>this is app
        <button onClick={() => setCount(count + 1)}>+{count}</button>
    </div>
}

清除副作用

useEffect中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用操作,我们想在组件卸载时把这个定时器清理掉,这个过程就是清理副作用

import {useEffect, useState} from "react";

function Son() {
    useEffect(() => {
        const timer = setInterval(() => {
            console.log("定时器执行中...")
        }, 1000)
        return () => {
            //     清楚副作用
            clearInterval(timer)
        }
    }, []);
    return <div>this is son</div>
}

function AppDemo() {
    const [show, setShow] = useState(true)
    return <div>this is app
        {show && <Son/>}
        <button onClick={() => setShow(false)}>卸载组件</button>
    </div>
}

export default AppDemo;

useReducer

  • 定义redcuer函数 (根据不同的action 返回不同的新状态)
  • 在组件中调用 useReducer 传入reducer函数和初始状态
  • 事件触发的时候,通过 dispatch函数 通过reducer要返回什么状态并且渲染UI
import React, {
   
   useReducer} from 'react';

// 根据不同的case 返回不同的状态
function reducer(state, action) {
   
   
    switch (action.type) {
   
   
        case 'INC':
            return state + 1
        case 'DEC':
            return state - 1
        case 'SET':
            return state = action.payload
        default:
            return state
    }
}

const ReducerDemo = () => {
   
   
    // 使用 use reducer
    const [state, dispatch] = useReducer(reducer, 0)
    return (
        <div>
            <button onClick={
   
   () => dispatch({
   
   type: 'INC'})}>+</button>
            {
   
   state}
            <button onClick={
   
   () => dispatch({
   
   type: 'DEC'})}>-</button>
            <button onClick={
   
   () => dispatch({
   
   type: 'SET', payload: 100})}>Set</button>
        </div>
    );
};

export default ReducerDemo;

这个钩子相当于 一个可以有多个修改state方法的 usestate

useMemo

作用:它在每次重新渲染的时候能够缓存计算的结果

小案例

  • 我们设置一个计算结果的方法 这个方法直接用 大括号的方式渲染
  • 设置两个按钮 每次usestate发生变化 都会渲染页面 会导致两个按钮无论点击哪一个都会导致计算结果方法的内容出现变化
import React, {
   
   useState} from 'react';

function factorialOf(n) {
   
   
    console.log('斐波那契函数执行了')
    return n <= 0 ? 1 : n * factorialOf(n - 1)
}

const MemoDemo = () => {
   
   
    const [count, setCount] = useState(0)
    // 计算斐波那契之和
    const sumByCount = factorialOf(count)

    const [num, setNum] = useState(0)

    return (
        <>
            {
   
   sumByCount}
            <button onClick={
   
   () => setCount(count + 1)}>+count:{
   
   count}</button>
            <button onClick={
   
   () => setNum(num + 1)}>+num:{
   
   num}</button>
        </>
    )
};

export default MemoDemo;

useMemo 就是用来解决这种问题的

import React, {
   
   useMemo, useState} from 'react';

function factorialOf(n) {
   
   
    console.log('斐波那契函数执行了')
    return n <= 0 ? 1 : n * factorialOf(n - 1)
}

const MemoDemo = () => {
   
   
    const [count, setCount] = useState(0)
    // 计算斐波那契之和
    // const sumByCount = factorialOf(count)
    const sumByCount = useMemo(() => {
   
   
        return factorialOf(count)
    }, [count])

    const [num, setNum] = useState(0)

    return (
        <>
            {
   
   sumByCount}
            <button onClick={
   
   () => setCount(count + 1)}>+count:{
   
   count}</button>
            <button onClick={
   
   () => setNum(num + 1)}>+num:{
   
   num}</button>
        </>
    )
};

export default MemoDemo;

就不会出现 点击num按钮也会触发求和方法情况了

useCallback

作用 在组件多次重新渲染的时候 缓存函数

自定义hook

暂时没有什么很好的例子 写一个比较简单的 之后再拓展

import {useState} from "react";

function useToggle() {
// 可复用代码
    const [value, setValue] = useState(true);
    const toggle = () => {
        setValue(!value)
    }
    return {value, toggle}
}

function AppDemo() {
    const {value, toggle} = useToggle()
    return <div>this is app
        {value && <div>this is show Toggle</div>}
        <button onClick={toggle}>Toggle</button>
    </div>
}

export default AppDemo;

效果

点击

Redux

完整代码案例仓库 :https://gitee.com/cold-abyss_admin/react-redux-meituan

Redux是 React 最常用的集中状态管理工具,类似与VUE的pinia(vuex) 可以独立于框架运行

image-20241211182447283

使用思路:

  1. 定义一个reducer函数 根据当前想要做的修改返回一个新的状态
  2. 使用createStore方法传入reducer函数 生成一个store实例对象
    1. subscribe方法 订阅数据的变化(数据一旦变化,可以得到通知)
    2. dispatch方法提交action对象 告诉reducer你想怎么改数据
    3. getstate方法 获取最新的状态数据更新到视图中

image-20241211185420416

配置Redux

在React中使用redux,官方要求安装俩个其他插件-和react-redux

官方推荐我们使用 RTK(ReduxToolkit) 这是一套工具集合 可以简化书写方式

  • 简化store配置
  • 内置immer可变式状态修改
  • 内置thunk更好的异步创建

调试工具安装

谷歌浏览器搜索 redux-devtool安装 工具

依赖安装

#redux工具包
npm i @reduxjs/toolkit react-redux
#调试工具包
npm install --save-dev redux-devtools-extension

image-20241213153131189

store目录机构设计

  • 通常集中状态管理的部分都会单独创建一个store目录
  • 应用通常会有多个子store模块,所以创建一个modules进行内部业务的区分
  • store中的入口文件index.js 的作用是组合所有modules的子模块 并且导出store

image-20241211190849933

快速上手

使用react+redux 开发一个计数器 熟悉一下技术

image-20241211190956868

  1. 使用 Reacttoolkit 创建 counterStore

    import {
         
         createSlice} from "@reduxjs/toolkit";
    
    const counterStore= createSlice({
         
         
        name: "counter",
        // 初始化 state
        initialState: {
         
         
            count: 0
        },
        // 修改状态的方法
        reducers:{
         
         
            increment(state){
         
         
                state.count++
            },
            decrement(state){
         
         
                state.count--
            }
        }
    })
    
    // 解构函数
    const {
         
         increment,decrement}= counterStore.actions
    // 获取reducer
    const reducer = counterStore.reducer;
    export {
         
         increment,decrement}
    export default reducer
    
  2. index.js集合counter

    import {
         
         configureStore} from "@reduxjs/toolkit";
    import counterStore from "./modules/counterStore";
    const store = configureStore({
         
         
        reducer:{
         
         
            couner: counterStore,
        }
    })
    
    export default store
    
  3. 为React 注入store, react-redux负责把Redux和React链接 起来,内置 Provider组件 通过 store 参数把创建好的store实例注入到应用中 找到项目中的index.js

    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <React.StrictMode>
          <Provider store={store}>
              <App />
          </Provider>
      </React.StrictMode>
    );
    
    
  4. 使用useSelector 获取到数据

    import {useSelector} from "react-redux";
    
    function App() {
      const {count} = useSelector(state => state.counter);
      return (
        <div className="App">
          {count}
        </div>
      );
    }
    
  5. 使用 钩子函数 useDispatch

    import {
         
         useDispatch, useSelector} from "react-redux";
    import {
         
         inscrement,descrement} from "./store/modules/counterStore"
    function App() {
         
         
      const {
         
         count} = useSelector(state => state.counter);
     const dispatch = useDispatch()
      return (
        <div className="App">
            <button onClick={
         
         ()=>dispatch(inscrement())}>+</button>
            {
         
         count}
            <button onClick={
         
         ()=>dispatch(descrement())}>-</button>
        </div>
      );
    }
    
    export default App;
    
    
  6. 查看效果

    image-20241211194212353

提交acntion传参

reducers的同步修改方法中添加action对象参数,在调用actionCreater参数的时候传递参数,参数会被传递到action对象的payload属性上

我们继续的改造一下counterStore

action这个对象参数有个固定的属性叫payload用来接收传参

image-20241212183228071

然后 app.js 添加两个按钮 用来传递参数

image-20241212183251405

效果

image-20241212183353807

Reudx action异步操作

区分同步和异步action

image-20241212192017617

如果action的内容是 object对象那就是同步action,如果是函数 那就是异步action

为什么我们需要异步action操作来使用请求 ?

例子:

我们有两种方式可以实现 隔五分钟 上蛋炒饭

一种是客人自己思考五分钟

一种是客人点好 叫服务员五分钟之后上

这个服务员就是 redux 我们刚希望相关aciton的操作都在redux里完成这个时候同步action就不能满足我们的需求了 所以需要使用异步action

​ 异步操作的代码变化不大,我们创建store的写法保持不变 ,但是在函数中用异步操作的时候需要一个能异步执行函数return出一个新的函数而我们的异步操作卸载新的函数中.

异步action中一般都会调用一个同步action

案例: 从后端获取到列表展示到页面

新建一个文件叫做 ChannelStore.js 然后编写对应的创建代码

import {
   
   createSlice} from "@reduxjs/toolkit";
import axios from "axios";
const  channelStore = createSlice({
   
   
    name: "channel",
    initialState: {
   
   
        channelList:[]
    },
    reducers:{
   
   
        setChannel(state, action){
   
   
            state.channelList=action.payload
        }
    }
})
const {
   
   setChannel}= channelStore.actions
// 异步请求
const fetchChannelList = ()=>{
   
   
    return async (dispatch)=>{
   
   
        const  res = await  axios.get('http://geek.itheima.net/v1_0/channels')
        dispatch(setChannel(res.data.data.channels))
    }
}

const reducer = channelStore.reducer;
export {
   
   fetchChannelList}
export default reducer

然后去store入口加入channelStore

import {
   
   configureStore} from "@reduxjs/toolkit";
import counterStore from "./modules/counterStore";
import channelStore from "./modules/channelStore";
const store = configureStore({
   
   
    reducer:{
   
   
        counter: counterStore,
        channel: channelStore,
    }
})

export default store

之后就可以在app.js加入代码

import {useDispatch, useSelector} from "react-redux";
import {useEffect} from "react";
import {fetchChannelList} from "./store/modules/channelStore";
function App() {
  const {channelList} = useSelector(state => state.channel);
 const dispatch = useDispatch()
    useEffect(() => {
        dispatch(fetchChannelList())
    }, [dispatch]);
  return (
      <div className="App">
          <ul>
              {channelList.map(item =><li key={item.id}>{item.name}</li>)}
          </ul>
      </div>

  );
}

export default App;

代码效果

image-20241212191240289

redux hooks

useSelector

它的作用是吧store中的数据映射到组件中

 const {
   
   count} = useSelector(state => state.counter);

这里的count其实对应的就是

image-20241211192355704

useDispatch

它的作用是生成提交 action对象的dispatch函数

import {
   
   useDispatch, useSelector} from "react-redux";
import {
   
   inscrement,descrement} from "./store/modules/counterStore"
function App() {
   
   
  const {
   
   count} = useSelector(state => state.counter);
 const dispatch = useDispatch()
  return (
    <div className="App">
        <button onClick={
   
   ()=>dispatch(inscrement())}>+</button>
        {
   
   count}
        <button onClick={
   
   ()=>dispatch(descrement())}>-</button>
    </div>
  );
}

export default App;

美团点餐界面小案例

下载模板地址:

git clone http://git.itcast.cn/heimaqianduan/redux-meituan.git

效果与功能列表展示

image-20241213143205796

基本的思路就是使用 RTK 来做状态管理,组件负责数据渲染和操作action

image-20241213144807698

我们在store文件夹下开始配置和编写store的使用逻辑

分类渲染

先编写对应的reducer 和异步请求逻辑

takeaway.js

用于异步请求列表数据

import {
   
   createStore} from './store';
import axios from "axios";
const foodsState = createStore({
   
   
    name:'foods',
    initialState: {
   
   
        foodsList:[]
    },
    reducers:{
   
   
        setFoodsList(state, action){
   
   
            state.foodsList=action.payload
        }
    }
});
const {
   
   setFoodsList} = foodsState.actions;
//异步获取部分
const fetchFoodsList = () => {
   
   
    return async dispatch => {
   
   
    //     异步逻辑
       const res =  await axios.get(' http://localhost:3004/takeaway\n')
    //     调用dispatch
        dispatch(setFoodsList(res.data))
    }
}
const reducer = foodsState.reducer
export {
   
   fetchFoodsList}
export default reducer

将子store管理起来 在store文件夹下编写一个index.js作为访问store的入口

import {
   
   configureStore} from "@reduxjs/toolkit";
import foodsReducer from './modules/takeaway'
const  store= configureStore({
   
   
    reducer:{
   
   
        foods:foodsReducer
    }
})

export default store

然后将redux和react连接起来 将store 注入进去 选择根目录的index.js

import React from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import App from './App'
import store from "./store";


const root = createRoot(document.getElementById('root'))
root.render(
    <Provider store={store}>
      <App />
    </Provider>
)

编写渲染页面

在app.js里 遵循步骤开始操作store

  1. 使用useDispatch函数取得对象
  2. 使用 useEffect 调用异步函数获取服务器数据
  3. 使用useSelector 拿到数据并且循环展示
import NavBar from './components/NavBar'
import Menu from './components/Menu'
import Cart from './components/Cart'
import FoodsCategory from './components/FoodsCategory'
import './App.scss'
import {useSelector} from "react-redux";

const App = () => {
 // 访问store拿到数据
 const {foodsList} = useSelector(state => state.foods)
  return (
    <div className="home">
      {/* 导航 */}
      <NavBar />

      {/* 内容 */}
      <div className="content-wrap">
        <div className="content">
          <Menu />

          <div className="list-content">
            <div className="goods-list">
              {/* 外卖商品列表 */}
              {foodsList.map(item => {
                return (
                  <FoodsCategory
                    key={item.tag}
                    // 列表标题
                    name={item.name}
                    // 列表商品
                    foods={item.foods}
                  />
                )
              })}
            </div>
          </div>
        </div>
      </div>

      {/* 购物车 */}
      <Cart />
    </div>
  )
}

export default App

效果

image-20241213145927894

侧边栏渲染.交互

我们需要在获取列表解构的时候 拿到属于左侧列表的数据

image-20241213150225046

然后循环的展示在menu组件中 只需要把异步请求的数据放到menu组件中就可以展示侧边栏了

import classNames from 'classnames'
import './index.scss'
import {
   
   useDispatch, useSelector} from "react-redux";
const Menu = () => {
   
   
  //    获取dispatch
  const  dispatch = useDispatch()
  // 访问store拿到数据
  const {
   
   foodsList} = useSelector(state => state.foods)
  const menus = foodsList.map(item => ({
   
    tag: item.tag, name: item.name }))
  return (
    <nav className="list-menu">
      {
   
   /* 添加active类名会变成激活状态 */}
      {
   
   menus.map((item, index) => {
   
   
        return (
          <div
            key={
   
   item.tag}
            className={
   
   classNames(
              'list-menu-item',
              'active'
            )}
          >
            {
   
   item.name}
          </div>
        )
      })}
    </nav>
  )
}

export default Menu

效果

image-20241213151100641

接下来编写交互操作 使用RTK来管理activeindex

  • 新增activeIndex并且设置好对应的同步操作action方法以及导出
import {
   
   createSlice} from '@reduxjs/toolkit';
import axios from "axios";
const foodsState = createSlice({
   
   
    name:'foods',
    initialState: {
   
   
        // 商品列表
        foodsList:[],
     // 菜单激活值
        activeIndex:0,
    },
    reducers:{
   
   
        setFoodsList(state, action){
   
   
            state.foodsList=action.payload
        },
        changeActiveIndex(state, action){
   
   
            state.activeIndex=action.payload
        }
    }
});
const {
   
   setFoodsList,changeActiveIndex} = foodsState.actions;
//异步获取部分
const fetchFoodsList = () => {
   
   
    return async dispatch => {
   
   
    //     异步逻辑
       const res =  await axios.get(' http://localhost:3004/takeaway\n')
    //     调用dispatch
        dispatch(setFoodsList(res.data))
        console.log(res.data)
    }
}
const reducer = foodsState.reducer
export {
   
   fetchFoodsList,changeActiveIndex}
export default reducer

然后开始编写menu组件的点击效果

image-20241213151806359

代码修改 menu/index.js

import classNames from 'classnames'
import './index.scss'
import {
   
   useDispatch, useSelector} from "react-redux";
import {
   
   changeActiveIndex} from "../../store/modules/takeaway";
const Menu = () => {
   
   
  //    获取dispatch
  const  dispatch = useDispatch()
  // 访问store拿到数据
  const {
   
   foodsList,activeIndex} = useSelector(state => state.foods)
  const menus = foodsList.map(item => ({
   
    tag: item.tag, name: item.name }))
  return (
    <nav className="list-menu">
      {
   
   /* 添加active类名会变成激活状态 */}
      {
   
   menus.map((item, index) => {
   
   
        return (
          <div
              onClick={
   
   ()=>dispatch(changeActiveIndex(index))}
            key={
   
   item.tag}
            className={
   
   classNames(
              'list-menu-item',
                activeIndex===index&& 'active'
            )}
          >
            {
   
   item.name}
          </div>
        )
      })}
    </nav>
  )
}

export default Menu

效果

当点击的时候index就会切换到对应的index上 并且在点击当前index的时候选项高亮

image-20241213153536781

image-20241213153228472

商品列表的切换显示

点击侧边栏的时候 菜单栏需要显示对应侧边栏index的菜单

修改 app.js菜单栏标签的显示规则就行

const App = () => {
   
   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冷环渊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值