Redux 、react-redux

390 阅读5分钟

Redux:如果你不知道是否需要 Redux,那就是不需要它。

redux类似于vuejs的vuex,是react的数据管理中心。

  • redux上手

    • 首先我们需要安装redux

      npm install redux --save
      
  • Store

    redux 中我们首先需要理解store,这个就是帮我们管理数据的政委,Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。

  • State

    Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。当前时刻的 State,可以通过store.getState()拿到

  • Action

    State 的变化,会导致 View 的变化。但是,用户不能够直接改变State,只能操控View所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。

    Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。

  • Dispatch

    功能就是我们View层要发出Action,必须要通过dispatch来进行触发

  • Reducer

    Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。

    Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。

  • 对redux通俗理解

    就拿我们 * 东来说,发货都是从仓库发出,仓库就是redux的store,store中的state是存储数据也就是相当于仓库的货物,当我们从*东软件上下单 就是我们触发一个dispatch,然后 *东后台接到订单通知(action)仓库,相当我们下单买的货物的通知到达仓库(这个过程是我们和货物不能直接接触的,如果能接触货物早就偷光了哈哈哈),通知到仓库后,仓库里边有工人(reducer)来进行处理发货一系列的动作,发出货物之后,仓库货物的数据(state数据)就变了,货物到我们手里(view也就变了)

  • 简单加减按钮的实现
    //  store.js
    import {createStore} from 'redux'
    const counterReducer = (state=0,action)=>{ // 设置默认值
        switch (action.type) {
            case 'add':
                return state+1 ;   // 一定是返回新的state 而不是像vue直接修改 
            case 'minus':
                return state-1 ;
            default:
                return state
        }
    };
    const store = createStore(counterReducer);
    export default store
    
    
    // App.js
    class App extends Component {
        render() {
            return (
                <div className="App">
               {/*getState()返回当前的state树它与 store 的最后一个 reducer 返回值相同。*/}
                    <p>{store.getState()}</p>
                    <Button type={"primary"} onClick={()=>store.dispatch({type:"add"})} >增加+</Button>
                    <Button type={"primary"} onClick={()=>store.dispatch({type:"minus"})}>减少-</Button>
                </div>
            );
        }
    }
    
    // index.js 中的render方法需要自己手动订阅监听 
    import store from "./store";
    const render =()=>{
        ReactDOM.render(
            <App />,
            document.getElementById('root')
        );
    
    };
    render(); 
    store.subscribe(render); // 每次 dispatch也就是state发生变化都需要render一次
    
  • react-redux

    上边使用redux 每次都需要render麻烦且不优雅,使用react-redux更好,不用手动再去监听

    npm insatll react-redux --save
    
    • Provider顶级组件,来提供数据,
    • connect高阶组件,提供数据和方法,把组件的状态映射到属性之上,然后只用props来进行调用
    • 看看react-redux如何实现上边的功能,
    // index.js  不需要手动再去监听 ,直接使用Provider进行包裹,自动监听数据了
    import store from "./store";
    import {Provider} from 'react-redux'
    ReactDOM.render(
        // 外层包一层Provider ,提供数据和组件间的数据传递一样
        <Provider store={store}>
            <App />
        </Provider>,
        document.getElementById('root')
    );
    

    Provider将数据传递进来数据任何地方想使用就用connect链接,这样App和redux就链接起来了

    // app.js
    class App extends Component {
        render() {
            return (
                <div className="App">
                    <p>{this.props.num}</p>  // dispatch 直接写对应的方法 
                    <Button type={"primary"} onClick={()=>this.props.add()}>增加+</Button>
                    <Button type={"primary"} onClick={()=>this.props.minus()}>减少-</Button>
                </div>
            );
        }
    }
    const mapStateToProps = (state) =>{ // 它把状态映射到属性之上 所以上边使用this.props
        return {
            num:state
        }
    };
    const mapDispatchToProps = dispatch=>{
        return{
            add:()=>dispatch({type:"add"}),
            minus:()=>dispatch({type:"minus"})
        }
    }
    
    App = connect(mapStateToProps,mapDispatchToProps)(App)
    
  • react-redux中的connect 使用装饰器写法

    上边的connect 使用的高阶组件来声明需要的state里边的数据和方法

    //App.js
    @connect(
        // Es6语法
        //state=>({num:state}),  
       	// 读数据
        state=>{
            return {
                num:state
            }
        },
        //除了ES6语法简写外 同步的dispatch可以进行简写
        //写数据,要调用的dispatch 并且这些方法会传递到props中
        // add:()=>({type:"add",step:2}), 可以传递多个参数,但第一参数是修改数据的请求
        {
            add:()=>({type:"add"}),
            minus:()=>({type:"minus"})
        }
        // dispatch=>{
        //     return{
        //         add:()=>dispatch({type:"add"}),
        //         minus:()=>dispatch({type:"minus"})
        //     }
        // }
    )
    
  • redux默认都是同步的操作,这样异步任务就需要使用 中间件 来完成

    为什么异步任务只能通过中间件来完成?

    1. Reducer :纯函数,只能承担计算State的功能,不适合承担其他功能,理论上纯函数不能进行读写操作
    2. View:与State一一对应,可以看做State的视觉层,也不适合承担其他功能
    3. Action:存放数据的对象,即消息的载体,只能被别人操控,自己并不能进行任何操作
    4. 实际的reducer和action store都需要独立拆分文件

    所以要进行异步任务的dispatch请求,到达Reducer之前都需要经过中间件的处理。

    我们试一下redux-logger和redux-thunk(异步操作) 两个中间件

    //store.js
    import {createStore,applyMiddleware} from 'redux' // applyMiddleware 是使用中间件
    import logger from 'redux-logger'
    import thunk from 'redux-thunk'
    const counterReducer = (state=0,action)=>{ // 设置默认值
        switch (action.type) {
            case 'add':
                return state+1 ;
            case 'minus':
                return state-1 ;
            default:
                return state
        }
    };
    const store = createStore(counterReducer,applyMiddleware(logger,thunk));
    
    @connect(
        state=>{
            return {
                num:state
            }
        },
        {
            add:()=>({type:"add"}),
            minus:()=>({type:"minus"}),
            asyncAdd:()=>dispatch=>{   // 延时加1。模拟异步任务
                setTimeout(()=>{
                    dispatch({type:"add"})
                },2000)
            }
        }
    )
    
  • 多个reducer
    //store.js
    import {createStore,applyMiddleware,combineReducers} from 'redux'
    const store = createStore(
        combineReducers({counterReducer,numberReducer}), // 假如我们还有一个numberReducer
        applyMiddleware(logger,thunk)
    );
    export default store
    
    
  • 抽离Reducer 和Action
    // 新建 reducer.redux.js
    const counterReducer = (state=0,action)=>{ // 设置默认值
        switch (action.type) {
            case 'add':
                return state+1 ;
            case 'minus':
                return state-1 ;
            default:
                return state
        }
    };
    const add= ()=>({type:"add"});
    const minus=()=>({type:"minus"});
    // 支持这种返回一个函数的写法,就是因为用了thunk
    const asyncAdd =()=>dispatch=>{
        setTimeout(()=>{
            dispatch({type:"add"})
        },2000)
    };
    export {counterReducer,add,minus,asyncAdd}
    
    
    import {Provider} from 'react-redux'
    import * as serviceWorker from './serviceWorker';
    import {createStore,applyMiddleware} from 'redux'
    import logger from 'redux-logger'
    import thunk from 'redux-thunk'
    import {counterReducer} from "./couter.redux";
    // 新建store 第一个参数reducer 第二个参数中间件
    const store = createStore(counterReducer,applyMiddleware(logger,thunk));
    //import store from "./store";       store.js文件就没有什么用了
    ReactDOM.render(
        // 外层包一层Provider ,提供数据和组件间的数据传递一样
        <Provider store={store}>
            <App />
        </Provider>,
        document.getElementById('root')
    );
    
    
    //  App.js 直接引入reducer.redux.js的方法
    import {add,minus,asyncAdd} from "./couter.redux";
    @connect(
        state=>{
            return {
                num:state
            }
        },
        {add,minus,asyncAdd}  //
    )