redux、react-redux、redux-thunk、redux调试工具

  • Post author:
  • Post category:其他


一、redux

安装:npm install –save redux

1. 什么是redux?

redux是一个独立专门用于做状态管理的JS库(不是react插件库)

它可以用在react, angular, vue等项目中, 但基本与react配合使用

作用: 集中式管理react应用中多个组件共享的状态

2. redux的工作流程图解

3. 什么情况下需要使用redux  ==>> 实际上并没有什么要求,你可以想用就用

总体原则: 能不用就不用, 如果不用比较吃力才考虑使用

某个组件的状态,需要共享 ==>> 组件间需要传值(复制(跨多个组件)那种,简单的props即可)

某个状态需要在任何地方都可以拿到

一个组件需要改变全局状态

一个组件需要改变另一个组件的状态

4. redux的核心API(方法)

4.1 createStore() :创建包含指定reducer的store对象,即参数为reducer

eg:

import {createStore} from 'redux'
import counter from './reducers/counter'
const store = createStore(counter)        //counter是一个reducer

4.2 applyMiddleware ():应用上基于redux的中间件(插件库)  ==> 最常见的就是实现异步的插件如 redux-thunk;参数为插件名

eg:

import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'  // redux异步中间件
const store = createStore(
  counter,
  applyMiddleware(thunk) // 应用上异步中间件
)

4.3 combineReducers () :合并多个reducer函数,便于统一引入并使用它创建store对象

eg:

export default combineReducers({
  user,
  chatUser,
  chat
})

5. redux的是三个核心概念

5.1 action :标识要执行行为的对象

包含两个属性:

a. type: 标识属性,值为字符串,具有唯一性,必须属性

b. xxx: 数据属性,值为任意类型,可选属性。一般使用data作为属性名(统一)

eg:

const action = {
	type: 'INCREMENT',
	data: 2
}

Action Creator(创建Action的工厂函数):统一管理action(非常有用)

eg:const increment = (number) => ({type: ‘INCREMENT’, data: number})

5.2 reducer :根据老的state和action, 产生新的state的纯函数

eg:

export default function counter(state = 0, action) {
	switch (action.type) {
		case 'INCREMENT':
		   return state + action.data
		case 'DECREMENT':
		   return state - action.data
		default:
		    return state
	}
}

注意:返回的是一个新的状态

不要直接修改原来的状态  ==> 原因在于:react的组件更新必须依靠setState方法实现,直接修改state并没有作用

5.3 store:将state,action与reducer联系在一起的对象,是redux库最核心的管理对象

内部维护着:state、reducer

核心方法:

getState(): 得到state

dispatch(action): 分发action, 触发reducer调用, 产生新的state ==> eg: store.dispatch({type:’INCREMENT’, number})

subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

使用createStore()方法创建store:

import {createStore} from 'redux'
import counter from './reducers/counter'
const store = createStore(counter)        //counter是一个reducer

6. 实例:

6.1 组件 pages/app.js

import React, {Component} from 'react';

import * as actions from '../redux/actions'         //引入所有的action

export default class App extends Component{

    addFun = () => {        //当前state 加上下拉框的值
        const number = Number(this.refs.numSelect.value);       //注意数据类型
        //通过store的dispatch分发(触发)action并传参,
        this.props.store.dispatch(actions.add(number));     //分发action后,会触发reducer的调用,更新state,但是组件并未更新,因为没有在index.js里添加监听
    };
    downFun = () => {        //当前state 减去下拉框的值
        const number = Number(this.refs.numSelect.value);       //注意数据类型
        //通过store的dispatch分发(触发)action并传参,
        this.props.store.dispatch(actions.down(number));
    };
    addIfOddFun = () => {        //当前state是奇数时:当前state 加上下拉框的值
        const number = Number(this.refs.numSelect.value);       //注意数据类型

        let count = this.props.store.getState();
        if(count % 2 === 1){
            //通过store的dispatch分发(触发)action并传参,
            this.props.store.dispatch(actions.add(number));
        }
    };
    addAsyncFun = () => {        //异步调用(定时器模拟):当前state 加上下拉框的值
        const number = Number(this.refs.numSelect.value);       //注意数据类型
        setTimeout(() => {
            //通过store的dispatch分发(触发)action并传参,   1s以后触发
            this.props.store.dispatch(actions.add(number));
        }, 1000)
    };
    render(){
        return(
            <div>
                <p>
                    运算后的state:  {this.props.store.getState()}
                </p>
                <select ref="numSelect">
                    <option value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>
                </select>{' '}
                <button onClick={this.addFun}>+</button>
                {' '}
                <button onClick={this.downFun}>-</button>
                {' '}
                <button onClick={this.addIfOddFun}>奇数时加</button>
                {' '}
                <button onClick={this.addAsyncFun}>异步加</button>
            </div>
        )
    }
}

6.2 redux/action-types.js  ==> Action对象的type常量名称模块,统一管理,避免出现不同文件中都要使用,出现错误的情况 ( 可直接写在action中 )

