react-05-Redux状态管理

Redux 状态管理(重点)

Redux 对于 JavaScript 应用而言是一个可预测状态的容器。换言之,它是一个应用数据流框架,而不是传统的像 underscore.js 或者 AngularJs 那样的库或者框架。

Redux 最主要是用作应用状态的管理。简言之,Redux 用一个单独的常量状态树(对象)保存这一整个应用的状态,这个对象不能直接被改变。当一些数据变化了,一个新的对象就会被创建(使用 actions 和 reducers)。

Redux 由下面四个部分组成:

  • Reducer
  • Store
  • ActionCreator
  • Component View

下面的代码都在同一个页面中编写,如果在不同的页面编写,需要额外添加导入导出等数据传递接收方法。

下载模块

npm install redux -S
# or
yarn add redux

引入模块

在 redux 中取出 createStore 对象,这个对象的作用是创建仓库的,仓库中存储着要用到的项目中的属性和方法。

import { createStore } from "redux"

创建 reducers

reducers 翻译过来是减震器、还原器的意思。

组件视图层触发的操作,执行的就是这个 reducers,通过业务逻辑的处理,最终将处理后的数据保存到 store 中。

// 初始的状态
const stateDefault = {

}

// 创建 reducers,把初始的状态带入进入,让两者关联在一起。
const reducers = function( state=stateDefault, action ){
    // 在此处应根据action.type来决定对state进行增删改查哪种操作
    // 暂时不做任何操作,直接把前一次的state直接返回给store
    return state
}

创建 store

保存状态的容器,不仅仅保存一些数据,reducers中还存放在修改数据的业务逻辑代码。

const store = createStore(reducers)

派发动作 actionCreator

组件视图层的用户操作,dispatch触发的就是reducers方法。

reducers内容中描述着修改store中state的业务逻辑。

var action = {
    type : 'ADD',
    val : 'abc123'
}
store.dispatch( action ) 

store 中的 state 发生变化后,视图层不会自动更新,可以通过 subscribe 把 App.js 中的 setState 的代码注册进来,让 App 组件视图层更新。

获取 state

组件视图层中获取 redux 中的 state 数据

store.getState()

订阅 subscribe

每当 reducers 执行完毕后,状态修改完毕后,自动触发的事件。

const unsubscribe = store.subscribe(function(){
    
})

取消订阅

unsubscribe()

注入到组件

同样是单项数据流原则,只能一层一层传入数据。所以后期应该用 react-redux 模块。

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

在组件中使用

this.props.store.dispatch()
// or
this.props.store.getState()

React-Redux(重点)

React-Redux 组件的作用是对 react 与 redux 进行连接,如果在 react 项目中直接使用 redux,那么需要把 redux 中的 store 数据,通过 props 属性,一层一层传递到组件中,这样做太麻烦了,所以可以借助 React-Redux 模块,可以跨层级的在任意组件中直接把 Redux 中的 store 数据取出来。

下载

npm install react-redux -S
# or
yarn add react-redux

引入

react-redux 模块唯一的作用就是用来连接 react 和 redux,它里面只有2个子对象

import { Provider, connect } from 'react-redux'
  • Provider: 提供。这是一个组件,这个组件中传入 store,即这个组件提供了redux中的store。
  • connect: 连接。无论是子组件还是其他后代组件,只要使用connect函数进行连接,就可以关联到redux中的store。

Provider组件

Provider组件,通常是最顶层,使用 Provider 组件把 store 共享出来。

react-redux 仅仅解决了组件间传递数据这个问题,所以 redux 本身还需要以前的方式编写出来,即在 redux 中把 store 创建出来,然后以属性的形式,挂载到 Provider 组件上。

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

Provider组件把redux的store共享出来之后,别的组件如果想使用Provider组件中的store数据,需要用 connect 将 Provider 及容器组件和UI组件连接到一起。

因为使用 react-redux 就是为了在不同层级都可以直接使用 redux 的 store,所以容器组件和UI组件随便写在哪里都可以。

