react-组件通信(redux)

组件通信

父子组件通信

vue中父子组件通信,父传子:props,子组件通知父组件:this.$emit

在react中,父子组件通信

父传子:使用props属性传递

子组件通知父组件修改数据可以调用父组件传递的函数去执行修改。

父组件App.js

import React, { Component } from 'react'
import Child from './components/Child'

export default class App extends Component {

  state = {
    username: '张三'
  }

  updateName = (name) => {
    this.setState({
      username: name
    })
  }

  render() {
    return (
      <div>
        <Child username={this.state.username} updateName={this.updateName}></Child>
      </div>
    )
  }
}

子组件Child.jsx

import React, { Component } from 'react'

export default class Child extends Component {

    changeName = () => {
        this.props.updateName('王五');
    }

  render() {
    return (
      <div>
        <p>child子组件</p>
        姓名:{this.props.username}
        <button onClick={this.changeName}>修改姓名</button>
      </div>
    )
  }
}

兄弟组件通信

通过将父组件作为中转站去通知兄弟组件修改数据,达到兄弟组件通信。

父组件App.jsx

import React, { Component } from 'react'
import Child from './components/Child'
import ChildTwo from './components/ChildTwo'

export default class App extends Component {

  state = {
    username: '张三'
  }

  childRef = React.createRef()

  updateName = (name) => {
    this.setState({
      username: name
    })
  }

  changeChildAge = (age) => {
    this.childRef.current.changeAge(age);
  }

  render() {
    return (
      <div>
        <Child ref={this.childRef} username={this.state.username} updateName={this.updateName}></Child>
        <ChildTwo changeChildAge={this.changeChildAge}></ChildTwo>
      </div>
    )
  }
}

子组件Child.jsx

import React, { Component } from 'react'

export default class Child extends Component {

    state = {
        age: 20
    }

    changeAge = (age) => {
        this.setState({
            age
        })
    }

    changeName = () => {
        this.props.updateName('王五');
    }

  render() {
    return (
      <div>
        <p>child子组件</p>
        姓名:{this.props.username}
        <br />
        年龄:{this.state.age}
        <button onClick={this.changeName}>修改姓名</button>
      </div>
    )
  }
}

子组件ChildTwo.jsx

import React, { Component } from 'react'

export default class ChildTwo extends Component {


    changeChildAge = () => {
        this.props.changeChildAge(30);
    }

  render() {
    return (
      <div>
        <p>ChildTwo</p>
        <button onClick={this.changeChildAge}>修改child中的名字</button>
    </div>
    )
  }
}

这种方式可以实现兄弟组件通信,但是如果组件嵌套太深,导致通信需要一层一层网上传,然后一层一层往下传递,这个过程非常繁琐,以后维护不方便。

基于事件总线的通信

事件总线的方式eventBus,是一种设计模式,叫做订阅发布,设计模式有23种,他们都是一种思想,是前辈们总结出来的一种经验和思想,能够帮助我们快速解决问题,比如:单例模式、策略模式、订阅发布等等。

react中本身没有提供发布订阅模式,我们需要安装第三方插件events

events API

  • 监听事件:addListener(‘事件名称’,执行函数)
  • 触发事件:emit(‘事件名称’,参数)
  • 移除事件:removeListener(‘事件名称’, 执行函数)
  1. 安装events插件

    npm i events
    # or
    yarn add events
    
  2. 创建eventBus文件

    utils/eventBus.js

    import { EventEmitter } from 'events';
    
    export default new EventEmitter(); // 生成一个eventBus
    
  3. 监听事件

    Child.jsx

    import React, { Component } from 'react'
    import eventBus from '../utils/eventBus'
    
    export default class Child extends Component {
        state = {
            age: 20
        }
        componentDidMount() {
            eventBus.addListener('changeAge', this.changeAge)
        }
        changeAge = (age) => {
            console.log(age)
            this.setState({
                age
            })
        }
    }
    
    
  4. 触发事件

    ChildTwo.jsx

    import React, { Component } from 'react'
    import eventBus from '../utils/eventBus'
    
    export default class ChildTwo extends Component {
    
    
        changeChildAge = () => {
            eventBus.emit('changeAge', 18)
        }
    
      render() {
        return (
          <div>
            <p>ChildTwo</p>
            <button onClick={this.changeChildAge}>修改child中的名字</button>
        </div>
        )
      }
    }
    
    
  5. 移除事件

    Child.jsx

    import React, { Component } from 'react'
    import eventBus from '../utils/eventBus'
    
    export default class Child extends Component {
        componentWillUnmount() {
            eventBus.removeListener('changeAge', this.changeAge)
        }
    }
    
    