/*
Action对象的type常量名称模块,统一管理,避免出现不同文件中都要使用,出现错误的情况
 */

// 实际项目中可定义成Object,一个大模块定义成一个对象,里面的每一个属性就是一个type

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

6.3 redux/actions.js

/*
Action Creator(创建Action的工厂函数),统一管理action
 */

import {INCREMENT, DECREMENT} from './action-types'

//action 是一个函数,返回的是一个标识属性type的对象
export const add = (number) => ({type: INCREMENT, data:number});
export const down = (number) => ({type: DECREMENT, data:number});

6.4 redux/reducers.js  ==>> 如果有多个reducer函数,使用combineReducers函数合并

/*
根据老的state和action, 产生新的state的纯函数
 */
import {INCREMENT, DECREMENT} from './action-types'

export function counter(state=0, action) {
    console.log('调用了reducer',state, action)
    switch (action.type) {
        case INCREMENT:
            return state + action.data;
        case DECREMENT:
            return state - action.data;
        default:
            return state;
    }
}



// export default combineReducers({             // 合并reducers
//     counter
// })

//引入时这样即可
// import reducers from './reducers'

6.5 主文件 index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {createStore} from 'redux'

import App from './pages/redux-demo/pages/app';
import {counter} from "./pages/redux-demo/redux/reducers";

const store = createStore(counter);          //使用createStore方法创建了reducer为counter的store ==> 注意:这里会默认调用一次counter函数,得到默认的state

//定义监听页面更新的函数
const render = () => {
    ReactDOM.render(<App store={store} />,document.getElementById('root'));
}

//render渲染
render();

//订阅监听,一旦reducer改变了state,将触发更新
store.subscribe(render)

6.6 效果

6.7 这样直接使用redux的缺点

代码比较冗余,特别是在index.js 和 pages/app.js 中,都会多次使用store

redux与react组件的代码耦合度太高,如调用action都必须通过store.dispatch

二、react-redux:一个react插件库,专门用来简化react应用中使用redux

安装:npm install –save react-redux

1. react-redux将所有组件分成两大类

1) UI组件

a. 只负责 UI 的呈现,不带有任何业务逻辑

b. 通过props接收数据(一般数据和函数)

c. 不使用任何 Redux 的 API

d. 一般保存在components或者pages文件夹下

2)容器组件

a. 负责管理数据和业务逻辑,不负责UI的呈现

b. 使用 Redux 的 API

c. 一般保存在containers文件夹下

2. 重要的组件和API

1)Provider:组件,让所有组件都可以得到state数据  ==> 即代替跟组件管理 store

<Provider store={store}> 
    <App /> 
</Provider>

2)connect():API,用于包装 UI 组件生成容器组件

个人理解:用于链接组件实例和redux

用于包装UI组件,从而生成容器组件

返回一个新的(特殊)组件

本身是一个函数

所有参数将被结构(即 … 运算)成为链接组件的props

使用之后,所有由redux(store)管理的 方法/state 都可以直接在props里面获取

import { connect } from 'react-redux'
  
connect(
    
    mapStateToprops,
        //将外部的数据(即state对象)转换为UI组件的标签属性
    mapDispatchToProps
      //将分发action的函数转换为UI组件的标签属性
)(Counter)

3. 实例 ==> 由上面的redux版本修改而来

1)新建容器组件文件夹 containers,并新建 app.js,代码如下:

/*
包含原本App组件的容器组件
 */

import React from 'react'           //react组件中必须引入,不管是否使用
import { connect } from 'react-redux'

// 引入action函数
import {add, down} from '../redux/actions'
import App from '../pages/app'      //引入原本的App  UI组件

// 向外暴露连接App组件的包装组件
export default connect(         //参数最终都将被映射成为原App组件的props,并返回一个新的组件
    //即mapStateToprops方法,是一个函数,返回的是一个对象,count是保存在store中的state的key,在UI组件中就通过这个key来访问
    state => ({count: state}),

    //即mapDispatchToProps方法,这样 this.props.store.dispatch(action)的操作将有 react-redux代为执行,我们直接使用this.props.action即可
    {add, down}
)(App)

2) 修改pages/app.js 即原本的App组件 ==>> 可省略(connect会给原本的App组件映射props,所以需要设置props的类型检查),增加如下代码:

static propTypes = {        //这三个props都是在调用connect是传入的参数
    count: PropTypes.number.isRequired,
    add: PropTypes.func.isRequired,
    down: PropTypes.func.isRequired
}

3)修改入口文件index.js,由Provider管理根组件(管理store并初始化渲染页面;监听state,更新组件),渲染容器组价App,不在渲染以前的UI组件App

import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
import {Provider} from 'react-redux'

import App from './containers/app'        //引入新的容器组件
import {counter} from './redux/reducers'

// 根据counter函数创建store对象
const store = createStore(counter)

// 定义渲染根组件标签的函数
ReactDOM.render(
  (
    //有Provider管理store并初始化渲染页面;监听state,更新组件
    <Provider store={store}>        
      <App/>
    </Provider>
  ),
  document.getElementById('root')
)


