【React】入门Day03 —— Redux 与 React Router 核心概念及应用实例详解

 

1. Redux 介绍

// 创建一个简单的Redux store
const { createStore } = Redux;

// reducer函数
function counterReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

// 创建store实例
const store = createStore(counterReducer);

// 订阅数据变化
store.subscribe(() => {
  console.log(store.getState());
});

// 触发数据变化
store.dispatch({ type: 'INCREMENT' });
  • 概念

    • 是 React 常用的集中状态管理工具,可独立于框架运行,类似于 Vue 中的 Pinia(Vuex)。
    • 通过集中管理方式管理应用状态。
  • 使用原因

    • 独立于组件,无视层级关系,简化通信。
    • 单向数据流清晰,易于定位 bug。
    • 调试工具配套良好,方便调试。

2. Redux 快速体验

<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>
  // 定义reducer函数 
  function counterReducer (state = { count: 0 }, action) {
    switch (action.type) {
      case 'INCREMENT':
        return { count: state.count + 1 }
      case 'DECREMENT':
        return { count: state.count - 1 }
      default:
        return state
    }
  }
  const store = Redux.createStore(counterReducer);
  store.subscribe(() => {
    document.getElementById('count').innerText = store.getState().count;
  });
  const inBtn = document.getElementById('increment');
  inBtn.addEventListener('click', () => {
    store.dispatch({ type: 'INCREMENT' });
  });
  const dBtn = document.getElementById('decrement');
  dBtn.addEventListener('click', () => {
    store.dispatch({ type: 'DECREMENT' });
  });
</script>
  • 计数器实现

    • 定义 reducer 函数,根据 action 返回新状态。
    • 使用 createStore 传入 reducer 生成 store 实例。
    • 用 store 的 subscribe 方法订阅数据变化。
    • 通过 store 的 dispatch 方法提交 action 触发变化。
    • 利用 store 的 getState 方法获取最新状态更新视图。
  • 数据流架构

    // 定义state
    const initialState = { count: 0 };
    
    // 定义action
    const incrementAction = { type: 'INCREMENT' };
    
    // 定义reducer
    function counterReducer(state = initialState, action) {
      switch (action.type) {
        case 'INCREMENT':
          return { count: state.count + 1 };
        default:
          return state;
      }
    }
    • 包含 state(存放数据对象)、action(描述数据修改对象)、reducer(根据 action 更新 state 的函数)三个核心概念。

3. Redux 与 React

# 创建React项目
npx create-react-app react-redux 

# 安装配套工具
npm i @reduxjs/toolkit  react-redux 

# 启动项目
npm run start 
  • 环境准备

    • 安装 Redux Toolkit(RTK)和 react-redux。
    • 使用 CRA 创建 React 项目,安装配套工具并启动。
    • 设计 store 目录结构,包括单独的 store 目录和内部的 modules 目录,入口文件组合子模块并导出 store。
  • 实现 counter

    // counterStore.js
    import { createSlice } from '@reduxjs/toolkit';
    
    const counterStore = createSlice({
      name: 'counter',
      initialState: { count: 1 },
      reducers: {
        increment(state) {
          state.count++;
        },
        decrement(state) {
          state.count--;
        }
      }
    });
    
    const { increment, decrement } = counterStore.actions;
    const counterReducer = counterStore.reducer;
    export { increment, decrement };
    export default counterReducer;
    
    // store.js
    import { configureStore } from '@reduxjs/toolkit';
    import counterReducer from './modules/counterStore';
    export default configureStore({
      reducer: {
        counter: counterReducer
      }
    });
    
    // App.jsx
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import App from './App';
    import store from './store';
    import { Provider } from 'react-redux';
    ReactDOM.createRoot(document.getElementById('root')).render(
      <Provider store={store}>
        <App />
      </Provider>
    );
    
    // 在组件中使用store数据
    import { useSelector } from 'react-redux';
    const count = useSelector(state => state.counter.count);
    
    // 在组件中修改store数据
    import { useDispatch } from 'react-redux';
    const dispatch = useDispatch();
    dispatch(increment());
    • 使用 React Toolkit 创建 counterStore,定义模块名称、初始数据和修改数据的同步方法。
    • 为 React 注入 store,使用 Provider 组件传递 store 实例。
    • React 组件中用 useSelector 获取 store 数据,用 useDispatch 修改 store 数据。
  • 提交 action 传参

    // reducer函数
    function counterReducer(state = { count: 0 }, action) {
      switch (action.type) {
        case 'INCREMENT_WITH_PARAM':
          return { count: action.payload.targetCount };
        default:
          return state;
      }
    }
    
    // 触发action并传递参数
    const targetCount = 10;
    store.dispatch({ type: 'INCREMENT_WITH_PARAM', payload: { targetCount } });
    • 在 reducers 同步修改方法中添加 action 对象参数,调用 actionCreater 时传递参数到 action 对象的 payload 属性。
  • 异步 action 处理

    // channelStore.js
    import { createSlice } from '@reduxjs/toolkit';
    import axios from 'axios';
    
    const channelStore = createSlice({
      name: 'channel',
      initialState: { channelList: [] },
      reducers: {
        setChannelList(state, action) {
          state.channelList = action.payload;
        }
      }
    });
    
    const { setChannelList } = channelStore.actions;
    const url = 'http://geek.itheima.net/v1_0/channels';
    const fetchChannelList = () => {
      return async (dispatch) => {
        const res = await axios.get(url);
        dispatch(setChannelList(res.data.data.channels));
      }
    };
    export { fetchChannelList };
    const channelReducer = channelStore.reducer;
    export default channelReducer;
    
    // App.jsx
    import { useEffect } from 'react';
    import { useSelector, useDispatch } from 'react-redux';
    import { fetchChannelList } from './store/channelStore';
    
    function App() {
      const { channelList } = useSelector(state => state.channel);
      useEffect(() => {
        dispatch(fetchChannelList());
      }, [dispatch]);
      return (
        <div className="App">
          <ul>
            {channelList.map(task => <li key={task.id}>{task.name}</li>)}
          </ul>
        </div>
      );
    }
    export default App;
    • 创建 store 配置同步方法,单独封装函数,在新函数中封装异步请求获取数据并调用同步 actionCreater 生成 action 对象提交。

