redux 是 JavaScript 状态容器,提供可预测化的状态管理。 可以用在React、Vue等前端库中。
react-redux是react中的redux插件,简化了redux的用法,使开发者更方便地使用redux来管理状态。
为什么要有redux?我们知道,react中的数据流是单项传递的,即状态只能从父组件传递给子组件,虽然可以用ref在父调中调用子组件的方法从而返回子组件的状态给父组件使用、或者使用contex跨级传递属性等方法,但是当组件很多而且都需要共享一个属性的时候,那些方法管理属性就显得不规范且不方便,因此,需要使用redux这个专门的状态容器来统一管理状态。

下面分别在不用redux、使用redux、使用react-redux实现同一功能为例讲解redux的原理以及用法。(记得用npm或者yarn安装redux和react-rudex包)

如果要实现这样的一个功能:点击“加号”按钮,数字加上下拉框中所选中的数字大小;点击“减号”按钮,数字减去下拉框中所选中的数字大小;点击“increment if odd”按钮,只有当数字是奇数的时候才做加运算;点击“increment async”按钮的时候异步做加运算。
效果图如下:
在这里插入图片描述

案例1:

用一般的onClick回调函数的写法如下:

import React, {Component} from 'react'

class App extends Component {
    constructor(props) {
        super(props);
        this.numberRef = React.createRef();
    }
    state = {
        count: 0
    };

    increment = () => {
        const number = this.numberRef.current.value * 1;
        this.setState({
            count: this.state.count + number
        })
    };
    decrement = () => {
        const number = this.numberRef.current.value * 1;
        this.setState({
            count: this.state.count - number
        })
    };
    incrementIfOdd = () => {
        const count = this.state.count;
        const number = this.numberRef.current.value * 1;
        if(count % 2 === 1) {
            this.setState({
                count: count + number
            })
        }
    };
    incrementAsync = () => {
        const number = this.numberRef.current.value * 1;
        setTimeout(() => {
            this.setState({
                count: this.state.count + number
            })
        }, 1000);

    };

    render() {
        const count = this.state.count;
        return (
            <div>
                <p>click {count} time</p>
                <select ref={this.numberRef}>
                    <option value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>
                </select>
                <button onClick={this.increment}>+</button>
                <button onClick={this.decrement}>-</button>
                <button onClick={this.incrementIfOdd}>increment if odd</button>
                <button onClick={this.incrementAsync}>increment async</button>
            </div>
        )
    }
}

export default App;

redux

案列2

用redux的写法如下:

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import store from './redux/store';


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

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

App.js

import React, {Component} from 'react'
import store from './redux/store';
import {increment, decrement} from './redux/actions';

class App extends Component {

    constructor(props) {
        super(props);
        this.numberRef = React.createRef();
    }

    increment = () => {
        const number = this.numberRef.current.value * 1;
        store.dispatch(increment(number))
    };
    decrement = () => {
        const number = this.numberRef.current.value * 1;
        store.dispatch(decrement(number))

    };
    incrementIfOdd = () => {
        const count = store.getState();
        const number = this.numberRef.current.value * 1;
        if(count % 2 === 1) {
            store.dispatch(increment(number))
        }
    };
    incrementAsync = () => {
        const number = this.numberRef.current.value * 1;
        setTimeout(() => {
            store.dispatch(increment(number))
        }, 1000);

    };
    
    render() {
        const count = store.getState();
        console.log("render--"+count);
        return (
            <div>
                <p>click {count} time</p>
                <select ref={this.numberRef}>
                    <option value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>
                </select>
                <button onClick={this.increment}>+</button>
                <button onClick={this.decrement}>-</button>
                <button onClick={this.incrementIfOdd}>increment if odd</button>
                <button onClick={this.incrementAsync}>increment async</button>
            </div>
        )
    }
}

export default App;

src/redux/constants.js

/**
 * 包含n个action的type常量名称的模块
 * 这里只是避免reducers和action的type中的常量不一致
 * */

export const DECREMENT = 'decrement';
export const INCREMENT = 'increment';

src/redux/actions.js

/**
 * n个用于创建action对象的工厂函数
 * */

import {INCREMENT, DECREMENT} from './constants';

export const increment = (number) => ({ type: INCREMENT, number});
export const decrement = (number) => ({ type: DECREMENT, number});

src/redux/reducer.js

/**
 * 管理状态数据的函数
 * 根据旧的state,删除新的state
 * 必须是纯函数
 * */
import {INCREMENT, DECREMENT} from './constants';

export default function count (state = 1, action) {
    switch(action.type) {
        case INCREMENT:
            return state + action.number;
        case DECREMENT:
            return state - action.number;
        default:
            return state;
    }
}

src/redux/store.js

/**
 * redux最核心的管理对象:store
 * */

import {createStore} from 'redux';
import reducer from './reducer';

//根据指定的reducer函数,产生一个store对象
//store对象内部管理数据的新状态,状态数据的初始值为reducer的返回值
const store = createStore(reducer);

export default store;

分析