事件总线的方式能让组件之间直接通信,但是存在问题:

当项目中大面积使用事件总线,监听和触发事件非常分散,导致以后维护不好维护,不知道哪里在监听哪里在触发。

建议:事件总线可以少用,不要大面积使用。

最好使用状态管理库,在react中通常使用的是redux。

redux三大核心概念

react本身没有提供相关的仓库管理插件,react社区非常庞大,社区提供了一个仓库管理插件redux,相当于vuex可以对数据进行管理。

在之前将的是redux@reduxjs/toolkit,它包括了redux的用法。

为什么redux官网推荐使用@reduxjs/toolkit,其中原因之一是因为redux在使用时非常繁琐,需要对文件进行拆分以及合并。

市面上关于状态管理的库,比如vuex(借鉴了redux的思想,根据vue语法特点去进行开发,方便vue使用者去使用)、redux(任何框架都可以使用,所以使用起来不是那么舒服)、mbox(语法采用装饰器语法)、zustand、@reduxjs/toolkit(redux官方推荐使用)。

这是用了redux和不同redux的区别:

Snipaste_2021-09-29_21-23-12

vuex图解

Snipaste_2021-09-29_14-28-12

redux图解

timg-2

store:redux仓库数据存放的地方,一般只会存在一个store仓库

action:作为动作描述期,是一个对象,描述了这个动作是干什么的,通知reducer去更新数据

reducer:相当于vuex中的mutations,去更新仓库数据的一个函数或对象。

store

redux仓库数据存放的地方,一般只会存在一个store仓库

  1. 安装@reduxjs/toolkit

    npm i @reduxjs/toolkit
    # or
    yarn add @reduxjs/toolkit
    
  2. 新建store/index.js文件

    创建仓库

    import { configureStore } from '@reduxjs/toolkit';
    
    let store = configureStore({
        reducer() {
    
        }
    });
    
    console.log('store', store);
    
  3. 在index.js文件中引入仓库使用

    import './store'
    

action

作为动作描述期,是一个对象,必须传递一个type属性,代表这个动作是什么,其他参数可以任意传递,通常其他参数会放在payload这个属性中,描述了这个动作是干什么的,通知reducer去更新数据

可以通过disaptch派发action通知reducer更新数据

定义action

const action = {
    type: 'add', // 动作类型
    num: 10
}

派发action通知reducer更新数据

store.dispatch(action);

reducer中会根据type属性去匹配执行相关操作。

reducer

相当于vuex中的mutations,去更新仓库数据的一个函数或对象。初始化数据。

初始化数据

import { configureStore } from '@reduxjs/toolkit';

let store = configureStore({
    reducer(state = 0, action) {
        console.log('reducer触发了', action);

        switch (action.type) {
            case 'add':
                state += action.num;
                return state;
            case 'reduce':
                state -= action.num
                return state;
        
            default:
                return state;
        }
    }
});

console.log('store', store);
console.log('getState', store.getState());

const action = {
    type: 'add', // 动作类型
    num: 10
}
store.dispatch(action);

console.log('add', store.getState());

const reduceAction = {
    type: 'reduce',
    num: 1
}
store.dispatch(reduceAction)

console.log('reduce', store.getState());


在reducer函数中第一个参数去定义数据,达到初始化数据的效果,然后返回这个state数据。

redux对象特性

