React入门(Day2)Redux + Router

166 阅读6分钟

1. Redux

1.1 Redux介绍

Redux 是React最常用的集中状态管理工具,类似于Vue中的Pinia(Vuex),可以独立于框架运行;
作用:通过集中管理的方式管理应用的状态,串联 --> 并联

image.png

为什么要使用Redux?

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

1.2 Redux快速体验

1.2.1 实现计数器

需求:不和任何框架绑定,不使用任何构建工具,使用纯Redux实现计数器

image.png

pinia


<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 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
    }
  }
  // 2.使用reducer函数生成store实例
  const store = Redux.createStore(counterReducer)

  // 3.通过store实例的subscribe订阅数据变化
  //回调函数可以在每次state发生变化的时候自动执行
  store.subscribe(() => {
    console.log(store.getState())
    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>
1.2.2 Redux数据流框架

image.png

Redux的难点是理解它对于数据修改的规则, 图示动态展示了在整个数据的修改中,数据的流向;

为了职责清晰,Redux代码被分为三个核心的概念,我们学redux,其实就是学这三个核心概念之间的配合,三个概念分别是:

  • state: 一个对象 存放着我们管理的数据
  • action: 一个对象 用来描述你想怎么改数据
  • reducer: 一个函数 根据action的描述更新state

1.3 Redux-React环境搭建

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

image.png

  1. 使用 CRA 快速创建 React 项目
  2. 安装配套工具
  3. 启动项目
npx create-react-app react-redux 
npm i @reduxjs/toolkit  react-redux 
npm run start 

store目录结构设计

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

1.4 Redux与React - 实现counter

image.png

1.4.1 使用React Toolkit 创建 counterStore
//counterStore.js

import { createSlice } from '@reduxjs/toolkit'

const counterStore = createSlice({
  // 模块名称独一无二
  name: 'counter',
  // 初始化state
  initialState: {
    count: 0
  },
  // 修改数据的同步方法
  reducers: {
    increment (state) {
      state.count++
    },
    decrement(state){
      state.count--
    }
  }
})
// 结构出actionCreater函数
const { increment,decrement } = counterStore.actions

// 获取reducer函数
const counterReducer = counterStore.reducer

// 导出
export { increment, decrement }
export default counterReducer
//index.js
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './modules/counterStore'

export default configureStore({
  reducer: {
    // 注册子模块
    counter: counterReducer
  }
})
1.4.2 为React注入store

react-redux负责把Redux和React 链接 起来,内置Provider组件通过 store 参数把创建好的store实例注入到应用中,链接正式建立

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
// 导入store
import store from './store'
// 导入store提供组件Provider
import { Provider } from 'react-redux'

ReactDOM.createRoot(document.getElementById('root')).render(
  // 提供store数据
  <Provider store={store}>
    <App />
  </Provider>
)
1.4.3 React组件使用store中的数据

在React组件中使用store中的数据,需要用到一个钩子函数 - useSelector,它的作用是把store中的数据映射到组件中,使用样例如下:

image.png

1.4.4 React组件修改store中的数据

React组件中修改store中的数据需要借助另外一个hook函数 - useDispatch,它的作用是生成提交action对象的dispatch函数

import { useSelector,useDispatch } from 'react-redux';
import { decrement, increment } from './store/modules/counterStore';

function App() {
  const { count } = useSelector(state => state.counter)
  const dispatch = useDispatch()
  return (
    <div className="App">
      <button onClick={()=> dispatch(decrement())}>-</button>
      {count}
      <button onClick={()=> dispatch(increment())}>+</button>
    </div>
  );
}

export default App;
1.4.5 Redux与React - 提交action传参

需求:组件中有俩个按钮 add to 10add to 20 可以直接把count值修改到对应的数字,目标count值是在组件中传递过去的,需要在提交action的时候传递参数

image.png

2. Router路由

2.1 快速开始

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

一个路径 path 对应一个组件 component 当我们在浏览器中访问一个 path 的时候,path 对应的组件会在页面中进行渲染

image.png

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}/>
)
2.1.1 抽象路由模块

image.png

1: 创建page目录,下面创建Login/Article文件夹 2: 新增router文件夹,里面的index.js文件

import login from '../page/Login'
import Article from '../page/Article'
import {createBrowserRouter} from 'reate-router'

createBrowserRouter([
    {path: '/login',
     element: <Login/>
    },
    {path: '/Article',
     element: <Artucle/>
    }
])
export default router


//1.导入路由router实例
import router from './router'
//2.路由绑定
<RouterProvider router={router}><RouterPrivider>

2.2 路由导航

image.png 路由系统中的多个路由之间需要进行路由跳转,并且在跳转的同时有可能需要传递参数进行通信

2.1.1 声明式导航
  • 声明式导航是指通过在模板中通过 <Link/> 组件描述出要跳转到哪里去,比如后台管理系统的左侧菜单通常使用这种方式进行;
  • 语法说明:通过给组件的to属性指定要跳转到路由path,组件会被渲染为浏览器支持的a链接,如果需要传参直接通过字符串拼接的方式拼接参数即可
<Link to="/article">文章</Link>
2.1.2 编程式导航
  • 编程式导航是指通过 useNavigate 钩子得到导航方法,然后通过调用方法以命令式的形式进行路由跳转,比如想在登录请求完毕之后跳转就可以选择这种方式,更加灵活;
  • 语法说明:通过调用navigate方法传入地址path实现跳转

image.png

2.3 导航传参

image.png

去对应router位置拼接上占位符 image.png

2.4 嵌套路由

在一级路由中又内嵌了其他路由,这种关系就叫做嵌套路由,嵌套至一级路由内的路由又称作二级路由,例如:

image.png

实现步骤:

  • 使用 children属性配置路由嵌套关系
  • 使用 <Outlet/> 组件配置二级路由渲染位置

image.png

2.4.1 默认二级路由

当访问的是一级路由时,默认的二级路由组件可以得到渲染,只需要在二级路由的位置去掉path,设置index属性为true

image.png

2.4.2 路由配置404

场景:当浏览器输入url的路径在整个路由配置中都找不到对应的 path,为了用户体验,可以使用 404 兜底组件进行渲染

实现步骤:

  • 准备一个NotFound组件
  • 在路由表数组的末尾,以*号作为路由path配置路由
const NotFound = () => {
    //自定义模板
    return <div>this is NotFound</div>
}
export default NotFound


{
    path: '*',
    element: <NotFound/>
}
2.4.3 两种路两种式

各个主流框架的路由常用的路由模式有俩种,history模式和hash模式,

ReactRouter分别由 createBrowerRouter 和 createHashRouter 函数负责创建

image.png