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

一、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. 效果

 

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值