@reduxjs/toolkit中,像原来一样去使用redux仓数据时,修改数据需要返回一个新的对象,目前不能直接修改state中的值,否则会有错误提示。

store/index.js

import { configureStore } from '@reduxjs/toolkit';

let store = configureStore({
    reducer(state = {count: 0}, action) {
        let count = state.count
        switch (action.type) {
            case 'add':
                // state.count += action.num
                count += action.num;
                return {
                    count
                };
            case 'reduce':
                // state -= action.num
                count -= action.num
                return {
                    count
                };
        
            default:
                return state;
        }
    }
});

export default store;

坑一:返回state数据不完整导致数据丢失

当state对象中有多个数据时,修改其中某些数据后,需要将全部数据返回,否则会导致数据丢失

import { configureStore } from '@reduxjs/toolkit';

let store = configureStore({
    reducer(state = {count: 0, name: '阿旺', age: 18}, action) {
        let count = state.count
        switch (action.type) {
            case 'add':
                count += action.num;
                return {
                    count
                };
            case 'reduce':
                count -= action.num
                return {
                    count
                };
        
            default:
                return state;
        }
    }
});

export default store;

此时,更新数据后,会导致nameage属性丢失,因为返回时只返回了count值。

正确处理方式:

将整个state对象拷贝一份,用修改后的数据覆盖掉原来的数据,数据不会丢失

import { configureStore } from '@reduxjs/toolkit';

let store = configureStore({
    reducer(state = {count: 0, name: '阿旺', age: 18}, action) {
        let count = state.count
        switch (action.type) {
            case 'add':
                count += action.num;
                return {
                    ...state,
                    count
                };
            case 'reduce':
                count -= action.num
                return {
                    ...state,
                    count
                };
        
            default:
                return state;
        }
    }
});

export default store;
坑二:如果state中嵌套对象,修改这个对象后,需要返回一个新的对象地址,否则不能引起组件渲染更新

错误案例:

import { configureStore } from '@reduxjs/toolkit';

let store = configureStore({
    reducer(state = {count: 0, name: '阿旺', age: 18, user: {address: '上海'}}, action) {
        let user = state.user;
        switch (action.type) {
            case 'changeAdress':
                user.address = '静安区';
                return {
                    ...state,
                    user
                }
            default:
                return state;
        }
    }
});

export default store;

此时修改user地址后,不会引起组件渲染,因为user对象是地址引用,不是一个新的对象。

正确做法:

将user对象深拷贝:

1)结构扩展(不是深拷, 对一层对象有效)

import { configureStore } from '@reduxjs/toolkit';

let store = configureStore({
    reducer(state = {count: 0, name: '阿旺', age: 18, user: {address: '北京'}}, action) {
        let user = state.user;
        switch (action.type) {
            case 'changeAdress':
                user.address = '朝阳区';
                return {
                    ...state,
                    user: {
                        ...user
                    }
                }
            default:
                return state;
        }
    }
});

export default store;

2)JSON.parseJSON.stringify深拷、或者递归实现

import { configureStore } from '@reduxjs/toolkit';

let store = configureStore({
    reducer(state = {count: 0, name: '阿旺', age: 18, user: {address: '广州'}}, action) {
        let user = state.user;
        switch (action.type) {
            case 'changeAdress':
                user.address = '天河区';
                return {
                    ...state,
                    user: JSON.parse(JSON.stringify(user))
                }
            default:
                return state;
        }
    }
});

export default store;

3)一开就深拷对象

import { configureStore } from '@reduxjs/toolkit';

let store = configureStore({
    reducer(state = {count: 0, name: '阿旺', age: 18, user: {address: '深圳'}}, action) {
        // 一开就深拷对象
        let user = {...state.user};
        switch (action.type) {
            case 'changeAdress':
                console.log('changeAdress触发了');
                user.address = '南山';
                return {
                    ...state,
                    user
                }
            default:
                return state;
        }
    }
});

export default store;

重点:state必须是一个完全的新的地址对象,才能引起组件的渲染更新。

