06-React组件 Redux React-Redux

React组件化(以Ant-Design为例)

组件化编程,只需要去安装好对应的组件,然后通过各式各样的组件引入,实现快速开发
我们这里学习的是 Ant-design (应该是这样),它有很多的组件供我们使用
在这里插入图片描述
引入各种组件,可以方便开发,省着自己去二次封装组件,同时也更好看了

在这里插入图片描述

快速起步

安装antd组件库

npm install antd

随便一个组件里面引入

import { Button } from "antd";

class App extends React.Component {
  render() {
	<div>
            <Flex gap="small" wrap="wrap">
              <Button type="primary">Primary Button</Button>
              <Button>Default Button</Button>
              <Button type="dashed">Dashed Button</Button>
              <Button type="text">Text Button</Button>
              <Button type="link">Link Button</Button>
            </Flex>
     </div>
  }
}
export default App;

运行一下
在这里插入图片描述

大概就是这么个东西,一定学会看官方文档,尤其这种组件类的,不看文档就啥也做不了
AntDesign官方文档

其他UI,但是是国外的
material-ui

大概就是这些东西,还有自定义主题的一些方法,因为每个版本的官方修改方式都不一样,所以建议现用现查

Redux

非必须学习的项目,但是如果项目里用了就得学

Redux简介

1.redux是一个专门用于做状态管理的JS库(不是react插件库,只是名字像)。
2.它可以用在react, angular, vue等项目中, 但基本与react配合使用。
3.作用: 集中式管理react应用中多个组件共享的状态。

也类似于VueX

什么时候需要用Redux

首先,Redux是在有很多很多组件的情况下,才有可能需要用到Redux的,如果是单个组件,就自然没有很复杂的传值需求了

Redux 适用于多交互、多数据源的场景。简单理解就是复杂

从组件角度去考虑的话,当我们有以下的应用场景时,我们可以尝试采用 Redux 来实现

  1. 某个组件的状态需要共享时
  2. 一个组件需要改变其他组件的状态时
  3. 一个组件需要改变全局的状态时

除此之外,还有很多情况都需要使用 Redux 来实现(还没有学 hook,或许还有更好的方法)

在这里插入图片描述

Redux工作流程

  • store

store 是 Redux 的核心,可以理解为是 Redux 的数据流向指挥,我们可以将任何我们想要存放的数据放在 store 中,在我们需要使用这些数据时,我们可以从中取出相应的数据。因此我们需要先创建一个 store ,在 Redux 中可以使用 createStore API 来创建一个 store

store是无法直接进行操作的,需要借助Redux进行操作
store是一个调度者,store是指挥者,不干活。需要任何操作,或者动作,都会去分发走,找到对应的担当来做。(如果越过了store,action直接去找reducers,就有点类似去餐厅点餐,不找前台点餐,直奔后厨要吃的😂)

  • action

actionstore 中唯一的数据来源,一般来说,我们会通过调用 store.dispatch 将 action 分发到 store

我们需要传递的 action 是一个对象,它必须要有一个 type 值(如果是初始化,就传@@init@@代表要初始化),data值在第一次传参的时候可以为undefined,然后由Reducers来初始化赋值。

  • reducers

在 Reducers 中,我们需要指定状态的操作类型(type),要做怎样的数据更新,因此这个类型是必要的。

因为Reducers中会有很多具体处理的Reducer,所以这里Reducers代表很多处理Reducer的集合。

reducer 会根据 action 的指示,对 state 进行对应的操作,然后返回操作后的 state

另外,Reducer可以加工状态,加工的前提是有上一次的状态值,如果没有状态值就要初始化一个状态值。有了状态值之后就可以进行下一步的加工。

在这里插入图片描述

手写一个Redux精简版

这里只是一个简化,忽略了很多东西(比如Creators创建Action的过程),侧重于展示store,reducer之间的关系
store.js文件:
创建一个store的js,导出供外界使用

import { createStore } from "redux";
//引入为store服务的Reducer
import countReducer from "./count_reducer";
//手动创建一个store
const store = createStore(countReducer);

//全局只暴露这一个store对象
export default store;

count_reducer.js文件:
我们创建专门计数的reducer(简单来说就是专门负责处理某件事的function),这里取名叫作count_reducer

所有的action通过dispatch传进去之后,类型,数据都托管于store这个中心,调用的组件只需要把 type,data传进去,剩下的只需要等着从store中获取结果即可!

/*
    作为一个reducer应该有如下功能
    Store传来(previousState,action),Reducer接收到参数做出判断,
    previousState判断是否是空,是空就得初始化。非空就按照action进行下一步操作

    action处理完之后,把处理好的newState(previousState处理之后的版本),返回给store,等待Store返回给React组件

    所以以上的这些操作,只能用function。所以Reducers里的Reducer本质就是函数

	动作完成后,将数据暂存给store,等待后续组件获取值即可
*/

export default function countReducer(previousState, action) {
    // 从Action对象里传来的action对象,里面包含type和data,所以我们需要解构出来。等待后续处理
    const { type, data } = action;
    if (previousState === undefined) {
        // 如果之前的状态是空的,就初始化之后还给store中心
        // 或者可以这么写,给参数给默认值
        // function countReducer(previousState=0, action) {

        return 0;
    }

    // 判断传入功能类型
    switch (type) {
        case "add":
            //这里用原来的值+新传入的值,得到新值
            return previousState + data * 1;
        case "sub":
            //这里用原来的值-新传入的值,得到新值
            return previousState - data * 1;
        // 提一嘴,这里都是return,所以不用break
        default:
            return previousState;
    }
}

Count.jsx文件:
调用方组件:

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

export default class Count extends Component {
    // 之前准备的state,存储值count 都不用存在了,因为已经托管给store了
    // 但是,自己组件内部的值其实是还需要放在state的,因为store只需要托管复杂共享的情况
    state = {
        // 这个实际上就不要了,因为保存在state中
        // countNumber: "",
        // 但是比如car这个属性,只有自己用,就没必要兜一圈放redux中了,放自己组件里就挺好
        car: "威朗Pro GS",
    };

    add = () => {
        const { value } = this.count;
        // store是分发中心,告诉reducer要干的事情,以及传入的数值
        // 我们只需要将 type(要做的事情),value(原始值) 告诉store 。让store去找对应的reducer操作即可
        // 对应的reducer拿到数值,做出判断以及相应动作处理数据,处理好之后return值在store中等待组件get
        // 但注意,仅仅调用dispatch是不够的,因为redux是第三方js。无法触发页面刷新
        // 所以需要检测Redux里状态改变时,就去调用render。这里用到了订阅subscribe
        // 你都不用自己再解构接收值,所有的函数处理,值存储,都在redux中做好了,只需要我们从store中get结果即可
        // 获取store的地方,因为被监听所以自动刷新了,触发render
        store.dispatch({ type: "add", data: value });
        store.subscribe(() => {
            // 只要Redux里状态改变时,就去调用render。这里用到了订阅subscribe。手动触发一下就行
            // 借助setState,传个空值进去就可以触发render重新渲染
            // 当然,我们也可以在index.js的根标签监听这个。监听整个App组件,利用diffing算法全部更新,避免性能下降
            this.setState({});
        });
    };
    sub = () => {
        const { value } = this.count;
        store.dispatch({ type: "sub", data: value });
        store.subscribe(() => {
            // 手动监听,触发页面重新渲染
            this.setState({});
        });
    };


