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

1. Redux 介绍

javascript 复制代码
// 创建一个简单的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 快速体验

javascript 复制代码
<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 方法获取最新状态更新视图。

数据流架构

javascript 复制代码
// 定义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

javascript 复制代码
# 创建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

javascript 复制代码
// 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 传参

javascript 复制代码
// 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 处理

javascript 复制代码
// 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. 路由快速上手

javascript 复制代码
// 假设已有一个简单的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 />);

前端路由概念

  • 一个路径对应一个组件,访问路径时对应组件在页面渲染。

开发环境创建

javascript 复制代码
# 使用CRA创建项目
npm create-react-app react-router-pro

# 安装最新的ReactRouter包
npm i react-router-dom

# 启动项目
npm run start
  • 使用 CRA 创建项目,安装 React Router 包,启动项目。

快速开始

javascript 复制代码
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. 抽象路由模块

javascript 复制代码
// 假设这是一个简单的路由模块文件
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. 路由导航

概念

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

声明式导航

javascript 复制代码
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属性为路由路径实现跳转,传参可字符串拼接。

编程式导航

javascript 复制代码
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. 导航传参

javascript 复制代码
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. 嵌套路由配置

概念

  • 一级路由中内嵌其他路由为嵌套路由,内嵌的为二级路由。

    javascript 复制代码
    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/>组件配置二级路由渲染位置。

    javascript 复制代码
    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可默认渲染。

    javascript 复制代码
    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组件,在路由表末尾用*作为路径配置路由。

    javascript 复制代码
    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事件,需后端支持。

      javascript 复制代码
      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事件,无需后端支持。

      javascript 复制代码
      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} />
      );
相关推荐
咖啡の猫2 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲4 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5815 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路5 小时前
GeoTools 读取影像元数据
前端
ssshooter5 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友5 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry6 小时前
Jetpack Compose 中的状态
前端
dae bal7 小时前
关于RSA和AES加密
前端·vue.js
柳杉7 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog7 小时前
低端设备加载webp ANR
前端·算法