容器组件

容器组件也被称为聪明的组件,容器组件和UI组件要组合在一起,容器组件跨层级的直接去Provider组件中获取数据,然后容器组件把得到的数据挂载到UI组件的属性上。

容器组件相当于直接把 redux 中的 State 和 Dispatch 映射到它对应的UI组件的属性上,然后UI组件通过 props 就可以拿到 State 和 Dispatch 了。

在 View 层使用组件时,使用该容器组件,这样容器组件里的UI组件就可以直接通过 props 拿到 State 和 Dispatch 了。

// 这是容器组件,也被称为聪明的组件
const mapStateToProps = function(state, props){
    return {}
}

const mapDispatchToProps = function(dispatch){
    return {}
}

export default connect( mapStateToProps, mapDispatchToProps )( UI组件 )

UI组件

UI组件也被称为展示组件,也被称为木偶组件,UI组件的父层组件是容器组件,容器组件将 redux 中的数据,挂载到了UI组件的属性上。

render(){
    return (
        <div>就是普通的组件,这里可以用this.props接收父层容器组件中映射过来的状态和方法</div>
    )
}

容器组件会把dispatch映射到展示组件中,用户调用dispatch时,就会执行reducers中函数,进而改变了redux的state。

在reducers中,修改state时,一定要改变栈值,这样与之关联的视图层都会重新渲染。

最简单的修改栈值方法:

JSON.parse(JSON.stringify(oldState))

combineReducers 合并

combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数合并成一个最终的 reducer 函数。

常见使用场景:项目模块特别多,每个模块又需要用到很多数据,这些数据如果都写在一起,会显得代码结构特别乱,所以要把每一个模块(功能)单独用一个reducers描述,最终使用 combineReducers 对所有的 reducers 合并,类似 modules 的概念。

import { createStore, combineReducers } from 'redux';

const store = createStore(combineReducers({a:reducers1, b:reducers2}));

调用的时候,还是通过 store.dispatch 来调用,如果多个reducers中有相同命名的 type,那么相关代码都会执行。

获取 state 时,需要写 reducers 的 key 名字。

applyMiddleware 中间件

中间件函数,Middleware 可以让你包装 store 的 dispatch 方法来达到你想要的目的。

项目中必然会涉及到异步代码,这种异步代码会写在哪里?

  • 方案1:直接写在 UI 组件中,当异步代码执行完后,调用 dispatch 方法,这样做的好处是 dispatch 是同步的,特别干净。

  • 方案2:写到容器组件中,在 dispatch 中描述异步代码,这样做的好处是一个功能的多种状态会被包装在一起。

redux-thunk

不要把 redux-thunk 想象的多么的高大上,它唯一的作用就是让 dispatch 中可以写函数。

写函数的目的是因为函数中可以包含很多业务逻辑,也可以把多个 dispatch 包裹在一起。

比如用户一个添加动作,可以分别开始添加、添加中、添加完成、添加失败等等各种状态,这些状态合在一起描述完整的添加过程。

store

yarn add redux-thunk
#or
npm i redux-thunk -S
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

const stateDefault = {
    text : "",
    todos : []
}

const reducers = function(state=stateDefault, action){
    switch( action.type ){          
        case 'ADD':
            console.log('ADD')
            return {
                todos : [...state.todos],
                text : '正在添加数据..'
            } 
        case 'ADD-SUCCESS':
            console.log('ADD-SUCCESS:', action.data)
            return {
                todos : [...state.todos, action.data],
                text : '成功'
            }; 
        case 'GET':
            console.log('GET:', action.data)
            return {
                todos: [...action.data]
            }
        default:    
            return state;
    }
}

const store = createStore(reducers, applyMiddleware(thunk));

export default store;

db.json

db.json 模拟后端接口,向这里添加新数据。

