【React 框架】redux / react-redux

本文详细介绍了Redux,包括其用途、工作流程,并通过案例分析了如何在React项目中应用Redux,从纯React版到异步action版,逐步深入。同时探讨了react-redux库的使用,将组件分为UI组件和容器组件,以及如何优化状态管理和组件间数据共享。最后,讨论了纯函数和高阶函数在Redux中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述

1、redux 是什么?

        (1)redux 是一个专门用于做状态管理的 JS 库 ( 不是 react 插件库 )。

        (2)它可以用在 react、angular、vue等项目中, 但基本与 react 配合使用。

        (3)作用:集中式管理 react 应用中多个组件共享的状态。

学习文档:

        (1)英文文档:Redux - A predictable state container for JavaScript apps. | Redux

        (2)中文文档:Redux中文文档

        (3)github:https://github.com/reactjs/redux

2、什么情况下需要使用 redux?

        (1)某个组件的状态,需要让其他组件可以随时拿到(共享)。

        (2)一个组件需要改变另一个组件的状态(通信)。

        (3)总体原则:能不用就不用,如果不用比较吃力才考虑使用。

3、redux 工作流程

流程说明: 

        (1)React Components 是组件,用于实现某种功能,呈现某种效果。

        (2)Action Creators 在 redux 中派发 action 方法,action 通过 store 的 dispatch 方法派发给 Store。

        (3)Store 接收 action,连同之前的 previousState,一起传递给 Reducers。

        (4)Reducers 将处理后的数据返回给 Store。

        (5)Store 改变自身的 state,组件可以通过 store.getState() 方法获取到 state。

餐厅例子说明:

         (1)【React Components — 客人】、【Action Creators — 服务员】、

                  【Store — 老板】、【Reducers — 厨师】。

        (2)客人来餐厅点餐,告诉服务员具体要求(什么菜系,菜名),服务员将记录下的菜单 (action) 拿给餐厅老板,老板反手交代厨师去做菜。厨师做好菜之后,将菜拿到前台老板处,老板叫号让客人过来取。

二、案例( 基于 redux )

1、纯 react 版 

/*  components/Count/index.jsx  */

import React, { Component } from 'react'

export default class Count extends Component {

	state = { count: 0 }

	//加法
	increment = () => {
		const { value } = this.selectNumber
		const { count } = this.state
		this.setState({ count: count + value * 1 })
	}

	//减法
	decrement = () => {
		const { value } = this.selectNumber
		const { count } = this.state
		this.setState({ count: count - value * 1 })
	}

	//奇数再加
	incrementIfOdd = () => {
		const { value } = this.selectNumber
		const { count } = this.state
		if (count % 2 !== 0) {
			this.setState({ count: count + value * 1 })
		}
	}

	//异步加
	incrementAsync = () => {
		const { value } = this.selectNumber
		const { count } = this.state
		setTimeout(() => {
			this.setState({ count: count + value * 1 })
		}, 500)
	}

	render() {
		return (
			<div>
				<h1>当前求和为:{this.state.count}</h1>
				<select ref={c => this.selectNumber = c}>
					<option value="1">1</option>
					<option value="2">2</option>
					<option value="3">3</option>
				</select>&nbsp;
				<button onClick={this.increment}>+</button>&nbsp;
				<button onClick={this.decrement}>-</button>&nbsp;
				<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
				<button onClick={this.incrementAsync}>异步加</button>&nbsp;
			</div>
		)
	}
}

2、redux 精简版

2.1 src 代码结构

2.2 store.js

步骤:

        (1)引入 redux 的 legacy_createStore 函数,创建一个 store。

        (2)legacy_createStore 调用时要传入一个为其服务的 reducer。

        (3)记得暴露 store对象。

// 引入 legacy_createStore 用于创建 redux 中最为核心的 store 对象
// 注:createStore 已被弃用
import { legacy_createStore } from 'redux';

// 引入为 Count 组件服务的 reducer
import countReducer from './count_reducer';

// 暴露 store
export default legacy_createStore(countReducer);

2.3 count_reducer.js 