createSlice模块化

createSlice是@reduxjs/toolkit提供的一个函数,通过它可以创建模块,相当于vuex中的modules子模块功能。

初始化

1)新建store仓库

store/index.js

import { configureStore } from '@reduxjs/toolkit';

let store = configureStore({
    reducer() {
        
    }
});

export default store;
2)通过createSlice创建模块

新建counter.js文件

store/counter.js

import { createSlice } from '@reduxjs/toolkit';

// 创建模块
let counterSlice = createSlice({
    name: 'counter', // 相当于vue子模块的命名空间
    initialState: { // 初始化数据
        count: 0
    },
    reducers: { // 相当于vuex中的mutations对象,用于同步修改数据 

    }
});

export default counterSlice;

createSlice:创建模块后,会生成一个模块,这个模块包含reduceraction creator创建器

name:相当于vue子模块的命名空间,这个名字必须是唯一的

initialState:初始化数据

reducers:相当于vuex中的mutations对象,用于同步修改数据

3)绑定reducer

在store/index.js中绑定reducer初始化数据

import { configureStore } from '@reduxjs/toolkit';
import counterSlice from './counter';

let store = configureStore({
    reducer: {
        counterRd: counterSlice.reducer
    }
});

export default store;

reducer:是一个对象,对象中的键就是划分模块,在访问数据时,需要安装模块名去访问数据

4)通过Provider绑定store仓库

react的组件是UI组件,如果UI组件要使用redux的数据,需要变成容器组件

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
// react的组件是UI组件,如果UI组件要使用redux的数据,需要变成容器组件
import { Provider } from 'react-redux'
import store from './store';

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

将store仓库数据传入到Provider中,后续的组件就有资格获取到redux的仓库数据。

5)通过connect高阶组件去获取redux中的数据
function mapStateToProps(state) {
  console.log('mapStateToProps', state);
  return {
    count: state.counterRd.count
  }
}

export default connect(mapStateToProps)(App);

mapStateToProps中访问数据时,需要注意通过模块划分时的key去访问数据。

修改数据

1)编写reducer
import { createSlice } from '@reduxjs/toolkit';

// 创建模块
let counterSlice = createSlice({
    name: 'counter', // 相当于vue子模块的命名空间
    initialState: { // 初始化数据
        count: 0
    },
    reducers: { // 相当于vuex中的mutations对象,用于同步修改数据 
        addCountRd(state, action) {
            // 这里的state数据proxy代理了
            console.log(state, action);
            state.count += action.num;
        }
    }
});

export default counterSlice;

注意:reducer中的state陪proxy代理过,直接修改state中的数据即可引起页面的渲染更新。

2)派发action通知reducer更新数据

派发action有两种方式:

  1. 派发一个对象

    this.props.dispatch({
      type: 'counter/addCountRd',
      num: 10
    });
    

    type:是一个字符串,有两部分组成,第一部分是createSlice创建模块时的命名空间name的值,第二部分是对应的reducer函数的名称。

  2. 通过派发模块生成的action creator创建器函数去生成action对象

    this.props.dispatch(counterSlice.actions.addCountRd(10));
    

    counterSlice.actions.addCountRd(10):就是模块化后生成的action creator创建器函数,运行后会返回一个action对象,对象中默认使用payload作为参数载体来传递参数。

createAsyncThunk异步处理

目前学习的仓库知识中,要处理异步数据,可能会考虑这样写:

componentDidMount() {
    getUserList().then(res => {
		this.props.dispatch({})
    })
}

这样写可以实现功能,但是不具备封装性,@reduxjs/toolkit提供了一种方式让我们去封装异步的代码。

createAsyncThunk异步处理

类似于vuex中的actions封装异步请求。

Thunk: 是一个插件,做异步处理来获取数据,目前@reduxjs/toolkit集成了这个异步处理插件,我们直接用就可以了。