{
    "todos":[
        {"id":"1", "title":"苹果"},
        {"id":"2", "title":"橘子"},
        {"id":"3", "title":"香蕉"},
        {"id":"4", "title":"西瓜"},
        {"id":"5", "title":"菠萝"}
    ]
}

千万不要把 db.json 文件放在当前目录中,如果使用 post 之类的方法修改了该文件,相当于当前项目中有文件做了更改,浏览器是会自动刷新的。

json-server db.json --port 3001

容器组件

import React, { Component } from 'react';
import './App.css';
import { connect } from 'react-redux';
import axios from 'axios';

const mapStateToProps = state=>({
    todos : state.todos,
    text : state.text
})

const mapDispatchToProps = dispatch=>({
    add(){
        dispatch((dispatch, getState)=>{
            dispatch({type:'ADD'})
            axios.get('/db.json').then(res=>{
                dispatch({type:'ADD-SUCCESS', payload:res.data})
                console.log( getState().todos )
            }).catch(err=>{
                dispatch({type:'ADD-FAIL'})
            })
        });
    },
    get(){            
        dispatch(function(dispatch, getState){
            axios.get("http://127.0.0.1:3001/todos").then(res=>{
                console.log('data:', res.data);
                dispatch({type:'GET', data:res.data})
            })
        })       
    }
})

class App extends Component {
    componentDidMount(){
        this.props.get();
    }
    render() {
        return (
            <div>
                <button onClick={()=>{this.props.add()}}>ADD</button>{ this.props.text }
                {this.props.todos.map((item,ind)=><li key={ind}>{item.title}</li>)}
            </div>
        );
    }
}

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

redux-promise-middleware

与thunk一样,解决的都是异步问题。

实际就是允许payload为promise对象,然后对type进行补充,例如下文,看上去执行的是ADD,实际上并没有执行ADD,过程中执行ADD_PENDING,成功时执行ADD_FULFILLED,失败时执行ADD_REJECTED。

store

yarn add redux-promise-middleware
#or
npm install redux-promise-middleware -S
import { createStore, applyMiddleware } from 'redux';
import reduxpromisemiddleware from 'redux-promise-middleware'

const stateDefault = {
    todos : [],
    text : ''
}

const reducers = function(state=stateDefault, action){
    //console.log('action:', action)
    switch( action.type ){          
        case 'ADD':
            //console.log('ADD');
            return {
                todos : [...state.todos],
                text : '开始..'
            };  
        case 'ADD_PENDING':
            //console.log('ADD_PENDING');
            return {
                todos : [...state.todos],
                text : '进行中'
            };  
        case 'ADD_FULFILLED':
            //console.log('ADD_FULFILLED');
            return {
                todos : [...state.todos, {...action.payload}],
                text : '已完成'
            };  
        case 'ADD_REJECTED':
            //console.log('ADD_FULFILLED');
            return {
                todos : [...state.todos, {...action.payload}],
                text : '失败'
            };
        case 'GET_FULFILLED':
            return {
                todos : [...action.payload],
                text : '初始化'
            };
        default:
            return state;
    }
}

const store = createStore(reducers, applyMiddleware(reduxpromisemiddleware));

export default store;

如果想使用多个 Middleware 可以用逗号分割,例如 applyMiddleware(thunk, reduxpromisemiddleware)

组件

import React from 'react';
import { connect } from 'react-redux';
import axios from 'axios';

const mapStateToProps = function(state, props){
    return {
        text : state.text,
        todos : state.todos
    }
}

const mapDispatchToProps = (dispatch)=>({
    add(val){
        // 这个会执行ADD
        dispatch({type:'ADD'}) 
        setTimeout(()=>{
            // 这个不会执行ADD,直接执行ADD_PENDING
            dispatch({type:'ADD', payload:new Promise(function(resolve){
                setTimeout(()=>{
                    axios.post('http://localhost:3002/todos', {val}).then(result=>{
                        // 这个执行ADD_FULFILLED
                        resolve(result.data)
                    })
                }, 500)
            })})
        }, 500)
    },
    get(){
        dispatch({type:'GET', payload:new Promise(function(resolve){
            setTimeout(()=>{
                axios.get('http://localhost:3002/todos').then(result=>{
                    resolve(result.data)
                })
            }, 500)
        })})
    }
})

