美文网首页
Redux 状态管理(重点)

Redux 状态管理(重点)

作者: 未来在奋斗 | 来源:发表于2019-12-22 09:36 被阅读0次

    Redux 状态管理(重点)

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

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

    Redux 由下面四个部分组成:

    • Reducer
    • Store
    • ActionCreator
    • Component View

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


    QQ图片20191223193413.png

    下载模块

    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(){
        
    })
    

    取消订阅

    // 一定要记得在销毁之前清除监听不然会报错
      componentWillUnmount() {
        this.subscribe()
      }
    

    注入到组件

    同样是单项数据流原则,只能一层一层传入数据。所以后期应该用 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);
    

    相关文章

      网友评论

          本文标题:Redux 状态管理(重点)

          本文链接:https://www.haomeiwen.com/subject/jlxznctx.html