1)安装axios
npm i axios
2)封装api接口
import axios from "axios";

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50IjoieGlhb2ZlaSIsInJvbGUiOnsibWVudXMiOlsiL2hvbWUiLCIvdXNlciIsIi9yb2xlIiwiL3Byb2R1Y3QiLCIvcHJvZHVjdC9saXN0IiwiL3Byb2R1Y3QvY2F0ZWdvcnkiLCIvY2hhcnQiLCIvY2hhcnQvYmFyIiwiL2NoYXJ0L2xpbmUiLCIvY2hhcnQvcGllIl0sIl9pZCI6IjVmYzc4NDJkMjY0MjAwMDBkYzAwNTAzYyIsImF1dGhUaW1lIjoiMjAyMS0wMy0xN1QxMzowMDozMC41MDJaIiwiYXV0aFVzZXIiOiJ4aWFvZmVpIiwiY3JlYXRlRGF0ZSI6bnVsbCwiY3JlYXRlVGltZSI6IjIwMjAtMTItMDIiLCJuYW1lIjoi6LaF57qn566h55CG5ZGYIiwic3RhdGUiOjEsInVwZGF0ZURhdGUiOiIyMDIxLTAzLTE3VDEzOjAwOjMwLjUwOVoifSwiX2lkIjoiNWZiNjY1ZjEyMjViMDAwMDM2MDA0ZGY1IiwiZXhwIjoxNjc3ODk4MTA1LjU0MywiaWF0IjoxNjc3ODExNzA1fQ.B3BqHeY3sfKLjywFN34PaIbLumnCCanfdhePXGiUzHQ'

export function getAccountList() {
    return axios.post('http://localhost:8002/users/getAccountList', {}, {
        headers: {
            token
        }
    })
}
3)创建异步thunk

通过createAsyncThunk去创建异步thunk,进行异步请求封装。会返回一个thunk创建器函数,包含三个状态:pendingfulfiledrejected,我们需要监听这个三个状态去对应执行reducer函数操作。

store/user.js

// 是一个函数,生成thunk进行异步派发,一个thunk生成器,用于dispatch派发
export let getUserListThunk = createAsyncThunk('user/getUserList', async () => {
    // 这里可以去调用接口做异步处理
    let res = await getAccountList();
    return res.data.data;
});

createAsyncThunk:需要将数据返回,不需要在这里去做数据修改操作。

4)通过extraReducers配置额外的reducer

在这里可以监听异步thunk创建器函数的状态,对应处理reducer函数

第一种配置方式

通过builder构建起的addCase函数去监听状态。

let userSlice = createSlice({
    name: 'user',
    initialState: {
        userList: []
    },
    reducers: {},
    extraReducers(builder) {
        // 监听调用接口的状态,执行对应的reducer函数去修改数据
        builder.addCase(getUserListThunk.fulfilled, (state, action) => {
            console.log('成功');
            console.log(state, action);
            state.userList = action.payload;
        }).addCase(getUserListThunk.pending, () => {
            console.log('挂起');
        }).addCase(getUserListThunk.rejected, () => {
            console.log('失败');
        })
    }
});

第二种配置方式

使用对象的方式配置:

let userSlice = createSlice({
    name: 'user',
    initialState: {
        userList: []
    },
    reducers: {},
    extraReducers: {
        [getUserListThunk.fulfilled](state, action) {
            console.log('成功');
            state.userList = action.payload;
        },
        [getUserListThunk.pending]() {
            console.log('挂起');
        },
        [getUserListThunk.rejected]() {
            console.log('失败');
        }
    }
});

解释:相当于对象中的key是一个变量的写法:

var k = 'name';

var obj = {
    [k]: 'xxx'
}

obj[k] = 'xxx'
obj.name = 'xxx'
5)派发异步thunk执行异步接口调用

App.js

import { getUserListThunk } from './store/user'

this.props.dispatch(getUserListThunk());

getUserListThunk:是一个thunk创建器,执行后会得到一个thunk函数,派发后会执行异步接口调用,获取到数据。自动触发extraReducers监听对应的状态执行reducer函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值