    render() {
        console.log(store);
        return (
            <div>
                当前结果<h1>{store.getState()}</h1>
                选择内容
                <select ref={(c) => (this.count = c)}>
                    <option value={1}>1</option>
                    <option value={2}>2</option>
                    <option value={3}>3</option>
                </select>
                <button onClick={this.add}>+</button>
                <button onClick={this.sub}>-</button>
            </div>
        );
    }
}

总结一下精简案例

(1).去除Count组件自身的状态
(2).src下建立:
				-redux
					-store.js
					-count_reducer.js

(3).store.js:
			1).引入redux中的createStore函数,创建一个store
			2).createStore调用时要传入一个为其服务的reducer
			3).记得暴露store对象

(4).count_reducer.js:
			1).reducer的本质是一个函数,接收:preState,action,返回加工后的状态
			2).reducer有两个作用:初始化状态,加工状态
			3).reducer被第一次调用时,是store自动触发的,
							传递的preState是undefined,
							传递的action是:{type:'@@REDUX/INIT_a.2.b.4}( _a.2.b.4是随机数,为了避免和自己写的type名有重合)

(5).在index.js中监测store中状态的改变,一旦发生改变重新渲染<App/>
		备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。

完整版Redux

上面的案例没有写action的创建,所以这里补齐就成为了完整版。
该文件专门为Count组件需要用的Reducer去生成action对象,封装好type,这样在调用组件传一个data进来就可以了

新增文件:
	1.count_action.js 专门用于创建action对象
	2.constant.js 放置容易写错的type值

store.js文件:
创建一个store的js,导出供外界使用。这个和上面的没区别,这里不赘述了

count_action.js文件:

/*
    该文件专门为Count需要用的Reducer去生成action对象,封装好type,这样在调用组件传一个data进来就可以了
*/
import { ADD } from "./constant.js";

// 注意,这里有个坑,就是我希望在这里返回对象的箭头函数,最外侧不能是花括号
// (data) => { type: ADD, data };  如果是这样,会把 type: ADD, data 这部分识别为函数体,就没法返回对象了
// 所以我们用括号给括起来,就自动返回对象了
export const createAddAction = (data) => ({ type: ADD, data });
// 这两种写法等价
export function createIncrementAction(data) {
     // 要返回Action对象(返回type和data数据)
     return { type: ADD, data };
}

count_reducer.js文件:
本案例中,主要是引入常量来表示Type,避免了容易拼错的问题

import { ADD, SUB } from "./constant.js";

export default function countReducer(previousState, action) {
    // 从Action对象里传来的action对象,里面包含type和data,所以我们需要解构出来。等待后续处理
    const { type, data } = action;
    if (previousState === undefined) {
        // 如果之前的状态是空的,就初始化之后还给store中心
        // 或者可以这么写,给参数给默认值
        // function countReducer(previousState=0, action) {

        return 0;
    }

    // 判断传入功能类型
    switch (type) {
        case ADD:
            //这里用原来的值+新传入的值,得到新值
            return previousState + data * 1;
        case SUB:
            //这里用原来的值-新传入的值,得到新值
            return previousState - data * 1;
        // 提一嘴,这里都是return,所以不用break
        default:
            return previousState;
    }
}

Count.jsx文件:
调用方组件,这里主要强调变化的部分,省略了部分代码:

import React, { Component } from "react";
// 传入action函数,我们只需要传入data即可
import { createAddAction } from "../redux/count_action";
// 获取store,用于获取store中的状态
import store from "../redux/store";

export default class Count extends Component {

	...省略
	
    add = () => {
        const { value } = this.count;
        // 引入了action后,就不需要我们手动定义type了,在action中已经做好定义了,我们只需要传入data即可
        // store.dispatch({ type: "add", data: value });

        // 引入,并且直接用count_action所封装好的函数来传入参数
        store.dispatch(createAddAction(value * 1));

        store.subscribe(() => {
            // 监听,store值变化重新render
            this.setState({});
        });
    };
    sub = () => {
            省略...
    };
    render() {
        return (
            省略...
        );
    }
}

异步action版

action有两种类型,根据返回值类型来区分

  • action的值为Object,则为同步action(store可以直接接收处理)
//返回一个对象
export const createSubAction = (data) => ({ type: SUB, data });
  • action的值为function,则为异步action(只有function才能开启异步任务,并且,store不可以直接接收处理异步action,需要通过中间件处理后才能接收
export const createAddAsync = (data,time) => {
    return ()=>{
        setTimeout(() => {
            ...省略
        }, time);
    }
};

之前的异步add方法,其本质还是在组件里写的,可以看到,等待的异步过程没有在Redux里面写

addAsync = () => {
    const { value } = this.count;
    setTimeout(() => {
        store.dispatch(createAddAction(value * 1));
    }, 5000);
};

我们现在要把异步等待的操作,放在Redux的Action Creators里面,不放在组件里面等待了
调用方组件Count:

addAsync = () => {
    const { value } = this.count;
    //常规调用,看似没问题
    store.dispatch(createAddAsync(value, 5000));
    store.subscribe(() => {
	    // 手动监听,触发页面重新渲染(也可以去监听App组件)
	    this.setState({});
    });
};

action组件:

export const createAddAsync = (data, time) => {
    return () => {
        // 这里其实只套了一个定时操作
        setTimeout(() => {
            // 通过store调用已经定义好的增加action,省着我们再写了
            store.dispatch(createAddAction(data));
        }, time);
    };
};

但实际上这是有问题的,运行代码会报错

翻译过来就是,store不直接接收action的值为function(异步action)。想要接收必须去用一个中间件,让store允许接收函数
在这里插入图片描述
引入中间件:npm install redux-thunk需要我们安装一下
引入完成之后,就需要在store.js里面修改一下,用于支持异步action。做的这一切,只是为了让store可以接收异步action返回的函数
store.js

//引入store创建,以及中间件
import { createStore, applyMiddleware } from "redux";
//引入为store服务的Reducer
import countReducer from "./count_reducer";
// 引入thunk给applyMiddleware中间件用
import thunk from "redux-thunk";
//手动创建一个store,传入applyMiddleware(thunk) 
const store = createStore(countReducer, applyMiddleware(thunk));

//全局只暴露这一个store对象
export default store;

此时我们再看修改后的组件:

调用方组件Count(没有变化):

addAsync = () => {
    const { value } = this.count;
    store.dispatch(createAddAsync(value, 5000));
    store.subscribe(() => {
        // 手动监听,触发页面重新渲染(也可以去监听App组件)
        this.setState({});
    });
};

action组件:

// 异步action,就是只action的值为函数,在异步action中,一般都会调用同步action,异步action不是必须要用的
export const createAddAsync = (data, time) => {
    return () => {
        // 这里其实只套了一个定时操作
        setTimeout(() => {
            // 调用已经定义好的增加action
            store.dispatch(createAddAction(data));
            console.log(data, time);
        }, time);
    };
    // 不用store调用dispatch也可以,因为dispatch会自动传进来一个,这两种完全等价
    // return (dispatch) => {
    //     // 这里其实只套了一个定时操作
    //     setTimeout(() => {
    //         // 调用已经定义好的增加action
    //         dispatch(createAddAction(data));
    //         console.log(data, time);
    //     }, time);
    // };
};

此时再测试,不再报错。

配置完毕之后的store
如果我们给store传入一个普通类型的Object action,store就会直接找Reducer去做处理
如果给store传入一个异步类型的Function action,这个函数store就会帮你调用

总结下来,虽然异步的action调用的时候返回值是函数,但是最后一般都会调用同步action,来完成数据的操作

React18版本的store监听刷新

之前给的store subscribe监听刷新,是React17版本的,React18版本的可以参考这个
ReactDOM.render is no longer supported in React 18.
改造之后的index.js

// 引入React核心库
import React from "react";
import { createRoot } from "react-dom/client";
// 引入App标签
import App from "./App";
import store from "./redux/store";

// React18版本监听并刷新页面
const root = createRoot(document.getElementById("root"));
root.render(<App />);

//监测redux中状态的改变,如redux的状态发生了改变,那么重新渲染App组件
store.subscribe(() => {
  root.render(<App />);
});

总结异步action

 (1).明确:延迟的动作不想交给组件自身,想交给action
 (2).何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。
 (3).具体编码:
 			1).npm install redux-thunk,并配置在store中
 			2).创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。
 			3).异步任务有结果后,分发一个同步的action去真正操作数据。
 (4).备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。

