每天学点React ~ react-redux

306 阅读6分钟

react-redux

简介

  1. 所有的UI组件都应该包裹一个容器组件(父子关系)
  2. 容器组件是真正和redux交互的,里面可以随意的使用reduxAPI
  3. UI组件中不能使用任何reduxAPI
  4. 容器组件会传给UI组件:
    • redux中所保存的状态
    • 用于操作状态的方法
  5. 容器传给UI传递:状态、操作状态的方法均通过props传递

image.png

案例前置准备

在上一篇《Redux入门》中我们有一个redux的案例,这也就直接在上面进行改进吧。

「目录结构」

image.png

  1. 改进Count组件,去除里面任何与redux相关的内容,将其改造成一个UI组件。
import React, { Component } from 'react'

export default class Count extends Component {

    state = {}

    // 加法
    increment = () => {
        const { value } = this.selectNumber
    }

    // 减法
    decrement = () => {
        const { value } = this.selectNumber
    }

    // 求和结果为奇数再增加
    incrementIfOdd = () => {
        const { value } = this.selectNumber
    }

    // 模拟异步增加
    incrementAsync = () => {
        const { value } = this.selectNumber
    }
    render() {
        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>
            </div>
        )
    }
}

2.创建containers文件夹,新建一个Count容器组件,然后根据模型图来引入所需要的组件。

  • 引入Count的UI组件
  • 引入connect用于连接UI组件与redux
  • 使用connect()()创建并暴露一个Count的容器组件
// 引入`Count`的UI组件
import CountUI from '../../components/Count'
// 引入`connect`用于连接UI组件与`redux`
import { connect } from 'react-redux'
// 使用`connect()()`创建并暴露一个`Count`的容器组件
export default connect()(CountUI)

3.通过App组件,引入store,再通过props属性传递给容器组件,这里需要注意的是,我们不能直接在容器组件中引入store,而是需要通过props来进传递,否则会报错提示找不到store

image.png

import React, { Component } from 'react'
import store from './redux/store'

import Count from './containers/Count'

export default class App extends Component {
    render() {
        return (
            <div>
                <Count store={store}></Count>
            </div>
        )
    }
}

4.父子组件传参

在常规的父子组件中,我们传递参数的方式是在父标签中引入子标签,同时申明参数即可,如:

<Father>
<Son a='1'/>
</Father>

但是在containers中,我们并没有类似的明显父子关系结构,难道容器组件和UI组件并不是父子关系吗?我们可以通过浏览器的插件看一下。

image.png

通过插件我们可以看到容器与UI确实是父子关系,那么父子间的参数传递应该如何实现呢?

connect的函数中,我们可以传递两个参数交给它 - redux中所保存的状态 - 用于操作状态的方法

需要注意的是,这两个参数需要定义成函数,我们要传递给子组件的内容需要通过函数的返回值进行传递,案例如下:

// 引入`Count`的UI组件
import CountUI from '../../components/Count'
// 引入`connect`用于连接UI组件与`redux`
import { connect } from 'react-redux'

function a(){
    return {a: 'aa'}
}

function b(){
    return {b: 11}
}

// 使用`connect()()`创建并暴露一个`Count`的容器组件
export default connect(a, b)(CountUI)

在UI组件中,我们在render()中打印一下props看看能不能正常接收到传递的参数。 image.png

可以看到在props中是有我们传递过来到状态的。既然参数已经传递进来了,那么我们就可以改进一下,把我们需要的数据给拿到。

// 引入`Count`的UI组件
import CountUI from '../../components/Count'
// 引入`connect`用于连接UI组件与`redux`
import { connect } from 'react-redux'
import {createIncrementAction, createDecrementAction, createIncrementAsyncAction} from '../../redux/count_action'

// mapStateToProps函数返回一个对象:
// 1. key作为传递给UI组件的props的key,value作为传递给UI组件的props的value
// 2. 用于传递状态
function mapStateToProps(state){
    return {count: state}
}

// mapDispatchToProps函数返回一个对象:
// 1. key作为传递给UI组件的props的key,value作为传递给UI组件的props的value
// 2. 用于传递操作状态的方法
function mapDispatchToProps(dispatch){
    return {
        increment: value => {dispatch(createIncrementAction(value * 1))},
        decrement: value => {dispatch(createDecrementAction(value * 1))},
        asyncIncrement: (value, time) => {dispatch(createIncrementAsyncAction(value * 1, time))},
    }
}

// 使用`connect()()`创建并暴露一个`Count`的容器组件
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
import React, { Component } from 'react'

export default class Count extends Component {

    // 加法
    increment = () => {
        const { value } = this.selectNumber
        this.props.increment(value)
    }

    // 减法
    decrement = () => {
        const { value } = this.selectNumber
        this.props.decrement(value)
    }

    // 求和结果为奇数再增加
    incrementIfOdd = () => {
        const { value } = this.selectNumber
        if (this.props.count % 2 !== 0) {
            this.props.increment(value)
        }
    }

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

image.png

image.png

求和结果的数值可以正常的显示了,并且几个按钮的功能也都可以正常使用。

两个函数的名字使用的是官网给出的提示定义的,具体复现方式如下:

function mapStateToProps(state){
    return 1
}

function mapDispatchToProps(dispatch){
    return 1
}

export default connect(mapStateToProps, mapDispatchToProps)(CountUI)

当我们传入当参数返回类型不是对象对适合,它会给出我们对应的错误提示,这里可以看到两个函数的名称。 image.png

优化

在上面的代码中,我们是通过App组件来传递store的,如果有多个组件,就需要重复多次传参,这样非常的繁琐,react-redux框架帮我们封装了一个方便方便的功能Provider,它可以在根标签那边引入,自动帮我们其余的组件加上store。同时,react-redux框架也帮我们实现了监听功能,我们也不需要在index.js文件中实现监听逻辑,可以直接去掉对应的监听。

// 引入React核心库
import React from 'react'
// 引入ReactDOM
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'

// 引入自定义组件
import App from './App.js'
import store from './redux/store'

// 渲染组件到页面
const container = document.getElementById('root')
const root = createRoot(container)
root.render(
    <Provider store={store}>
        <App/>
    </Provider>
)

对于容器组件,两个参数函数也可以进一步的优化

// 引入`Count`的UI组件
import CountUI from '../../components/Count'
// 引入`connect`用于连接UI组件与`redux`
import { connect } from 'react-redux'
import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/count_action'

// 使用`connect()()`创建并暴露一个`Count`的容器组件
export default connect(
    // mapStateToProps, 
    state => ({ count: state }),
    // mapDispatchToProps的一般写法
    // dispatch => ({
    //     increment: value => { dispatch(createIncrementAction(value * 1)) },
    //     decrement: value => { dispatch(createDecrementAction(value * 1)) },
    //     asyncIncrement: (value, time) => { dispatch(createIncrementAsyncAction(value * 1, time)) },
    // })

    // mapDispatchToProps的简写
    {
        increment: createIncrementAction,
        decrement: createDecrementAction,
        asyncIncrement: createIncrementAsyncAction,
    }
)(CountUI)

小结

  1. 明确两个概念:

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

    2)容器组件:负责和redux通信,将结果交UI组件

  2. 创建容器组件:

    1)通过react-reduxconnect()函数创建

     - mapStateToProps: 映射状态,返回值是一个对象
     - mapDispatchToProps: 映射操作状态的方法,返回值是一个对象,本身也可以是一个对象
    
  3. 容器组件中的store是靠props传进去的,不能在容器组件中直接引用

本文正在参加「金石计划 . 瓜分6万现金大奖」