4. Redux 调试

  • 官方提供调试工具,支持实时 state 信息展示和 action 提交信息查看。

5. 路由快速上手

// 假设已有一个简单的React组件结构
import React from 'react';
import ReactDOM from 'react-dom/client';

// 定义两个简单组件
const LoginComponent = () => <div>登录页面内容</div>;
const ArticleComponent = () => <div>文章页面内容</div>;

// 这里模拟路由配置(实际使用React Router的配置方式会更复杂)
const routes = [
  { path: '/login', component: LoginComponent },
  { path: '/article', component: ArticleComponent }
];

// 根据当前路径渲染相应组件(这里是简化的逻辑,实际需要根据React Router的机制来实现)
const App = () => {
  const currentPath = window.location.pathname;
  const ComponentToRender = routes.find(route => route.path === currentPath)?.component || null;
  return <>{ComponentToRender}</>;
};

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  • 前端路由概念

    • 一个路径对应一个组件,访问路径时对应组件在页面渲染。
  • 开发环境创建

    # 使用CRA创建项目
    npm create-react-app react-router-pro
    
    # 安装最新的ReactRouter包
    npm i react-router-dom
    
    # 启动项目
    npm run start
    • 使用 CRA 创建项目,安装 React Router 包,启动项目。
  • 快速开始

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import { createBrowserRouter, RouterProvider } from 'react-router-dom';
    
    const router = createBrowserRouter([
      {
        path: '/login',
        element: <div>登录</div>
      },
      {
        path: '/article',
        element: <div>文章</div>
      },
    ]);
    
    ReactDOM.createRoot(document.getElementById('root')).render(
      <RouterProvider router={router} />
    );
    • 使用createBrowserRouter创建路由,配置路径和对应组件,通过RouterProvider渲染路由。

6. 抽象路由模块

// 假设这是一个简单的路由模块文件
import { createBrowserRouter, RouterProvider } from 'react-router-dom';

// 定义各个路由组件
const HomeComponent = () => <div>首页</div>;
const AboutComponent = () => <div>关于我们</div>;
const ContactComponent = () => <div>联系我们</div>;

// 创建路由配置
const router = createBrowserRouter([
  {
    path: '/',
    element: HomeComponent
  },
  {
    path: '/about',
    element: AboutComponent
  },
  {
    path: '/contact',
    element: ContactComponent
  }
]);

// 导出路由配置供其他文件使用
export default router;
  • (文档中未详细说明相关内容,可能需要进一步查看实际模块相关代码)

7. 路由导航

  • 概念

    • 路由系统中多个路由间需进行跳转及参数传递通信。
  • 声明式导航

    import React from 'react';
    import { Link } from 'react-router-dom';
    
    const NavMenu = () => (
      <nav>
        <Link to="/home">首页</Link>
        <Link to="/about">关于</Link>
        <Link to="/contact">联系</Link>
      </nav>
    );
    • 通过<Link/>组件,指定to属性为路由路径实现跳转,传参可字符串拼接。
  • 编程式导航

    import React from 'react';
    import { useNavigate } from 'react-router-dom';
    
    const LoginButton = () => {
      const navigate = useNavigate();
    
      const handleLogin = () => {
        // 假设这里是登录逻辑,登录成功后进行跳转
        navigate('/dashboard');
      };
    
      return <button onClick={handleLogin}>登录</button>;
    };
    • 使用useNavigate钩子获取导航方法,调用navigate方法传入路径实现跳转,更灵活。