React-Redux基础

安装:npm install react-redux
安装不上去就用这个:npm install react-redux --legacy-peer-deps
还不行就把package.json里面的这俩都删了,在安装React-Redux

"redux": "^4.2.1",
"redux-thunk": "^2.4.2",

引言

React-Redux是React专门出的Redux,属于官方出品
宗旨在于将UI与redux分隔开,所有的操作都要经由容器组件
在这里插入图片描述

引言-简化版连接UI组件与容器组件


前置知识

redux里的唯一的api:connect;

使用的时候,一般都是这么用connect()();

简单来说,得分开看connect()是一个返回一个函数的方法
connect()()是在connect()返回函数后,继续再次调用这个返回的函数
就有点类似下面这个,调用connect()(),最后会触发a里的输出OK
当然,a()也可以返回一个返回值

connect(){
	return a();
}

a(){
	console.log("OK")
}

首先明确文件应该放在哪个包下面:
UI组件:components包下
容器组件:containers包下

且UI组件的里面,不能有任何关于Redux相关的API(store,actionCreator,dispatch… 这些API都不能引入了)。只能有UI组件以及UI组件动作相关的东西。在components里创建countTemplate.jsx
countTemplate.jsx

import React, { Component } from "react";

export default class CountTemplate extends Component {
    // 纯UI组件+页面操作
    //加法
    increment = () => {
        // 这个是获取页面的选择值
        const { value } = this.selectNumber;
    };
    ...
    //异步加
    incrementAsync = () => {
        const { value } = this.selectNumber;
    };

    render() {
        //console.log('UI组件接收到的props是',this.props);
        return (
            <div>
                <h1>当前求和为:{"???"}</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>
        );
    }
}

简化版里面,容器组件更像是一个桥梁,负责连接 UI组件与Redux 的交互。所以在Container里面,创建一个count.jsx

在这个桥梁(count.jsx)里,我们可以导入UI组件等待连接,(理论上应该导入Redux的Store,就完成了,但是不行,store必须从App里面通过props传进来 )

我在container容器里面引入了store仍然报找不到的错(这里是错误示范)

//引入Count的UI组件
import CountTemplate from "../components/CountTemplate";
// 引入store
import { store } from "../../redux/store";
// 引入connect的API
import { connect } from "reat-redux";

// 导入connect,并且连接UI
export default connect()(CountTemplate);

我已经引进来store了,但是还是提示找不到store,这里不是因为我没有在connect里连接,而是不允许这种用法。只能在调用Container组件的组件里传值用props进去。
在这里插入图片描述

注意:react-redux不允许直接引入,只能从Container组件被调用的那一级组件里传进来
App组件(调用Container的组件)。这样就不报错了

import React from "react";
import Count from "./pages/Count";
import "./App.css";
import store from "./redux/store";
class App extends React.Component {
    render() {
        return (
            <div>
                {/* 只能用props传递store进入Container组件 */}
                <Count store={store}></Count>
            </div>
        );
    }
}
export default App;

省略store.subscribe

首先就是之前的store.subscribe,之前我们在Redux里面,由于组件和Redux之间没有直接监听更新的手段。所以这里需要手动去监听渲染组件

// 引入React核心库
import React from "react";
import { createRoot } from "react-dom/client";
// 引入App标签
import App from "./App";
import store from "./redux/store";

// React18版本监听并刷新页面
const root = createRoot(document.getElementById("root"));
root.render(<App />);

//监测redux中状态的改变,如redux的状态发生了改变,那么重新渲染App组件
store.subscribe(() => {
  root.render(<App />);
});

而在React-Redux中,connect就自动集成了监听并更新的功能,所以我们不必再手动监听。删掉即可,测试完美替换,不影响功能。

// 引入React核心库
import React from "react";
import { createRoot } from "react-dom/client";
// 引入App标签
import App from "./App";

// React18版本监听并刷新页面
const root = createRoot(document.getElementById("root"));
root.render(<App />);

React-Redux的基本使用

上面说到,Container想接受到store,只能通过props形式传递,但是Container本身,并不是标签形式的,所以就用不了props来传递数据。要想把Container接收到的props传给UI组件,就只能用connect的API,通过函数返回对象的形式来传递


首先看这个Container组件,是没有办法去写子组件传值的
也就是<CountTemplate key1={value1} ...>这种是没有机会写的
所以我们只能依靠connect传值

//引入Count的UI组件
import CountTemplate from "../../components/Count/CountTemplate";
// 引入connect的API
import { connect } from "react-redux";

// 使用connect()()创建并暴露一个Count的容器组件
export default connect()(CountTemplate);

props是key-value的形式,所以我们要来模仿这种形式
这里就采用了对象的形式,来模拟这种key-value
{key:value}或者{key:()=>{函数体}} 也就是说,props的形式都可以模拟
修改一下Container的Count组件

//引入Count的UI组件
import CountTemplatefrom "../../components/Count/CountTemplate";
// 引入connect的API
import { connect } from "react-redux";

function a() {
    // a函数返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value,这个value是状态
    return { key1: "value1" };
}

function b() {
    // a函数返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value,这个value是函数
    return {
        key2: () => {
            console.log("OK");
        },
    };
}
// 使用connect建并暴露一个Count的容器组件,传a,b函数的返回值给UI组件
export default connect(a, b)(CountTemplate);

UI组件获取参数:
在UI组件打印一下:console.log("UI组件接收到的props是", this.props);

在这里插入图片描述

所以想获取就更简单了,直接this.props.key即可

注意,a传值的时候有点小坑,我们应该专注于状态的传递。所以优化一下

function a(state) {
	// 这里直接传state即可,这个state是从App传过来的
	// 所有的状态就直接用这个state给传过去了(注意是所有的state)
	// key1是自定义的,不设限
    return { key1: state };
}

同样,b里如果要调用dispatch函数,本应该import store

import store from "../../redux/store";
import { createAddAction } from "../../redux/count_action";

function b() {
    // 返回值返回value为函数的对象
    // add是自定义的,不设限,仅代表当前返回值的函数
    return {
        add: (number) => store.dispatch(createAddAction(number)),
    };
}

但是由于Container里面不应该引入store,并且dispatch可以自动传入,所以就不用通过导入store来调用dispatch了。和上面的state一样,自动传入dispatch
改造后:

//这个是自定义的action函数,不是redux的函数
import { createAddAction } from "../../redux/count_action";

function b(dispatch) {
    // 自动传入了dispatch
    return {
    // 不再需要用store调用dispatch  
    // 这种就可以简写了add: (number) => store.dispatch(createAddAction(number)),
        add: (number) => dispatch(createAddAction(number)),
    };
}

