一文学会react+redux(模块化/同步/异步操作)

本文基于npx create-react-app创建

太久没看react,闲来无事重新捡起做一点笔记,希望对部分vue的同行想学习redux起到一些帮助

1.准备工作安装

1.安装项目插件

yarn add @craco/craco craco-less

2.修改package.json中的scripts,将react-scripts替换为craco

"scripts": {
  "start": "craco start",
  "build": "craco build",
  "test": "craco test",
  // ...
}

3.craco.config.js

根目录下创建或修改craco.config.js来配置Less 以及@别名配置:
const CracoLessPlugin = require('craco-less');
const path = require('path');

module.exports = {
  plugins: [
    // 配置CracoLessPlugin插件,用于处理Less样式文件
    {
      plugin: CracoLessPlugin, // 引入的插件
      options: {
        lessLoaderOptions: {
          // 配置Less加载器
          lessOptions: {
            modifyVars: { '@primary-color': '#1DA57A' }, // 自定义Less变量,例如改变主题颜色
            javascriptEnabled: true, // 启用Less中的JavaScript
          },
        },
      },
    },
  ],
  webpack: {
    configure: (webpackConfig) => {
      webpackConfig.resolve.alias = {
        ...(webpackConfig.resolve.alias || {}),
        '@': path.resolve(__dirname, 'src'),
      };
      return webpackConfig;
    },
  },
};

2.redux基础操作-同步

2.1.准备工作

2.1.1.src下创建view
2.1.2.view下创建两个文件夹(Home,About)
2.1.3.Home和About下创建Home.jsx/About.jsx
2.1.4.src下创建store文件夹
2.1.5.store下创建reducer1.js 用于单个模块化的reducer
2.1.6.store下创建reducer.js 用于合并reducer
2.1.7.store下创建store.js store的暴露文件
-src
	-view
		-Home
			Home.jsx
		-About
			About.jsx
	-store
		-reducer1.js
		-reducer.js
		-store.js
		

2.2 编写reducer1.js文件

//初始化该模块的state
const initalState = 0
//reducer接受两个参数 state(初始值initalState) action:一个对象{type:"自定义的类型INCREMENT",payload:"携带的信息"}
const counterReducer=(state=initalState,action)=>{
    //使用swicth判断匹配类型 return 对应的操作结果
    Switch(action.type){
        case "INCREMENT":
        	//注意这里不是直接改变state 而是返回对state操作的结果
        	return state+1;
        default :
        	return state
    }
}
//将counterReducer进行暴露 在reducer.js中引入进行合并操作
export default counterReducer

2.3 reducer.js 文件编写

//从redux引入合并函数
import {combineReducers} from "redux"
//引入reducer1.js中的模块化的reducer函数
import counterReducer from "./reducer1"
const reducer = combineReducers({
	//state name:reducer name 
    //这里代表这个加载进去的模块名就是counter
    //后续使用store.getState().counter可获取该模块的state 
    //connect connect(mapStateToProps,mapDispatchToProps)(Home) 
    //使用connect时 mapStateToProps(state) { return { counter: state.counter } }
	counter:counterReducer
})

//暴露合并的reducer
export default reducer

2.4 store.js操作创建store

//从redux引入创建函数
import {createStore} from "redux"
//从reducer.js引入合并后的reducer
import rootReducer from "./reducer"

const store = createStore(rootReducer)
//暴露store
export default store

2.5.页面中的基础使用-同步(1)

2.5.1 Home.jsx store.dispatch使用1
//引入写好的store.js
import store from "@/store/store"
//从react-redux引入useSelect函数 用户获取store 中对应的state
import {useSelector} from "react-redux"
function Home(){
    const counter = useSelector(state=>state.counter)
    const increment = ()=>{
        store.dispatch({type:"INCREMENT"})
    }
	return (
		<>
        	<div>Home</div>
        	<div>reducer1中的counter为:{counter}</div>
        	{/*在页面上点击按钮后,counter会变化(每次+1)*/}
        	<button onClick={increment}>调用store.dispatch修改counter</button>
        </>
	)
}
export default Home
2.5.2 Home.jsx Home.jsx store.dispatch使用2
2.5.2.1 修改reducer1.js action.payload属性(携带的信息)
//初始化该模块的state
const initalState = 0
//reducer接受两个参数 state(初始值initalState) action:一个对象{type:"自定义的类型INCREMENT",payload:"携带的信息"}
const counterReducer=(state=initalState,action)=>{
    //使用swicth判断匹配类型 return 对应的操作结果
    Switch(action.type){
        case "INCREMENT":
        	//注意这里不是直接改变state 而是返回对state操作的结果
        	//这里使用action的pyload属性(也就是携带的信息)
        	return state+action.payload;
        default :
        	return state
    }
}
//将counterReducer进行暴露 在reducer.js中引入进行合并操作
export default countReducer
2.5.2.2 action携带信息payload
//引入写好的store.js
import store from "@/store/store"
//从react-redux引入useSelect函数 用户获取store 中对应的state
import {useSelect} from "react-redux"
function Home(){
    const counter = useSelect(state=>state.counter)
    const increment = ()=>{
        //增加payload
        store.dispatch({type:"INCREMENT",payload:5})
    }
	return (
		<>
        	<div>Home</div>
        	<div>reducer1中的counter为:{counter}</div>
        	{/*在页面上点击按钮后,counter会变化(每次+5)*/}
        	<button onClick={increment}>调用store.dispatch修改counter</button>
        </>
	)
}
export default Home

