06-React组件 & Redux & React-Redux

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

组件化编程,只需要去安装好对应的组件,然后通过各式各样的组件引入,实现快速开发

我们这里学习的是 Ant-design (应该是这样),它有很多的组件供我们使用

引入各种组件,可以方便开发,省着自己去二次封装组件,同时也更好看了

快速起步

安装antd组件库

javascript 复制代码
npm install antd

随便一个组件里面引入

javascript 复制代码
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,导出供外界使用

javascript 复制代码
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中获取结果即可!

javascript 复制代码
/*
    作为一个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文件:

调用方组件:

javascript 复制代码
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文件:

javascript 复制代码
/*
    该文件专门为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,避免了容易拼错的问题

javascript 复制代码
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文件:

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

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

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

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

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

调用方组件Count:

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

action组件:

javascript 复制代码
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

javascript 复制代码
//引入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(没有变化):

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

action组件:

javascript 复制代码
// 异步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

javascript 复制代码
// 引入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

javascript 复制代码
"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

javascript 复制代码
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仍然报找不到的错(这里是错误示范)

javascript 复制代码
//引入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的组件)。这样就不报错了

javascript 复制代码
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之间没有直接监听更新的手段。所以这里需要手动去监听渲染组件

javascript 复制代码
// 引入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就自动集成了监听并更新的功能,所以我们不必再手动监听。删掉即可,测试完美替换,不影响功能。

javascript 复制代码
// 引入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传值

javascript 复制代码
//引入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组件

javascript 复制代码
//引入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传值的时候有点小坑,我们应该专注于状态的传递。所以优化一下

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

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

javascript 复制代码
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

改造后:

javascript 复制代码
//这个是自定义的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

javascript 复制代码
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

javascript 复制代码
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

javascript 复制代码
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

javascript 复制代码
// 引入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,就不需要我们再去单独定义了。更加规范了。

javascript 复制代码
// 函数命名不规范
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

官方解释:mapStateToPropsmapDispatchToProps

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


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

优化之后:
Container.jsx:

javascript 复制代码
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:

javascript 复制代码
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:

javascript 复制代码
/*
该文件专门为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 的工作流程

似乎少了点什么,我们在这里调用了函数,创建了 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:

javascript 复制代码
 function mapStateToProps(store) {
   return { key1: store };
 }

可以用箭头函数来写

javascript 复制代码
const mapStateToProps = (store)=> {
   return { key1: store };
 }

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

最后优化完

javascript 复制代码
const mapStateToProps = (store)=> ({ key1: store })

例子2:

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

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

javascript 复制代码
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只认位置上的传入的值。

简写完前后对比:

javascript 复制代码
/*
// 传统写法
 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了。

javascript 复制代码
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 时,很麻烦的

js 复制代码
<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 中即可

js 复制代码
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生成一个容器组件,并暴露,写法如下:
javascript 复制代码
connect(
	state => ({key:value}), //映射状态
	{key:xxxxxAction} //映射操作状态的方法
)(UI组件)
  • 3)在UI组件中通过this.props.定义好的key名 读取和操作状态

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

引言

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

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

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

整合UI组件与容器组件

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

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

这里演示一下两个或者多个组件在同一个文件里

创建一个Test组件

javascript 复制代码
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:

javascript 复制代码
//引入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

javascript 复制代码
// 使用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

javascript 复制代码
/*
    (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:

javascript 复制代码
// 引入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:

javascript 复制代码
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:

javascript 复制代码
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:

javascript 复制代码
import { ADD_PERSON } from "../constant";

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

reducer下的count.js:

javascript 复制代码
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:

javascript 复制代码
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

javascript 复制代码
export const ADD = "add";
export const SUB = "sub";
export const ADD_PERSON = "addPerson";

store.js

javascript 复制代码
//引入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

javascript 复制代码
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函数必须是一个纯函数

例子

纯函数要求输入和输出相同

比如:

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

再拿一个reducer举个例子:

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

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

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

javascript 复制代码
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库的依赖

javascript 复制代码
 npm install redux-devtools-extension --legacy-peer-deps

store需要导入redux-devtools-extension的依赖,并且在暴露store的connect函数的第二个参数位置传入对应的api

如果之前有applyMiddleware(thunk)的中间件操作,可以选择将中间件传入其api

javascript 复制代码
...省略
// 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存进去的东西了

总结工具使用

javascript 复制代码
(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文件,完成汇总合并操作

javascript 复制代码
/*
    该文件用于汇总所有的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文件,非常清爽

javascript 复制代码
//引入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))
);

总结:

javascript 复制代码
(1).所有变量名字要规范,尽量触发对象的简写形式。
(2).reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer
相关推荐
Mr_Xuhhh12 分钟前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
永乐春秋1 小时前
WEB攻防-通用漏洞&文件上传&js验证&mime&user.ini&语言特性
前端
鸽鸽程序猿1 小时前
【前端】CSS
前端·css
ggdpzhk1 小时前
VUE:基于MVVN的前端js框架
前端·javascript·vue.js
学不会•3 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
活宝小娜6 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点6 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow6 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o6 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic7 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端