class App extends React.Component {
    componentDidMount(){
        this.props.get();
    }
    render(){             
        return (
            <div>
                <input ref="input1" />{this.props.text}
                <button onClick={()=>{this.props.add(this.refs.input1.value)}}>
                    添加
                </button>
                {this.props.todos.map(item=>(
                    <li key={item.id}>{item.val}</li>
                ))}
            </div>
        )
    }
}

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

redux-saga

把所有的过程写在generator函数中,这样语义就会很舒服。

store

import { createStore, applyMiddleware } from 'redux';
import axios from 'axios';
import createSagaMiddleware from 'redux-saga';
import { call, put, select } from 'redux-saga/effects';

// store仓库使用saga中间件
const sagaMiddleware = createSagaMiddleware();

// 添加数据
const postData = (payload)=>new Promise((resolve, reject)=>{
    axios.post('http://localhost:3002/todos', payload).then(res=>{
        resolve(res.data);
    }).catch(err=>{
        reject(err);
    })
});

// 获取数据
const getData = ()=>new Promise((resolve, reject)=>{
    axios.get('http://localhost:3002/todos').then(res=>{
        resolve(res.data);
    }).catch(err=>{
        reject(err);
    })
});

// generator函数 (添加的一系列动作)
const addSaga = function * (payload){
    try{
        const data = yield call(()=>postData(payload)); //添加数据,获取响应
        yield put({ type: 'ADD-SUCCESS', payload: data}); // 执行别的action
        //const todos = yield select(state =>state.todos);  // 获取todos
    }catch(err){
        yield put({ type: 'ADD-FAIL', payload: err}); // 执行别的action
    }
}

// generator函数 (获取的一系列动作)
const getSaga = function * (){
    const data = yield call(()=>getData());
    yield put({ type: 'GET-SUCCESS', payload: data});
}

// 默认状态
const stateDefault = {
    todos : [],
    text : ''
}

// reducers
const reducers = function(state=stateDefault, action){
    //console.log('action:', action)
    switch( action.type ){          
        case 'ADD':
            console.log('ADD')
            sagaMiddleware.run(()=>addSaga(action.payload));
            return {
                ...state,
                text : '添加..'
            };
        case 'ADD-SUCCESS':
            console.log('ADD-SUCCESS', action.payload)
            return {
                text : '添加成功',
                todos : [...state.todos, action.payload]
            }    
        case 'ADD-FAIL':
            console.log('ADD-FAIL')
            return {
                ...state,
                text : '添加失败'
            }  
        case 'GET':
            console.log('GET')
            sagaMiddleware.run(()=>getSaga());
            return {
                ...state,
                text : '获取..'
            }; 
        case 'GET-SUCCESS':
            console.log('GET-SUCCESS', action.payload)
            return {
                text : '获取成功',
                todos : [...action.payload]
            }       
        default:
            return state;
    }
}

const store = createStore(reducers, applyMiddleware(sagaMiddleware));

export default store;

组件

import React from 'react';
import { connect } from 'react-redux';

const mapStateToProps = function(state, props){
    return {
        text : state.text,
        todos : state.todos
    }
}

const mapDispatchToProps = function(dispatch){
    return {
        add(val){
            dispatch({type:'ADD', payload:{val}});
        },
        get(){
            dispatch({type:'GET'});
        }
    }
}

class App extends React.Component {
    componentDidMount(){
        this.props.get();
    }
    render(){             
        return (
            <div>
                <input ref="input1" />{this.props.text}
                <button onClick={()=>{this.props.add(this.refs.input1.value)}}>
                    添加
                </button>
                {this.props.todos.map(item=>(
                    <li key={item.id}>{item.val}</li>
                ))}
            </div>
        )
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(App);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值