2.6 页面中的基础使用-同步(2)-高阶组件

2.6.1 需要修改index.js
//引入Provider
//从react-redux中引入 用户连接redux和react的插件
import {Provider} from "react-redux"
//引入store/store.js
import store from "@/store/store"
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
	//使用Provider包裹App组件
  <Provider store={store}>
    <App />
  </Provider>
);

我的App组件是这样的:

import { BrowserRouter, Routes, Route,Navigate } from "react-router-dom"
import Home from "@/view/Home/Home";
import About from "@/view/About/About"
import NotFound from "@/view/NotFound/NotFound"
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Navigate replace to="/home"></Navigate>}></Route>
        <Route path="/home" element={<Home></Home>}></Route>
        <Route path="/about" element={<About></About>}></Route>
        <Route path="*" element={<NotFound />} /> {/* 使用'*'来匹配所有未找到的路由 */}
      </Routes>
    </BrowserRouter>
  );
}
export default App;

2.6.2 修改Home.jsx文件
//从react-redux引入connect
import {connect} from "react-redux"
const mapStateToProps=(state)=>{
	return {
        counter:state.counter
    }
}
const mapDispatchToProps=(dispatch)=>{
    return{
        increment:()=>{
            dispatch({type:"INCREMENT",payload:5})
        }
    }
}
function Home(props){
    //使用connect包裹的操作相当于将state和increment映射进了Home组件的props
    const {counter,increment} = props
	return (
    <>
        <div>Home</div>
        <div>reducer1中的counter为:{counter}</div>
        {/*在页面上点击按钮后,counter会变化(每次+5)*/}
        <button onClick={increment}>调用store.dispatch修改counter</button>
    </>
    )
}
//使用connect参入参数回调函数mapStateToProps,mapDispatchToProps包裹Home组件后暴露 高阶函数用法
export default connect(mapStateToProps,mapDispatchToProps)(Home)

该使用效果与同步(1)一样

3.redux异步操作

1.安装中间件扩展dispatch功能

dispatch接受的是一个对象

也就是说redux分发任务的时候disptach接受的action是一个对象,当使用异步操作时,可以利用thunk中间件,让dispatch接受一个函数

安装redux-thunk插件

yarn add redux-thunk

2024-07-11补充:store需要导入applyMiddleware 和thunk

import {createStore,applyMiddleware } from "redux"
import thunk from "redux-thunk"
import rootReducer from "./reducer"
//创建Store 新增applyMiddleware(thunk)
const store = createStore(rootReducer,applyMiddleware(thunk))

export default store

2.原来的dispatch调用是这样的

const mapDispatchToProps=(dispatch)=>{
    return{
        increment:()=>{
            //dispatch接受的是一个对象
            dispatch({type:"INCREMENT",payload:5})
        }
    }
}

另一中接受对象的写法(等同于上面):

function incrementCounter(amount){
	return {type:"INCREMENT",payload:amount}
}
const mapDispatchToProps=(dispatch)=>{
    return{
    	//传入回调函数 回调函数会返回对象{type:"INCREMENT",payload:5}
        increment:dispatch(incrementCounter(5))
    }
}

另外,可将incrementCounter函数放入一个单独创建的actions文件夹下创建的action.js文件,达成模块化的操作

3.模块化actions,编写模拟异步请求

3.1 store文件夹下创建actions文件夹
3.2 actions文件夹下创建独立的action模块 action1.js

这里同理,可以将reducer.js和reducer1.js放入单独的reducer文件夹

-store
	-actions
		-action1.js
	-reducer.js
	-reducer1.js
	-store.js
	//新增一个常亮文件 用于放置action.type常量 可将之前的reducer Switch中的case 后的常亮提取出来放到此处,再从此处暴露引入到reducer文件中
	-constant.js