案例总结

demo结构,重点主要集中在Container上
在这里插入图片描述

这四个文件照之前的没有任何变化,所以不赘述了
在这里插入图片描述
UI组件 CountTemplate.jsx

import React, { Component } from "react";

export default class Count extends Component {
  add = () => {
  	// add操作
    const { value } = this.selectNumber;
    // 找到传入函数并传参
    this.props.add(value * 1);
  };
  addNotOdd = () => {
    // 奇数add操作
    const { value } = this.selectNumber;
    if (this.props.key1 % 2 !== 0) {
      this.props.add(value * 1);
    }
  };
  addAsync = () => {
  	// 异步add操作
    const { value } = this.selectNumber;
    setTimeout(() => {
      this.props.add(value * 1);
    }, 500);
  };

  render() {
    return (
      <div>
        <div>当前求和:{this.props.key1}</div>
        <select ref={(c) => (this.selectNumber = c)}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>
        <div>
          <button onClick={this.add}>add</button>
          <button onClick={this.sub}>sub</button>
          <button onClick={this.addNotOdd}>addNotOdd</button>
          <button onClick={this.addAsync}>addAsync</button>
        </div>
      </div>
    );
  }
}

Container组件 Countainer.jsx

import { connect } from "react-redux";
import CountTemplatefrom "../../components/count/CountTemplate";
import { add } from "../../redux/count_action";

function a(store) {
  return { key1: store };
}

function b(dispatch) {
  // 允许传递多个函数
  return {
    // add操作的函数
    add: (data) => dispatch(add(data)),
    // sub操作的函数
    sub: (data) => dispatch(sub(data)),
  };
}

// 传入func a(负责state传递)  func b(负责函数动作传递)
// 最后桥梁连接CountTemplate组件
export default connect(a, b)(CountTemplate);

App.jsx

import React from "react";
import Container from "./pages/container/Container";
import store from "./redux/store";
import { BrowserRouter } from "react-router-dom";

class App extends React.Component {
  render() {
    return (
      <BrowserRouter>
      {/* 在App向Container传递store(props形式) */}
        <Container store={store}></Container>
      </BrowserRouter>
    );
  }
}
export default App;

index.js

// 引入React核心库
import React from "react";
import { createRoot } from "react-dom/client";
// 引入App标签
import App from "./App";
import store from "./redux/store";

// React18版本监听并刷新页面
const root = createRoot(document.getElementById("root"));
root.render(<App />);

//监测redux中状态的改变,如redux的状态发生了改变,那么重新渲染App组件
store.subscribe(() => {
  root.render(<App />);
});

以上就可以完成计算组件

在这里插入图片描述

Container命名优化(引出规定的函数名)

传值的函数可以看到,实际上,所有的state和function的参数传递,通过一次就可以完全传递完。
所以react-redux里面就提供好了函数名专门传递state和function,就不需要我们再去单独定义了。更加规范了。

// 函数命名不规范
function a(store) {
  return { key1: store };
}
// 函数命名不规范
function b(dispatch) {
  // 允许传递多个函数
  return {
    // add操作的函数
    add: (data) => dispatch(add(data)),
    // sub操作的函数
    sub: (data) => dispatch(sub(data)),
  };
}

// 使用connect建并暴露一个Count的容器组件,传a,b函数的返回值给UI组件
export default connect(a, b)(CountUI);

connect 方法是一个连接器,用于连接容器组件和 UI 组件,它第一次执行时,接收4个参数,这些参数都是可选的,它执行的执行的结果还是返回一个函数,第二次执行接收一个 UI 组件

connect方法第一次执行时(connect())的四个参数:mapStateToPropsmapDispatchToPropsmergePropsoptions
connect方法第二次执行时传入的UI组件connect()(UI组件)

这里先说传state和传方法的两个函数
mapStateToProps函数返回的是一个对象(对象value是state),mapStateToProps用于传递状态
mapDispatchToProps函数返回的是一个对象(对象value是function),mapDispatchToProps用于传递操作状态的方法

这只是官方推荐的API定义方式,后面还有更简写的方式来传递state和function

官方解释:mapStateToProps,mapDispatchToProps

具体在UI获取时候的key,还是需要看return的对象把key定义成什么,才能用this.props来获取key


同时之前的定时加操作是在UI组件里面做的,并没有放在`count_action.js`里面,所以把定时加操作搬进`count_action.js`

优化之后:
Container.jsx:

import { connect } from "react-redux";
import CountUI from "../../components/count/CountUI";
import { add, sub, addAsync } from "../../redux/count_action";

// 传递state
function mapStateToProps(store) {
  return { key1: store };
}

// 传递dispatch
function mapDispatchToProps(dispatch) {
  // 允许传递多个函数   通过dispatch通知Redux执行函数
  return {
    // add操作的函数
    add: (data) => dispatch(add(data)),
    // sub操作的函数
    sub: (data) => dispatch(sub(data)),
    // addAsync操作的函数,addAsync操作搬进action里
    addAsync: (data, time) => dispatch(addAsync(data, time)),
  };
}

// 传入func mapStateToProps(负责state传递)  func mapDispatchToProps(负责函数动作传递)
// 最后传入CountTemplate组件完成连接
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);

ConuntTemplate.jsx:

import React, { Component } from "react";

export default class Count extends Component {
	...
  addAsync = () => {
    const { value } = this.selectNumber;
    this.props.addAsync(value * 1, 500);
  };

  render() {
    return (
      <div>
		...
      </div>
    );
  }
}

count_action.js:

/*
该文件专门为Count需要用的Reducer去生成action对象,封装好type,这样在调用组件传一个data进来就可以了
*/
import { ADD, SUB, ADD_ASYNC } from "./constant.js";

export function add(data) {
  // 要返回Action对象(返回type和data数据)
  return { type: ADD, data };
}

export function sub(data) {
  // 要返回Action对象(返回type和data数据)
  return { type: SUB, data };
}

// 把这个定时操作集成在action里面 => addAsync
// setTimeout(() => {
//   this.props.add(value * 1);
// }, 500);
export function addAsync(data, time) {
  // dispatch为自动传入
  return (dispatch) => {
    setTimeout(() => {
      // 通过dispatch调用add,直接调用是不可以的
      dispatch(add(data));
    }, time);
  };
}

其他的文件没啥变化,不赘述了

写到这里其实 connect 已经比较完善了,但是你可以仔细想想 redux 的工作流程

image-20210909194900532

似乎少了点什么,我们在这里调用了函数,创建了 action 对象,但是好像 store 并没有执行 dispatch ,那是不是断了呢?执行不了呢?

其实这里 react-redux 已经帮我们做了优化,当调用 Action Creator 的时候,会立即发送 actionstore 而不用手动的 dispatch。后面马上会用到这个。

总结React-Redux的基本使用

(1).明确两个概念:
			1).UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
			2).容器组件:负责和redux通信,将结果交给UI组件。
(2).如何创建一个容器组件————靠react-redux 的 connect函数
				connect(mapStateToProps,mapDispatchToProps)(UI组件)
					-mapStateToProps:映射状态,返回值是一个对象(对象以k:v形式保存state)
					-mapDispatchToProps:映射操作状态的方法,返回值是一个对象(对象以k:v形式保存多个方法)
(3).备注1:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
(4).备注2:mapDispatchToProps,也可以是一个对象(只传递一个方法,如果多个方法就需要多个k:v对象)

React-Redux优化