redux流程图如下:
在这里插入图片描述
redux的三个关键函数:getState()、subscribe()、dispatch()

  • getState() :用于获取当前最新的状态
  • subscribe() :用于订阅监听当前状态的变化,然后促使页面重新渲染
  • dispatch() :用于发布最新的状态
    .
    现在解释上面代码中src/redux目录下建立的文件夹:
  • constants.js :用于定义常量
  • actions.js :创建action对象的工厂函数,创建出来的action对象用于传递给dispatch()
  • reducers.js :用于管理状态数据的函数,当调用dispatch发布最新状态的时候,根据dispatch的回调函数中type的类型,触发reducers做出不同的状态改变。例如,点击“+”按钮的时候,dispatch中的回调函数为increment,这个函数在actions.js中定义,然后触发reducers中的函数(这个函数就是需要在store.js中传给createStore()的参数的函数)作出不同的状态改变,
    store.js 根据指定的reducer函数,生成一个store对象,store对象中存储的状态初始值就是reducer函数中默认返回(case default)的值。
    .
    再来解释上面的流程图:
  • 当react组件发生某个事件调用dispatch发布消息的时候,由action去触发reducers,reducers的第一个参数为改变之前的状态,第二个参数就是action函数中的对象(必须包含一个type,其他参数任意),然后对状态做出修改后再放入store容器中。通过subscribe订阅了状态改变的组件就会接受状态改变的事件,通过getState获取最新的状态后重新渲染页面。
    .
    从执行npm start后渲染页面的角度解释:
  • 当页面第一次渲染的之前,store.js中的createStore()函数就根据参数reducers创建了store对象,并且reduceers中的默认返回值就是store对象需要管理的状态以及状态初始值。然后页面调用getState()获取到状态,值获取完毕后进行页面渲染。当用户点击按钮的时候,就会将不同的action传递给dispatch,一旦调用dispatch,就会触发reducer中的函数调用,改变状态之后,将新的状态传递给store,store接受到新的状态之后就会去调用subscribe中的回调函数。

直接使用redux的方式导致react组件和redux的耦合度很高而且通过dispatch,getState等函数调用很麻烦,这时候就应该学习react-redux的使用了,毕竟该插件包装了redux后专门用于react中。

react-redux

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

UI组件

  • 只负责UI的呈现,不带有任何业务逻辑
  • 通过props接受属性(非函数属性)
  • 不使用任何redux的API
  • 一般保存在components文件夹下

容器组件

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

react-redux的相关API:

  • Provider
    让所有组件都可以使用state数据
ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
);
  • connect()
    用于包装UI组件来生成容器,是一个高阶函数(接受一个组件,返回一个包装后的新组件)
export default connect(mapStateToProps, mapDispatchToProps)(App);
  • mapStateToProps,mapDispatchToProps具体在案例3中的代码注释讲解。
案例3:

在这里插入图片描述
redux文件夹下放的四个js文件代码和案例2中一样
src/components/counters.js

/**
 * UI组件,主要负责页面显示
 * */

import React, {Component} from 'react'
import PropTypes from 'prop-types';

class Counter extends Component {

    constructor(props) {
        super(props);
        this.numberRef = React.createRef();
    }

    static propTypes = {
        count: PropTypes.number.isRequired,
        increment: PropTypes.func.isRequired,
        decrement: PropTypes.func.isRequired,

    };

    increment = () => {
        const number = this.numberRef.current.value * 1;
        this.props.increment(number);
    };
    decrement = () => {
        const number = this.numberRef.current.value * 1;
        this.props.decrement(number);
    };
    incrementIfOdd = () => {
        const count = this.props.count;
        const number = this.numberRef.current.value * 1;
        if(count % 2 === 1) {
            this.props.increment(number);
        }
    };
    incrementAsync = () => {
        const number = this.numberRef.current.value * 1;
        setTimeout(() => {
            this.props.increment(number);
        }, 1000);

    };

    render() {
        const count = this.props.count;
        console.log("render--"+count);
        return (
            <div>
                <p>click {count} time</p>
                <select ref={this.numberRef}>
                    <option value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>
                </select>
                <button onClick={this.increment}>+</button>
                <button onClick={this.decrement}>-</button>
                <button onClick={this.incrementIfOdd}>increment if odd</button>
                <button onClick={this.incrementAsync}>increment async</button>
            </div>
        )
    }
}

export default Counter;

src/containers/App.js

/**
 * 将UI组件包装成容器组件,这样在产生的新组件中就可以使用react-redux中的API
 * */
import React, {Component} from 'react'
import {connect} from 'react-redux';

import {increment, decrement} from '../redux/actions';
import Counter from '../components/counters';

/**
 * 将特定的state数据映射成标签属性传递给UI组件Counter
 * redux在调用此函数时,传入了store.getState()的值
 * 在reducer中我们定义了一个状态名为count,这里的state就是传递的count
 * */
const mapStateToProps = (state) => ({
    count: state
});

/**
 * 将包含dispatch函数调用语句的函数映射成函数属性传递给UI组件Counter
 * redux在调用此函数时,传入了store.dispatch
 * */
const mapDispatchToProps = (dispatch) => ({
    increment: (number) => {dispatch(increment(number))},
    decrement: (number) => {dispatch(decrement(number))}
});

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(Counter);

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';

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

import App from './containers/App';

ReactDOM.render(
    /*Provider会将接受到的store对象提供给所有的容器组件*/
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
);
Logo

前往低代码交流专区

更多推荐