8. 导航传参

import React from 'react';
import { useNavigate } from 'react-router-dom';

const ProductDetailButton = () => {
  const navigate = useNavigate();

  const productId = 123; // 假设这是一个产品ID

  const handleNavigate = () => {
    navigate(`/product/${productId}`);
  };

  return <button onClick={handleNavigate}>查看产品详情</button>;
};
  • (文档中未详细说明相关内容,可能需要进一步查看实际传参相关代码)

9. 嵌套路由配置

  • 概念

    • 一级路由中内嵌其他路由为嵌套路由,内嵌的为二级路由。
      import React from 'react';
      import { createBrowserRouter, RouterProvider } from 'react-router-dom';
      
      // 定义子路由组件
      const SubPage1 = () => <div>子页面1</div>;
      const SubPage2 = () => <div>子页面2</div>;
      
      // 一级路由组件
      const MainPage = () => <div>主页面</div>;
      
      // 创建嵌套路由配置
      const router = createBrowserRouter([
        {
          path: '/main',
          element: MainPage,
          children: [
            {
              path: 'sub1',
              element: SubPage1
            },
            {
              path: 'sub2',
              element: SubPage2
            }
          ]
        }
      ]);
      
      ReactDOM.createRoot(document.getElementById('root')).render(
        <RouterProvider router={router} />
      );
  • 配置步骤

    • 使用children属性配置嵌套关系,用<Outlet/>组件配置二级路由渲染位置。
      import React from 'react';
      import { createBrowserRouter, RouterProvider, Outlet } from 'react-router-dom';
      
      // 定义子路由组件
      const SubPage1 = () => <div>子页面1</div>;
      const SubPage2 = () => <div>子页面2</div>;
      
      // 一级路由组件
      const MainPage = () => <div>主页面,这里可以放置一些通用的内容,子页面会在Outlet处渲染</div>;
      
      // 创建嵌套路由配置
      const router = createBrowserRouter([
        {
          path: '/main',
          element: MainPage,
          children: [
            {
              path: 'sub1',
              element: SubPage1
            },
            {
              path: 'sub2',
              element: SubPage2
            }
          ]
        }
      ]);
      
      ReactDOM.createRoot(document.getElementById('root')).render(
        <RouterProvider router={router}>
          <MainPage>
            <Outlet />
          </MainPage>
        </RouterProvider>
      );
  • 默认二级路由

    • 访问一级路由时,二级路由去掉路径,设置index属性为true可默认渲染。
      import React from 'react';
      import { createBrowserRouter, RouterProvider, Outlet } from 'react-router-dom';
      
      // 定义子路由组件
      const SubPage = () => <div>默认子页面</div>;
      
      // 一级路由组件
      const MainPage = () => <div>主页面,这里可以放置一些通用的内容,子页面会在Outlet处渲染</div>;
      
      // 创建嵌套路由配置
      const router = createBrowserRouter([
        {
          path: '/main',
          element: MainPage,
          children: [
            {
              path: '',
              index: true,
              element: SubPage
            }
          ]
        }
      ]);
      
      ReactDOM.createRoot(document.getElementById('root')).render(
        <RouterProvider router={router}>
          <MainPage>
            <Outlet />
          </MainPage>
        </RouterProvider>
      );
  • 404 路由配置

    • 准备NotFound组件,在路由表末尾用*作为路径配置路由。
      import React from 'react';
      import { createBrowserRouter, RouterProvider } from 'react-router-dom';
      import NotFoundComponent from './NotFoundComponent';
      
      // 其他路由配置
      const router = createBrowserRouter([
        //...其他路由
        {
          path: '*',
          element: NotFoundComponent
        }
      ]);
      
      ReactDOM.createRoot(document.getElementById('root')).render(
        <RouterProvider router={router} />
      );
  • 路由模式

    • 常用history模式和hash模式。
      • history模式:url表现为url/login,基于history对象和pushState事件,需后端支持。
        import React from 'react';
        import { createBrowserRouter, RouterProvider } from 'react-router-dom';
        
        // 假设后端已正确配置处理history模式的路由请求
        
        const router = createBrowserRouter([
          {
            path: '/login',
            element: <div>登录页面内容</div>
          },
          {
            path: '/article',
            element: <div>文章页面内容</div>
          }
        ]);
        
        ReactDOM.createRoot(document.getElementById('root')).render(
          <RouterProvider router={router} />
        );
      • hash模式:url表现为url/#/login,监听hashChange事件,无需后端支持。
        import React from 'react';
        import { createHashRouter, RouterProvider } from 'react-router-dom';
        
        const router = createHashRouter([
          {
            path: '/login',
            element: <div>登录页面内容</div>
          },
          {
            path: '/article',
            element: <div>文章页面内容</div>
          }
        ]);
        
        ReactDOM.createRoot(document.getElementById('root')).render(
          <RouterProvider router={router} />
        );

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值