之前的案例,很多功能都是杂糅到一起的,如果体量大起来会让人不知道该怎么办,所以这里在这个案例里面对之前的功能代码进行拆分操作,分文件处理。优化文件结构。

前置知识

例子1:

 function mapStateToProps(store) {
   return { key1: store };
 }

可以用箭头函数来写

const mapStateToProps = (store)=> {
   return { key1: store };
 }

如果箭头函数只有一句并且默认return只有一个对象,就可以省略return,直接箭头指一个括号,就代表return值了 ({ key1: store })
最后优化完

const mapStateToProps = (store)=> ({ key1: store })

例子2:

function mapDispatchToProps(dispatch) {
  // 多个方法传递
  return {
    add: (data) => dispatch(add(data)),
    sub: (data) => dispatch(sub(data)),
    addAsync: (data, time) => dispatch(addAsync(data, time)),
  };
}

按照上面的说法,反正只有一个return,这里就可以直接优化成

mapDispatchToProps=(dispatch)=>{
    add: (data) => dispatch(add(data)),
    sub: (data) => dispatch(sub(data)),
    addAsync: (data, time) => dispatch(addAsync(data, time)),
  }

带入到connect里面,甚至不用写属性,直接把函数体丢进去即可

简写 mapStateToProps & mapDispatchToProps

对于connect来说,第一次调用connect(),传入什么名字的函数不重要,传入的位置很重要,connect只认位置上的传入的值。
简写完前后对比:

/*
// 传统写法
 function mapStateToProps(store) {
   return { key1: store };
 }
 function mapDispatchToProps(dispatch) {
   // 多个方法传递
   return {
     add: (data) => dispatch(add(data)),
     sub: (data) => dispatch(sub(data)),
     addAsync: (data, time) => dispatch(addAsync(data, time)),
   };
 }
*/

// 引入connect生成一个容器组件,连接好React-Redux和UI组件,并暴露
// UI组件中依旧是通过this.props.xxxxxxx读取和操作状态
// export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
export default connect(
  // 对于第一个参数位置来说,首次调用可以传四个参数[ mapStateToProps 、mapDispatchToProps 、mergeProps、options  ]
 //store没有React-Redux给你自动调用,所以这里要自己写
  (state) => ({ count: state }), // 等价于mapStateToProps 

  //mapDispatchToProps的简写(key:函数名),甚至不需要标注参数列表
  //dispatch有React-Redux给你自动调用,所以这里不用写了
  { add: add, sub: sub, addAsync: addAsync }//等价于mapDispatchToProps 
)(CountUI);

完整开发

目录结构:
在这里插入图片描述

首先就是把Contrainer和UI组件放在一起,合并成一个Container文件叫Count。虽然文件是在一起,但是功能和类都是分开的。只是文件放一起了,对外依旧暴露Container,UI这次彻底不用export了。

import { connect } from "react-redux";
import { add, sub, addAsync } from "../../redux/count_action";
import React, { Component } from "react";

// 这次CountUI彻底不用暴露了,暴露的任务交给connect来做。connect生成一个对外的Container,同时包含了数据和UI组件
// export default class Count extends Component {
class CountUI extends Component {
  add = () => {
    const { value } = this.selectNumber;
    this.props.add(value * 1);
  };
  sub = () => {
    const { value } = this.selectNumber;
    if (this.props.key1 === 0 || this.props.key1 < value) {
      return;
    }
    this.props.sub(value * 1);
  };
  addNotOdd = () => {
    const { value } = this.selectNumber;
    if (this.props.key1 % 2 !== 0) {
      this.props.add(value * 1);
    }
  };
  addAsync = () => {
    const { value } = this.selectNumber;
    this.props.addAsync(value * 1, 500);
  };

  render() {
    console.log(this.props);

    return (
      <div>
        <div>当前求和:{this.props.key1}</div>
        <select ref={(c) => (this.selectNumber = c)}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>
        <div>
          <button onClick={this.add}>add</button>
          <button onClick={this.sub}>sub</button>
          <button onClick={this.addNotOdd}>addNotOdd</button>
          <button onClick={this.addAsync}>addAsync</button>
        </div>
      </div>
    );
  }
}

/*
 function mapStateToProps(store) {
   return { key1: store };
 }
 function mapDispatchToProps(dispatch) {
   // 多个方法传递
   return {
     add: (data) => dispatch(add(data)),
     sub: (data) => dispatch(sub(data)),
     addAsync: (data, time) => dispatch(addAsync(data, time)),
   };
 }
*/

// 引入connect生成一个容器组件,连接好React-Redux和UI组件,并暴露
// UI组件中依旧是通过this.props.xxxxxxx读取和操作状态
// export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
export default connect(
  (state) => ({ key1: state }), // 状态
  { add: add, sub: sub, addAsync: addAsync } // 方法(不再需要注明参数列表)
)(CountUI);

Provider组件使用

首先看个之前的store传递,很麻烦,需要手动传递,如果每个标签都传递,都需要使用 store 时,很麻烦的

<Count store={store}/>
{/* 示例 */}
<Demo1 store={store}/>
<Demo1 store={store}/>
<Demo1 store={store}/>
<Demo1 store={store}/>
<Demo1 store={store}/>

所以官方就提供了一个大组件Provider,自动可以寻找需要用store的标签,自动精准传递store进去

我们可以这么做:在 src 目录下的 index.js 文件中,引入 Provider ,直接用 Provider 标签包裹 App 组件,将 store 写在 Provider 中即可

import { Provider } from "react-redux";

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

这样我们在 App.jsx 文件中,组件无需手写指定 store ,即可使用 store 非常方便

总结React-Redux优化

1.容器组件和UI组件整合一个文件

2.若有多个容器组件,无需自己给每个容器组件传递store,给包裹一个<Provider store={store}>即可。整体就贡献同一个store了。

3.使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。

4.mapDispatchToProps也可以简单的写成一个对象,因为react-redux可以自动dispatch

5.一个组件要和react-redux“打交道”要经过哪几步?

  • 1)定义好UI组件—不暴露
  • 2)引入connect生成一个容器组件,并暴露,写法如下:
connect(
	state => ({key:value}), //映射状态
	{key:xxxxxAction} //映射操作状态的方法
)(UI组件)
  • 3)在UI组件中通过this.props.定义好的key名 读取和操作状态

React-Redux综合案例(多组件组合共享)

引言

在写完了基本的 Redux 案例之后,我们可以尝试一些更实战性的操作,比如我们可以试试多组件间的状态传递,相互之间的交互

react-redux-demo

如上动图所示,我们想要实现上面的案例,采用纯 React 来实现是比较困难的,我们需要很多层的数据交换才能实现,但是我们如果采用 Redux 来实现会变得非常简单

因为 Redux 打通了组件间的隔阂,完成了复杂组件的数据交互,我们可以自由的进行数据交换,所有存放在 store 中的数据都可以实现共享,那我们接下来看看如何实现的吧~

整合UI组件与容器组件

如果UI组件和容器组件都分开写,那么实际上文件数量就得成倍增长,为了优化就整合一下两个文件,把UI组件和容器组件 放在一起。实际开发中也是整合的。

首先,一个jsx文件里面可以定义多个组件,其实思想上还是容器组件往UI组件传props,只不过两个组件放在了同一个文件里,对外只暴露一个Container接口。

这里演示一下两个或者多个组件在同一个文件里
创建一个Test组件
在这里插入图片描述

import React, { Component } from "react";

// 默认对外暴露的容器组件
export default class Test extends Component {
    render() {
        return <div>Test</div>;
    }
}

