【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} />
      );
相关推荐
Front思17 分钟前
vue3中el-input在form表单按下回车刷新页面
前端·javascript·vue.js
Minyy1120 分钟前
小程序-使用npm包
前端·小程序·npm·node.js
牧小七22 分钟前
ElementPlus---Timeline 时间线组件使用示例
前端·javascript·vue.js·element-plus·element-plus组件
fishmemory7sec24 分钟前
Electron 使用 Nodemon 配置自动重启
前端·javascript·electron
Mrs_Lupin25 分钟前
npm与包
前端·npm·node.js
dy171728 分钟前
el-table按照查询条件再对应行数据进行高亮,并可以定位到某行
javascript·vue.js·elementui
潜心专研的小张同学28 分钟前
pnpm依赖安装失败解决|pnpm项目从一个文件夹复制到另一个文件夹运行失败问题解决-以vbenAdmin项目为例
前端·javascript·vscode·npm·vue·pnpm
weixin_531804241 小时前
泛型中的通配符<?>、<? extends T>、<? super T>的使用场景。ArrayList与LinkedList的区别及适用场景。
java·前端·javascript
@AfeiyuO1 小时前
Angular基础学习(入门 --> 入坑)
前端·angular.js
惜.己1 小时前
CSS样式基础样式选择器(案例+代码实现+效果图)
开发语言·前端·css·vscode·html·html5