3.3 constant.js文件 新增异步action.type
export const TEST_INCREMENT = "TEST_INCREMENT"
3.4 action1.js编写如下
//引入需要用的action.type
import { TEST_INCREMENT } from "@/store/constant"
//doSome 是一个模拟请求的函数 这里doSome函数可以提取到对应的api请求文件中
function doSome() {
    return new Promise(r => {
        setTimeout(() => {
            r({ code: 0, data: 10 })
        }, 2000)
    })
}
//fetchTest是一个函数,它返回一个异步函数 函数中执行模拟请求 dispatch分发action 去执行对应的reducer操作
const fetchTest=()=>{//注意这里没有async 这里就是一个普通函数
    //回调函数接受传递dispatch
	return async(dispatch)=>{//这里返回的才是异步函数
        let res = await doSome()
        if(res.code===0){
            //调用dispatch 传入action对象  data等于10 也就是说2s请求完成后,state会增加10 参考3.4 reducer1.js 新增action匹配
            dispatch({type:TEST_INCREMENT,payload:res.data})
        }
    }
}
3.5 reducer1.js 新增action匹配
//从constant.js引入需要使用的action.type常量
import { TEST_INCREMENT } from "@/store/constant"
const initalState = 0
const counterReducer = (state = initalState, action) => {
    switch (action.type) {
        case "INCREMENT":
            return state + action.payload;
        //这里就是例子,直接使用引入的常量进行匹配
        case TEST_INCREMENT:
            return state + action.payload;
        default:
            return state;
    }
}
export default counterReducer
3.6 Home.jsx修改如下 新增了incrementAsync函数 点击按钮执行
import React from 'react'
import { useNavigate } from "react-router-dom"
import { fetchTest} from "../../store1/actions1"
import { connect } from "react-redux"

function Home(props) {
    const history = useNavigate()
    const goToAbout = () => {
        history("/about")
    }
    //从props中解构出来 connect映射的数据(状态以及函数)
    const { counter, increment, incrementAsync } = props
    return (
        <div>
            <header>Home</header>
            <section>
                <div></div>
                <button onClick={increment}>修改state.counter{counter}</button>
                <button onClick={incrementAsync}>异步改变counter{counter}</button>
                <button onClick={goToAbout}>去about页面</button>
            </section>

        </div>
    )
}
const mapStateToProps = (state) => {
    return {
        counter: state.counter
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        increment: () => {
            dispatch({ type: "INCREMENT", payload: 5 })
        },
        //这里 incrementAsync是一个函数 函数内部调用dispatch dispatch的参数就是fetchTest(),fetchTest()返回的是一个异步函数,这个异步函数接受dispatch,等待请求结果,然后使用dispatch分发对应的action 参考3.3 action1.js编写如下
        incrementAsync:()=>dispatch(fetchTest())
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(Home)

以上都是基于connect 高阶组件映射props 调用dispatch达到操作store的效果

此外还可以使用hook useSelect useDispatch达到操作store的效果

4.hook useSelector useDispatch

其他文件不变,Home.jsx使用hook操作

使用方法如下:

import React from 'react'
import { useNavigate } from "react-router-dom"
import { fetchUser} from "../../store1/actions1"
// import { connect } from "react-redux"
import {useSelector,useDispatch} from "react-redux"

export default function Home() {
    const history = useNavigate()
    const goToAbout = () => {
        history("/about")
    }
    //使用useSelector获取对应的仓库状态
    const counter = useSelector(state => state.counter)
    //useDispatch获取dispatch
    const dispatch = useDispatch()
    const increment=()=>{
        //dispatch分发action
        dispatch({ type: "INCREMENT",payload:5 })
    }
    //dispatch 执行异步操作
    const incrementAsync=()=>dispatch(fetchUser())
    return (
        <div>
            <header>Home</header>
            <section>
                <div></div>
                <button onClick={increment}>修改state.counter{counter}</button>
                <button onClick={incrementAsync}>异步改变counter{counter}</button>
                <button onClick={goToAbout}>去about页面</button>
            </section>

        </div>
    )
}
  • 31
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前端开发中的React Redux全家桶是一套常用的技术栈,用于构建复杂的Web应用程序。React是一个由Facebook开发的JavaScript库,用于构建用户界面。它通过将应用程序拆分成可重用的组件,使开发人员能够更轻松地开发、测试和维护Web应用程序。 Redux是一个用于管理应用程序状态的库。它采用了一种称为单一状态树的模式,将整个应用程序的状态存储在一个对象中,并使用纯粹的函数来修改状态。Redux的核心概念包括:store、reducer和action。Store是应用程序的状态容器,reducer是一个纯函数,用于根据action来修改状态,而action代表用户触发的操作React Redux是将ReactRedux结合在一起使用的库。通过使用React Redux,我们可以将Redux的状态管理功能集成到React组件中。React Redux提供了一种称为容器组件的机制,它负责从Redux store中提取数据,并将其作为props传递给展示组件。这种分离开发的模式使得代码更加模块化和易于维护。 React Redux全家桶还包括一些其他的辅助库,如React Router用于跟踪和管理应用程序的URL路径,以及Redux Thunk或Redux Saga用于处理异步操作。这些库的整合和使用能够帮助开发人员构建可扩展、高效和易于维护的前端应用程序。 总之,前端开发中的React Redux全家桶提供了一套完善的工具和库,帮助开发人员构建复杂的Web应用程序。它能够将状态管理和用户界面开发结合在一起,并提供了一种模块化和分离开发的解决方案。通过学习和使用React Redux全家桶,开发人员可以提高开发效率,并构建出更好的用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值