// UI组件,不对外暴露,但是可以在自己文件里面用
class Test2 extends Component {
    render() {
        return <div>UI组件</div>;
    }
}
// 他俩之间的连接靠connect
// 这是个简单的connect,后面有详细的描述
export default connect(
    (state) => ({对象}),
    {
        // action动作
    }
)(CountUI);

所以以后就可以只留一个Container文件夹放容器组件

UI组件只要想去拿Redux的状态,就直接找内部的connect,因为这个connect已经拿到了Redux的所有state
后面有详细的描述

重新安排Redux文件结构

如果有Person和Count的组件,每个组件对应的action和reducer这两个文件,都是成倍的增长,所以肯定不能这么一大堆罗列于此,就要把Redux文件夹分层
在这里插入图片描述
分层后的Redux,将action文件和reducer文件分开

就不用再叫 组件_action.js组件_reducer.js 的这种了,因为已经在action文件夹下面了,根据文件夹就能分辨出来
在这里插入图片描述

React-Redux结构 & 合并Reducer

React-Redux结构

Redux里用的存储结构:
如果是只存了一个值,比如数字这种,那就是不用key去取,直接就this.store即代表着当前对象。
在这里插入图片描述

如果是存了两个值以上,n个对象,那就是Redux作为一个大对象,里面存了n多个小对象,通过key来获取目标对象(key是合并reducer时定义的)
在这里插入图片描述

合并Reducer

之前的store中只注册了一个Reducer并导出,对于多个Reducer来说,只导出一个肯定是不行的,所以我们要将其他Reducer也注册到store里面,并且命名好key来帮助后续获取

这里提一个前置知识:combineReducers({对象})传入的对象,就是Redux保存的总对象


重要事情说三遍~
combineReducers({对象})传入的所有对象,就是Redux保存的总对象
combineReducers({对象})传入的所有对象,就是Redux保存的总对象
combineReducers({对象})传入的所有对象,就是Redux保存的总对象


合并后的store.js:

//引入store创建,以及中间件
import { combineReducers,createStore } from "redux";
//引入为store服务的Reducer
import countReducer from "../redux/reducer/count";
import personReducer from "../redux/reducer/person";

// 之前的store只能注册一个reducer,也就是count的countReducer
// 没法注册person的reducer,所以我们需要用函数把两个函数都注册了
// const store = createStore(countReducer, applyMiddleware(thunk));

// 合并
const allReducer = combineReducers({
    // sum就是countReducer的key,
    sum: countReducer,
    // persons就是personReducer的key,
    persons: personReducer,
});
//全局只暴露这个Reducers,同样需要用createStore函数创建
export default createStore(allReducer);

Container通过key获取对应的状态
Count组件的connect

// 使用connect建并暴露一个Count的容器组件,传a,b函数的返回值给UI组件
/*
    (state) => ({ sumNumber: state.sum })
    对应的含义:
    (默认传入的全局Redux对象) => ({ 自定义的在UI组件使用的名字: 默认传入的全局Redux对象.目标对象的key })
*/
export default connect(
    // 这里还获取了Person组件的人数长度(通过Redux获取其他组件的值)
    // 这里可以传入多个 k-v ,用逗号分隔
    (state) => ({ sumNumber: state.sum, personLength: state.persons.length }),
    {
        // action动作和以前一样传递即可
        add: add,
        sub: sub,
        addAsync: addAsync,
    }
)(CountUI);

Person组件的connect

/*
    (state) => ({ personList: state.persons }),
    对应的含义:
    (默认传入的全局Redux对象) => ({ 自定义的在UI组件使用的名字: 默认传入的全局Redux对象.目标对象的key }),
*/
export default connect(
    // 这里还获取了Count组件的求和值(通过Redux获取其他组件的值)
    // 这里可以传入多个 k-v ,用逗号分隔
    (state) => ({ personList: state.persons, countSum: state.sum }),
    {
        // 这里绑定的action该咋传咋传
        addPerson: addPerson,
    }
)(PersonUI);

整个案例代码

先看结构:
在这里插入图片描述
容器组件Count.jsx:

// 引入connect的API
import { connect } from "react-redux";
import { add, sub, addAsync } from "../../redux/action/count";

import React, { Component } from "react";

class CountUI extends Component {
    add = () => {
        const { value } = this.count;
        this.props.add(value);
    };
    sub = () => {
        const { value } = this.count;
        this.props.sub(value);
    };
    addNotOdd = () => {
        if (this.props.sumNumber % 2 !== 0) {
            this.props.add(value);
        }
    };
    addAsync = () => {
        const { value } = this.count;
        this.props.addAsync(value, 500);
    };

    render() {
        console.log(this.props);
        return (
            <div>
                <h1>
                    当前结果{this.props.sumNumber},下方组件总人数为
                    {this.props.personLength}
                </h1>
                选择内容
                <select ref={(c) => (this.count = c)}>
                    <option value={1}>1</option>
                    <option value={2}>2</option>
                    <option value={3}>3</option>
                </select>
                <button onClick={this.add}>+</button>
                <button onClick={this.sub}>-</button>
                <button onClick={this.addNotOdd}>当前奇数加</button>
                <button onClick={this.addAsync}>非同步加</button>
            </div>
        );
    }
}

// 使用connect建并暴露一个Count的容器组件,传a,b函数的返回值给UI组件
/*
    (state) => ({ sumNumber: state.sum })
    对应的含义:
    (默认传入的全局Redux对象) => ({ 自定义的在UI组件使用的名字: 默认传入的全局Redux对象.目标对象的key })
*/
export default connect(
    // 这里还获取了Person组件的人数长度(通过Redux获取其他组件的值)
    // 这里可以传入多个 k-v ,用逗号分隔
    (state) => ({ sumNumber: state.sum, personLength: state.persons.length }),
    {
        // action动作和以前一样传递即可
        add: add,
        sub: sub,
        addAsync: addAsync,
    }
)(CountUI);

容器组件Person.jsx:

import React, { Component } from "react";
import { addPerson } from "../../redux/action/person";
import { nanoid } from "nanoid";
import { connect } from "react-redux";

class PersonUI extends Component {
    addPerson = () => {
        const name = this.name.value;
        const age = this.age.value;
        if (name === "" || age === "") {
            return;
        }
        const newPeson = { id: nanoid(), name, age };

        this.props.addPerson(newPeson);
    };
    render() {
        return (
            <div>
                <h1>我是person组件,上方组件求和为:{this.props.countSum}</h1>
                <input
                    ref={(name) => {
                        this.name = name;
                    }}
                ></input>
                <input
                    ref={(age) => {
                        this.age = age;
                    }}
                ></input>
                <button onClick={this.addPerson}>add New Person</button>
                <ul>
                    {this.props.personList.map((p) => {
                        return (
                            <li key={p.id}>
                                name: {p.name}---- age: {p.age}
                            </li>
                        );
                    })}
                </ul>
            </div>
        );
    }
}

/*
    (state) => ({ personList: state.persons }),
    对应的含义:
    (默认传入的全局Redux对象) => ({ 自定义的在UI组件使用的名字: 默认传入的全局Redux对象.目标对象的key }),
*/
export default connect(
    // 这里还获取了Count组件的求和值(通过Redux获取其他组件的值)
    // 这里可以传入多个 k-v ,用逗号分隔
    (state) => ({ personList: state.persons, countSum: state.sum }),
    {
        // 这里绑定的action该咋传咋传
        addPerson: addPerson,
    }
)(PersonUI);