说明:

        (1)reducer 的本质是一个函数,接收:preState、action,返回加工后的状态。

        (2)reducer 有两个作用:初始化状态、加工状态、

        (3)reducer 被第一次调用时,是store自动触发的,

                 传递的 preState 是 undefined、传递的 action 是:{type:'@@REDUX/INIT_a.2.b.4}。

const initState = 0;
export default function countReducer(prestate = initState, action) {
    const { type, data } = action;
    switch (type) {
        case 'incNumber':
            return prestate + data * 1;
        default:
            return prestate;
    }
}

2.4 Count / index.js

import React, { Component } from 'react'
// 引入 store 用于获取 redux 中的状态
import store from '../redux/store';

export default class Count extends Component {

    // 渲染界面的第一种方式,但是这种方式只能渲染当前的页面。
    state = {};
    componentDidMount() {
        // 监测 redux 中状态的变化,只要状态变化,就会调用这个函数。
        store.subscribe(() => {
            // 这里并没有更新任何 this.state 的值,只是借用 state 来调用 render。
            this.setState({});
        })
    }

    // +
    incNumber = () => {
        const { value } = this.seleteNumber;
        store.dispatch({ type: 'incNumber', data: value });
        // store.dispatch(createIncNumber(value));
    }

    // 异步加
    asynNumber = () => {
        const { value } = this.seleteNumber;
        setTimeout(() => {
            store.dispatch({ type: 'incNumber', data: value });
        }, 2000)
    }

    render() {
        return (
            <div>
                <h4>当前求和为: {store.getState()}</h4><br />
                <select ref={c => { this.seleteNumber = c }}>
                    <option value='1'>1</option>
                    <option value='2'>2</option>
                    <option value='3'>3</option>
                </select>&nbsp;
                <button onClick={this.incNumber}>+</button>&nbsp;
                <button onClick={this.asynNumber}>异步加</button>&nbsp;
            </div>
        )
    }
}

2.5 index.js

        在 index.js 中监测 store 中状态的改变,一旦发生改变重新渲染 <App/>。

        备注:redux 只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。

import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App'
import store from './component/redux/store';

const root = createRoot(document.getElementById('root'));
root.render(<App />)

// 第二种方式:当状态发生变化时,重新渲染 App 组件。
store.subscribe(() => {
    root.render(<App />)
})

3、redux 完整版

redux 文件夹里的新增文件:

        (1)count_action.js 专门用于创建 action 对象。

        (2)constant.js 放置容易写错的 type 值。

// count_action.js
import { INC } from "./constant";

export function createIncAction(data) {
    return { type: INC, data: data };
}
// constant.js
export const INC = 'incNumber';

4、异步 action 版

action 有两种形式: Object 类型的一般对象、函数。

同步 action:action 的值为 Object 类型的一般对象。

异步 action:action 的值为函数,异步 action 中一般都会调用同步 action,异步 action 不是必须要用的。

(1)明确:延迟的动作不想交给组件自身,想交给 action。

(2)何时需要异步 action:想要对状态进行操作,但是具体的数据靠异步任务返回

(3)具体编码:

         ① npm i redux-thunk,并配置在 store 中。

         ② 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。

         ③ 异步任务有结果后,分发一个同步的action去真正操作数据。

         ④ 备注:异步 action 不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。

4.1 index.js 

// index.js
    asynNumber = () => {
        const { value } = this.seleteNumber;
        store.dispatch(createAsynAction(value * 1, 500));

        // 原来的异步操作(在这部分直接等待结果)
        // setTimeout(() => {
        //     store.dispatch({ type: 'incNumber', data: value });
        // }, 500)
    }

4.2 store.js

// 引入中间件的API: applyMiddleware
import { legacy_createStore, applyMiddleware } from 'redux';
import countReducer from './count_reducer';
// 引入中间件
import thunk from 'redux-thunk';

// 暴露 store, applyMiddleware(thunk) 作为第二个参数传入
export default legacy_createStore(countReducer, applyMiddleware(thunk));

4.3 count_action.js

import { INC } from "./constant";

export function createIncAction(data) {
    return { type: INC, data: data };
}

export function createAsynAction(data, time) {
    // 返回一个函数
    return (dispatch) => {
        setTimeout(() => {
            dispatch(createIncAction(data));
        }, time)
    }
}

三、react-redux

1、概述

1.1 理解

  • 一个react插件库。
  • 专门用来简化react应用中使用redux。

1.2 react-redux 将所有组件分成两大类

(1)UI 组件

  • 只负责 UI 的呈现,不带有任何业务逻辑。
  • 通过props接收数据(一般数据和函数)。
  • 不使用任何 Redux 的 API。
  • 一般保存在components文件夹下。

(2)容器组件

  • 负责管理数据和业务逻辑,不负责UI的呈现。
  • 使用 Redux 的 API。
  • 一般保存在containers文件夹下。

总结:

(1)明确两个概念:

        ① UI组件:不能使用任何 redux 的 api,只负责页面的呈现、交互等。

        ② 容器组件:负责和 redux 通信,将结果交给 UI 组件。

(2)如何创建一个容器组件 — 靠 react-redux 的 connect 函数.

         connect(mapStateToProps,mapDispatchToProps)(UI组件)

                 -mapStateToProps:映射状态,返回值是一个对象

                 -mapDispatchToProps:映射操作状态的方法,返回值是一个对象

(3)备注1:容器组件中的 store 是靠 props 传进去的,而不是在容器组件中直接引入。

(4)备注2:mapDispatchToProps,也可以是一个对象。

2、案例(效果与上一节相同)

2.1 代码结构

2.2 containers / Count / index.js(容器组件)

        说明:该组件为容器组件,个人理解是有两个作用:① 与 redux 通信(核心是store),使用 redux 的  API 获取状态(state)、操作状态(dispatch)。② 通过 connect 与 UI 组件绑定,为 UI 组件传递状态和操作状态的方法。

        注意:容器组件中的 store 不能在文件中直接导入,而应该通过 App.js,也就是外部组件向容器组件传递 store。

// App.js
import React, { Component } from 'react'
import Count from './container/Count'
import store from './redux/store'

export default class App extends Component {
    render() {
        return (
            <div>
                <Count store={store} />
            </div>
        )
    }
}
// 引入 Count 的 UI 组件
import CountUI from '../../component/Count'

// 引入 redux 定义好的方法
import { createIncAction, createAsynAction } from '../../redux/count_actions'

// 引入 connect 用于连接 UI 组件与 redux
import { connect } from 'react-redux'

/* 
    1.mapStateToProps 函数返回的是一个对象;
    2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
    3.mapStateToProps 用于传递状态
*/
const mapStateToProps = (state) => {
    return { count: state };
}

/* 
    1.mapDispatchToProps 函数返回的是一个对象;
    2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
    3.mapDispatchToProps 用于传递操作状态的方法
*/
const mapMethodToProps = (dispatch) => {
    return {
        createIncAction: (value) => { dispatch(createIncAction(value)) },
        createAsynAction: (value, time) => { dispatch(createAsynAction(value, time)) }
    }
}
/*  
    1.使用connect()()创建并暴露一个Count的容器组件
    2.connect 接收两个参数(都是函数):
      mapStateToProps 用于传递状态,mapMethodToProps用于传递操作状态的方法。
*/
export default connect(mapStateToProps, mapMethodToProps)(CountUI)

2.3 component / Count / index.js(UI 组件)

        说明:这个文件是 Count 的 UI 组件,组件中不包含任何 redux 的 API,只关注于如果获取状态等最终呈现页面。

        关于传递状态及操作状态的方法:容器组件与 UI 组件是父子组件,UI 组件通过 this.props 获取状态及操作状态的方法。

import React, { Component } from 'react'

export default class Count extends Component {

    // +
    incNumber = () => {
        const { value } = this.seleteNumber;
        this.props.createIncAction(value * 1);
    }

    // 异步加
    asynNumber = () => {
        const { value } = this.seleteNumber;
        this.props.createAsynAction(value * 1, 500);
    }

    render() {
        return (
            <div>
                <h4>当前求和为: {this.props.count}</h4><br />
                <select ref={c => { this.seleteNumber = c }}>
                    <option value='1'>1</option>
                    <option value='2'>2</option>
                    <option value='3'>3</option>
                </select>&nbsp;
                <button onClick={this.incNumber}>+</button>&nbsp;
                <button onClick={this.asynNumber}>异步加</button>&nbsp;
            </div>
        )
    }
}

3、优化

3.1 简写 mapMethodToProps

        mapMethodToProps 可以简化成用对象 { key: value } 的方式。因为 key 和 value 的名称相同,因此简写了。

/* 未简化方法 */
// const mapStateToProps = (state) => {
//     return { count: state };
// }

// const mapMethodToProps = (dispatch) => {
//     return {
//         createIncAction: (value) => { dispatch(createIncAction(value)) },
//         createAsynAction: (value, time) => { dispatch(createAsynAction(value, time)) }
//     }
// }

// export default connect(const mapStateToProps, mapMethodToProps)(CountUI)


/* 简化方法 */
export default connect(
    state => ({ count: state }),
    {
        createIncAction,
        createAsynAction,
    }
)(CountUI)

3.2 Provider 组件的使用

        无需自己给容器组件传递 store,给 <App/> 包裹一个 <Provider store={store}> 即可。

import { Provider } from 'react-redux';
import store from './redux/store';

const root = createRoot(document.getElementById('root'));
root.render(
    <Provider store={store}>
        <App />
    </Provider>
)

3.3 整合 UI 组件与容器组件

import { createIncAction, createAsynAction } from '../../redux/count_actions'
import { connect } from 'react-redux'
import React, { Component } from 'react'

class Count extends Component {

    // +
    incNumber = () => {
        const { value } = this.seleteNumber;
        this.props.createIncAction(value * 1);
    }

    // 异步加
    asynNumber = () => {
        const { value } = this.seleteNumber;
        this.props.createAsynAction(value * 1, 500);
    }

    render() {
        return (
            <div>
                <h4>当前求和为: {this.props.count}</h4><br />
                <select ref={c => { this.seleteNumber = c }}>
                    <option value='1'>1</option>
                    <option value='2'>2</option>
                    <option value='3'>3</option>
                </select>&nbsp;
                <button onClick={this.incNumber}>+</button>&nbsp;
                <button onClick={this.asynNumber}>异步加</button>&nbsp;
            </div>
        )
    }
}

export default connect(
    state => ({ count: state }),
    {
        createIncAction,
        createAsynAction,
    }
)(Count)

3、多组件数据共享案例

笔记总结:

        (1)定义一个 Pserson 组件,和 Count 组件通过 redux 共享数据。

        (2)为 Person 组件编写:reducer、action,配置 constant 常量。

        (3)重点:Person 的 reducer 和 Count 的 Reducer 要使用 combineReducers 进行合并,合并后的总状态是一个对象!!!

        (4)交给 store 的是总 reducer,最后注意在组件中取出状态的时候,记得“取到位”。

3.1 效果

        有两个组件:Count 和 Person,实现组件间的数据共享。

3.2 代码结构

        代码只说明数据共享的部分,完整代码放在资源:CSDN。(不需要积分)

3.3 store.js

import { legacy_createStore, applyMiddleware, combineReducers } from 'redux';

// 引入为 Count 组件服务的 reducer
import countReducer from './reducers/count_reducer';

// 引入为 Person 组件服务的 reducer
import personReducer from './reducers/person_reducer';

// 引入异步操作需要的模块
import thunk from 'redux-thunk';

const allReducer = combineReducers({
    sum: countReducer,
    personHandle: personReducer
})

// 暴露 store
export default legacy_createStore(allReducer, applyMiddleware(thunk));

3.4 container / Person / index.js

export default connect(
    // 传递状态
    state => ({
        personArr: state.personHandle,
        count: state.sum
    }),
    // 操作状态的方法
    {
        addPerson,
    }
)(Person)

四、纯函数与高阶函数(一些知识点)

1、纯函数

(1)一类特别的函数:只要是同样的输入(实参),必定得到同样的输出(返回)。

(2)必须遵守以下一些约束:

         ① 不得改写参数数据。

         ② 不会产生任何副作用,例如网络请求,输入和输出设备。

         ③ 不能调用Date.now()或者Math.random()等不纯的方法。

(3)redux 的 reducer 函数必须是一个纯函数。【所以数组推入的方式使用的是 [ personObj, ...data ],不使用 arr.push 的方法(使用会导致页面数据不更新)】

2、高阶函数

(1)理解:一类特别的函数:

         ① 情况1:参数是函数。        

         ② 情况2:返回是函数。

(2)常见的高阶函数:

         ① 定时器设置函数。

         ② 数组的 forEach() / map() / filter() / reduce() / find() / bind()。

         ③ promise。

         ④ react-redux 中的 connect 函数。

(3)作用:能实现更加动态,更加可扩展的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值