4)UI组件,原本的app.js

访问store中的state   ==>> this.props.count

触发action: this.props.store.dispatch(actions.add(number));   ==>> this.props.add(number);

store.dispatch这个操作有react-redux内部去执行

全部代码:

import React, {Component} from 'react';


export default class App extends Component{
    componentDidMount(){
        console.log('=====this.props',this.props);
    }

    addFun = () => {        //当前state 加上下拉框的值
        const number = Number(this.refs.numSelect.value);       //注意数据类型
        //通过store的dispatch分发(触发)action并传参,
        this.props.add(number);     //分发action后,会触发reducer的调用,更新state,但是组件并未更新,因为没有在index.js里添加监听
    };
    downFun = () => {        //当前state 减去下拉框的值
        const number = Number(this.refs.numSelect.value);       //注意数据类型
        //通过store的dispatch分发(触发)action并传参,
        this.props.down(number);
    };
    addIfOddFun = () => {        //当前state是奇数时:当前state 加上下拉框的值
        const number = Number(this.refs.numSelect.value);       //注意数据类型

        let count = this.props.count;
        if(count % 2 === 1){
            //通过store的dispatch分发(触发)action并传参,
            this.props.add(number);
        }
    };
    addAsyncFun = () => {        //异步调用(定时器模拟):当前state 加上下拉框的值
        const number = Number(this.refs.numSelect.value);       //注意数据类型
        setTimeout(() => {
            //通过store的dispatch分发(触发)action并传参,   1s以后触发
            this.props.add(number);
        }, 1000)
    };
    render(){
        return(
            <div>
                <p>
                    运算后的state:  {this.props.count}
                </p>
                <select ref="numSelect">
                    <option value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>
                </select>{' '}
                <button onClick={this.addFun}>+</button>
                {' '}
                <button onClick={this.downFun}>-</button>
                {' '}
                <button onClick={this.addIfOddFun}>奇数时加</button>
                {' '}
                <button onClick={this.addAsyncFun}>异步加</button>
            </div>
        )
    }
}

注意:a. 看一下componentDidMount打印的this.props    ==>> 这三个props都是在connect传入的

b. 在react开发工具中看一下结构

5)redux的主要代码,即 redux/action-types.js 、redux/actions.js 、redux/reducers.js 不变

三、redux-thunk :redux插件(异步中间件)

安装:npm install –save redux-thunk

1. 为什么使用异步中间件

redux默认是不能进行异步处理的

应用中又需要在redux中执行异步任务(ajax, 定时器)

2. 实例 ==>> 从上面的 react-redux修改

1)把生成sore的代码从入口文件 index.js 中抽离出来,redux/store.js ,代码如下:

import React from 'react'
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'

import {counter} from './reducers'

// 根据counter函数创建store对象
export default createStore(
  counter,
  applyMiddleware(thunk) // 应用上异步中间件
)

2)修改入口文件 index.js 如下

import React from 'react'
import ReactDOM from 'react-dom'
import {Provider} from 'react-redux'

import App from './containers/app'        //引入新的容器组件
import store from './redux/store'

// 定义渲染根组件标签的函数
ReactDOM.render(
  (
    <Provider store={store}>
      <App/>
    </Provider>
  ),
  document.getElementById('root')
)

3)在redux/actions.js 中新增一个异步的增加action(以前的两个加、减不变),即

// 异步action creator(返回一个函数)
export const addAsync = number => {
  return dispatch => {
    setTimeout(() => {
      dispatch(add(number))        //一秒以后调用同步的add  action函数
    }, 1000)
  }
}

注意:addAsync本身是一个函数,返回的也是一个函数

redux默认是同步的,action函数只能返回一个对象

如果需要返回一个函数(redux的异步写法),必须使用异步中间件后才可以这么写

4)在containers/app.js下引入addAsync,并作为参数传入connect中

/*
包含原本App组件的容器组件
 */

import React from 'react'           //react组件中必须引入,不管是否使用
import { connect } from 'react-redux'

// 引入action函数
import {add, down, addAsync} from '../redux/actions'
import App from '../pages/app'      //引入原本的App  UI组件

// 向外暴露连接App组件的包装组件
export default connect(     
    state => ({count: state}),
    {add, down, addAsync}
)(App)

4)在UI组件app中调用这个异步的action

incrementAsync = () => {
    const number = this.refs.numSelect.value*1  //转为number
    this.props.addAsync(number)
  }

四、redux调试工具

1. 安装chrome插件:Redux DevTools

2. 在项目中安装开发依赖:npm install –save-dev redux-devtools-extension

3. 编码:

import { composeWithDevTools } from 'redux-devtools-extension'

const store = createStore(
  
    counter,
  
    composeWithDevTools(applyMiddleware(thunk)) 

)

4. 效果

文章仅为本人学习过程的一个记录,仅供参考,如有问题,欢迎指出!



版权声明:本文为halo1416原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。