Action下的count.js:


import { ADD, SUB } from "../constant.js";
import store from "../store.js";

export const add = (data) => ({ type: ADD, data });
export const sub = (data) => ({ type: SUB, data });

// 异步action,就是只action的值为函数,在异步action中,一般都会调用同步action,异步action不是必须要用的
// 能在这里写函数,是因为在store做了redux-thunk的设置
export const addAsync = (data, time) => {
    return () => {
        // 这里其实只套了一个定时操作
        setTimeout(() => {
            // 调用已经定义好的增加action
            store.dispatch(add(data));
        }, time);
    };
};

Action下的person.js:

import { ADD_PERSON } from "../constant";

export const addPerson = (data) => ({ type: ADD_PERSON, data });

reducer下的count.js:

import { ADD, SUB } from "../constant.js";

export default function countReducer(previousState, action) {
    // 从Action对象里传来的action对象,里面包含type和data,所以我们需要解构出来。等待后续处理
    const { type, data } = action;
    if (previousState === undefined) {
        return 0;
    }

    // 判断传入功能类型
    switch (type) {
        case ADD:
            //这里用原来的值+新传入的值,得到新值
            return previousState + data * 1;
        case SUB:
            if (previousState < data * 1) {
                return previousState;
            }
            //这里用原来的值-新传入的值,得到新值
            return previousState - data * 1;
        // 提一嘴,这里都是return,所以不用break
        default:
            return previousState;
    }
}

reducer下的person.js:

import { ADD_PERSON } from "../constant.js";

const initPersonList = [{ id: "123456", name: "Tom", age: "20" }];

export default function personReducer(previousState = initPersonList, action) {
    const { type, data } = action;
    switch (type) {
        case ADD_PERSON:
            // 把之前的展开(之前的数组是没展开的),把新来的加进去
            return [data, ...previousState];
        default:
            return previousState;
    }
}

常量constant.js

export const ADD = "add";
export const SUB = "sub";
export const ADD_PERSON = "addPerson";

store.js

//引入store创建,以及中间件
import { applyMiddleware, combineReducers, createStore } from "redux";
//引入为store服务的Reducer
import countReducer from "../redux/reducer/count";
import personReducer from "../redux/reducer/person";
import thunk from "redux-thunk";

// 之前的store只能注册一个reducer,也就是count的countReducer
// 没法注册person的reducer,所以我们需要用函数把两个函数都注册了
// const store = createStore(countReducer, applyMiddleware(thunk));

// 合并
const allReducer = combineReducers({
    // sum就是countReducer的key,
    sum: countReducer,
    // persons就是personReducer的key,
    persons: personReducer,
});

// 全局只暴露这个Reducers,同样需要用createStore函数创建
// 同时允许接收函数的applyMiddleware(thunk)也不能丢了
export default createStore(allReducer, applyMiddleware(thunk));

App.jsx

class App extends React.Component {
    render() {
        return (
            <div>
                <Provider store={store}>
                    {/* 用Provider组件的props传递store进入Container组件 */}
                    // 被Provider组件包裹的子组件都能自动接收到store
                    // 这俩都是容器组件
                    <Count></Count>
                    <Person></Person>
                </Provider>
            </div>
        );
    }
}
export default App;

发现个小bug:
在这里插入图片描述
原因是没有弄redux-thunk做中间件支持,再详细可以看这个:redux-thunk

总结React-Redux综合案例

(1).定义一个Pserson组件,和Count组件通过redux共享数据。
(2).为Person组件编写:reducer、action,配置constant常量。
(3).重点:Person的reducer和Count的Reducer要使用combineReducers进行合并,
		合并后的总状态是一个对象!!!
(4).交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”。

纯函数概念

就是一个概念,没有编码

引言

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

2.必须遵守以下一些约束

  • 1)不得改写参数数据
  • 2)不会产生任何副作用,例如网络请求,输入和输出设备
  • 3)纯函数里面不能调用Date.now()或者Math.random()等不纯的方法

3.redux的reducer函数必须是一个纯函数

例子

纯函数要求输入和输出相同
比如:

// a是纯函数
function a(data) {
    return data;
}
// b不是纯函数
function b(data) {
    let c = 1;
    return c;
}

再拿一个reducer举个例子:
这里的 return [data, ...previousState];。因为地址引用发生了变化,所以就能触发页面更新。因为Redux是浅比较,不比较对象内容,发现return的引用地址变化了就自动更新了。

export default function personReducer(previousState = initPersonList, action) {
    const { type, data } = action;
    switch (type) {
        case ADD_PERSON:
            // 把之前的展开(之前的数组是没展开的),把新来的加进去
            return [data, ...previousState];
        default:
            return previousState;
    }
}

如果是非纯函数,比如这样。这就无法生效,因为不是纯函数

export default function personReducer(previousState = initPersonList, action) {
    const { type, data } = action;
switch (type) {
        case ADD_PERSON:
            var newArray = previousState;
            newArray.push(data);
            return newArray;
        default:
            return previousState;
    }
}

Redux-DevTools

顾名思义,开发者工具

需要插件+npm库的依赖

 npm install redux-devtools-extension --legacy-peer-deps

在这里插入图片描述

store需要导入redux-devtools-extension的依赖,并且在暴露store的connect函数的第二个参数位置传入对应的api
如果之前有applyMiddleware(thunk)的中间件操作,可以选择将中间件传入其api

...省略
// redux-devtools api
import { composeWithDevTools } from "redux-devtools-extension";

// 合并
const allReducer = combineReducers({
    // sum就是countReducer的key,
    sum: countReducer,
    // persons就是personReducer的key,
    persons: personReducer,
});

export default createStore(
    allReducer,
    // redux-devtools api
    composeWithDevTools(applyMiddleware(thunk))
);

再次启动项目,就可以在浏览器的插件里面看redux存进去的东西了

在这里插入图片描述

总结工具使用

(1).yarn add redux-devtools-extension
(2).store中进行配置
		import {composeWithDevTools} from 'redux-devtools-extension'
		const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

Reducers优化

之前store中,所有的Reducers都是积压在store中进行的合并,如果是大型项目将难以维护,所以我们一般把Reducers的合并,放在一个单独的js文件中去做,完成导出,导出完毕之后在store中引入即可
在这里插入图片描述
在reducer下专门新建一个index文件,完成汇总合并操作

/*
    该文件用于汇总所有的reducer为一个总的reducer
*/
import { combineReducers } from "redux";

//引入为store服务的Reducer
import countReducer from "./count";
import personReducer from "./person";
// 合并
const allReducer = combineReducers({
    // sum就是countReducer的key,
    sum: countReducer,
    // persons就是personReducer的key,
    persons: personReducer,
});

// 导出
export default allReducer;

精简之后的store文件,非常清爽

//引入store创建,以及中间件
import { applyMiddleware, createStore } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";

// 引入reducers
import allReducer from "./reducer";

export default createStore(
    allReducer,
    composeWithDevTools(applyMiddleware(thunk))
);

总结:

(1).所有变量名字要规范,尽量触发对象的简写形式。
(2).reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/233864.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

kali linux使用Proxmark3

其实kali linux下已经集成了Proxmark3命令&#xff0c;但是由于Proxmark3是开源设备&#xff0c;有时候系统默认安装的版本并不能很好的使用&#xff0c;因此需要手动编译最新的版本。 step 1 准备Proxmark3编译环境&#xff0c;因为kali linux比较激进&#xff0c;很多老旧的…

【EI会议征稿中】第三届信号处理与通信安全国际学术会议(ICSPCS 2024)

第三届信号处理与通信安全国际学术会议&#xff08;ICSPCS 2024&#xff09; 2024 3rd International Conference on Signal Processing and Communication Security 信号处理和通信安全是现代信息技术应用的重要领域&#xff0c;近年来这两个领域的研究相互交叉促进&#xf…

基于YOLOv7算法的高精度实时海上船只目标检测识别系统(PyTorch+Pyside6+YOLOv7)

摘要&#xff1a;基于YOLOv7算法的高精度实时海上船只目标检测系统可用于日常生活中检测与定位海上船只目标&#xff0c;此系统可完成对输入图片、视频、文件夹以及摄像头方式的目标检测与识别&#xff0c;同时本系统还支持检测结果可视化与导出。本系统采用YOLOv7目标检测算法…

配置BFD多跳检测示例

BFD简介 定义 双向转发检测BFD&#xff08;Bidirectional Forwarding Detection&#xff09;是一种全网统一的检测机制&#xff0c;用于快速检测、监控网络中链路或者IP路由的转发连通状况。 目的 为了减小设备故障对业务的影响&#xff0c;提高网络的可靠性&#xff0c;网…

【亲测有效】支持横竖屏 微信小程序video禁止进度条拖动,微信小程序遮罩进度条,

背景&#xff1a;部分课程禁止客户拖动视频进度条直至播放结束 红色是遮罩区域遮罩区域 实际遮罩效果&#xff08;有一个很浅的阴影区域&#xff09; 实现代码 .wxml文件 <video enable-progress-gesture"false" ><cover-view class"cover">…

JAVA全栈开发 day18MySql03

一、复习 为什么要用数据库数据库好处数据库的发展史​ 层次模型​ 网状模型​ 关系模型&#xff08;二维表专门存储数据&#xff0c; 表与表的关联&#xff09;​ 表与表的关系&#xff1a; 1对1 &#xff0c;1对多&#xff0c;多对多​ 非关系模型关系模…

Linux常见压缩指令小结

为什么需要压缩技术 我们都知道文件是以byte作为单位的&#xff0c;如果我们的文件仅仅在低位占一个1 0000 0001这种情况我们完全可以压缩一下&#xff0c;将高位的0全部抹掉即可。 如上所说是一种压缩技术&#xff0c;还有一种就是将1111(此处省略96个)一共100个1&#xff0…

Unity中Shader黑白阀值后处理效果

文章目录 前言一、我们先来PS看一下黑白阀值的效果二、使用step(a,b)函数实现效果三、实现脚本控制黑白阀值1、在Shader属性面板定义控制阀值变量2、把step的a改为_Value3、在后处理脚本设置公共成员变量,并且设置范围为&#xff08;0&#xff0c;1&#xff09;4、在Graphics.B…

vulnhub靶机Prime-2

下载地址&#xff1a;Prime (2021): 2 ~ VulnHub 主机发现 目标145 端口扫描 端口服务扫描 漏洞扫描 先去看一下80 扫目录 发现点不一样的 Wordpress&#xff08;记住还是要用api&#xff09; 好洞出来了看看利用方法 192.168.21.145/wp/wp-content/plugins/gracemedia-media…

【刷题篇】动态规划(六)

文章目录 1、最大子数组和2、环形子数组的最大和3、乘积最大子数组4、乘积为正数的最长子数组长度5、 等差数列划分6、最长湍流子数组 1、最大子数组和 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&…

Proteus仿真--基于12864的自制硬件汉字库的应用

本文介绍基于12864的自制硬件汉字库的应用设计&#xff08;完整仿真源文件及代码见文末链接&#xff09; 其中12864LCD中显示的汉字是存储在2块AT24C1024芯片中 仿真图如下 仿真运行视频 Proteus仿真--基于12864的自制硬件字库的应用 附完整Proteus仿真资料代码资料 链接&am…

陀螺仪LSM6DSV16X与AI集成(4)----Qvar触摸电容配置

陀螺仪LSM6DSV16X与AI集成.4--Qvar触摸电容配置 概述视频教学样品申请源码下载生成STM32CUBEMX串口配置IIC配置CS和SA0设置串口重定向参考程序初始换管脚获取ID复位操作BDU设置Qvar 功能的实现和配置设置量程和速率配置过滤链激活 Qvar 功能获取Qvar数据演示 概述 Qvar&#x…

onnxruntime和tensorrt多batch推理

以lenet网络为例。 onnxruntime多batch推理 当batch size为2时&#xff0c;导出如下结构的onnx文件&#xff1a; python推理&#xff1a; import cv2 import numpy as np import onnxruntimeimg0 cv2.imread("2.png", 0) img1 cv2.imread("10.png", …

【MATLAB】基于EEMD分解的信号去噪算法(基础版)

代码操作 【MATLAB】基于EEMD分解的信号去噪算法&#xff08;基础版&#xff09; 代码的主要内容 基于EEMD&#xff08;集合经验模态分解&#xff09;的信号去噪算法通常可以结合相关系数、信号的熵值或者方差贡献率来完成去噪处理。这些指标可以用于确定阈值&#xff0c;从而…

Android:java.lang.RuntimeException: Unable to start activity ComponentInfo

java.lang.RuntimeException: Unable to start activity ComponentInfo 报错描述&#xff1a; 在导入别人项目运行时出现了这个报错&#xff1a; java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.news/com.example.activity.DetailNews}: ja…

SpringMVC修炼之旅(3)REST风格与拦截器

一、概述 1.1简介 Restful就是一个资源定位及资源操作的风格。不是标准也不是协议&#xff0c;只是一种风格。基于这个风格设计的软件可以更简洁&#xff0c;更有层次&#xff0c;更易于实现缓存等机制。 1.2功能 资源&#xff1a;互联网所有的事物都可以被抽象为资源 资源操作…

C++之获取变量信息名称、类型typeid

摘要 对于C工程量级比较庞大的代码&#xff0c;代码中的变量、类、函数、结构体的识别都是一件让人头疼的事情&#xff0c;一方面代码越写越多&#xff0c;内容越来越丰富&#xff0c;但是没有办法对已有的代码框架进行高度的整合提炼&#xff1b;另一方面对新人逐渐不友好&am…

C++笔记之通过静态类成员变量的方式在不同的类之间传递参数

C笔记之通过静态类成员变量的方式在不同的类之间传递参数 code review! 在C中&#xff0c;可以使用静态类成员变量作为一种在不同类之间传递参数的方式。静态类成员变量是类的所有对象之间共享的变量&#xff0c;它们存在于类的内部&#xff0c;但不属于任何特定的类对象。 …

Git—文件添加查看删除修改

目录 1.添加文件—场景一 2.查看.git文件 3.添加文件—场景三 4.修改文件 5.版本回退 6.撤销修改 7.删除文件 1.添加文件—场景一 在包含.git的目录下新建⼀个ReadMe文件&#xff0c;我们可以使用 git add 命令可以将文件添加到暂存 区&#xff1a; ●添加一个或多个文…

安卓拍照扫描APP解决方案——基于深度学习与NDK实现文档图像版面检测与分析

一、概述 文档版面分析是针对图片或页面扫描图像上感兴趣的区域进行定位和分类的过程。其主要目标在于让机器能够理解文档结构&#xff0c;即将文档图像划分为不同类型内容的区域&#xff0c;并分析这些区域之间的关系。这是进行内容识别之前的关键步骤&#xff0c;它通常可以…
最新文章