美文网首页
Redux初步理解

Redux初步理解

作者: oNexiaoyao | 来源:发表于2017-09-12 21:18 被阅读19次

    Redux笔记

    参考理解

    Redux 中文文档
    Redux 阮一峰

    严格的单向数据流是Rduex设计核心。

    Redux简单概括:单一的state是存储在store中,当要对state进行更新的时候,首先要发起一个action(通过dispatch

    函数),action的作用就相当于一个消息通知,用来描述发生了什么(比如:增加一个TODO),然后reducer会根据action来
    进行对state更新,这样就可以更新新的state去渲染View.

    从不直接修改 state 是 Redux 的核心理念之一, 所以你会发现自己总是在使用 Object.assign() 创建对象拷贝, 而拷贝中会包含新创建或更新过的属性值。在下面的 todoApp 示例中, Object.assign() 将会返回一个新的 state 对象, 而其中的 visibilityFilter 属性被更新了:

    function todoApp(state = initialState, action) {
        switch (action.type) {
          case SET_VISIBILITY_FILTER:
          return Object.assign({}, state, {
          visibilityFilter: action.filter
         })
       default:
        return state
      }
    }
    

    Redux适用场景

    1. 用户的使用方式复杂(Ul界面复杂,操作内容多)
    2. 不同身份的用户有不同的使用方式(普通用户和管理员)
    3. 多个用户之间可以协作
    4. 与服务器有大量交互
    5. View要从多个来源获取数据

    从组件看,存在以下场景可以考虑使用Redux

    1. 某个组件的状态需要共享
    2. 某个状态需要在任何地方可以拿到
    3. 一个组件需要改变全局状态
    4. 一个组件需要改变另一个组件的状态

    要点

    1. 应用中所有的state都以一个对象树的形式存储在一个单一的store中
    1. 唯一改变state的办法就是触发action,一个描述发生什么的对象
    2. 为了描述action如何改变state树,需要编写reducers.

    store

    store 充当一个容器,用来保存数据的地方。也可以理解成存储state的地方。整个应用 只能 有一个Store
    由于整个应用只有一个store,所以store保存了所有的数据。可以通过store.getState()获取当前时刻的state.

    store 里能直接通过 store.dispatch() 调用 dispatch() 方法,但是多数情况下你会使用 react-redux 提供的 connect() 帮助器来调用。

    action

    Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。
    action是由用户操作view产生的。view的改变产生action,action的改变会传到store,进而影响state的改变。

    import { createStore } from 'redux';
    const store = createStore(fn);
    store.dispatch({
      type: 'ADD_TODO',
      payload: 'Learn Redux'
    });
    通过store.dispatch()将action传递到store里面。
    

    reducer

    store 接收到action后,必须给出一个新的state,从而是view做出变化。这样的一个计算过程叫做Reducer.
    Reducer是一个 函数,接收action和当前的state作为参数,然后返回一个新的state.

    const reducer = function (state, action) {
      // ...
      return new_state;};
    

    因为Reducer函数负责生成state,而整个应用只有一个state,所以当state非常大的时候,导致Reduce函数也非常的大,
    根据action不同的种类,我们可以将reducer拆分为多个小的reducer,最后再合成一个大的reducer。

    const chatReducer = (state = defaultState, action = {}) => {
    const { type, payload } = action;
    switch (type) {
    case ADD_CHAT:
      return Object.assign({}, state, {
        chatLog: state.chatLog.concat(payload)
      });
    case CHANGE_STATUS:
      return Object.assign({}, state, {
        statusMessage: payload
      });
    case CHANGE_USERNAME:
      return Object.assign({}, state, {
        userName: payload
      });
    default: return state;
    }};
    上面这个Reducer包含了3个action,显得比较大,比较臃肿。
    我们可以对其进行拆分函数:
    const chatReducer = (state = defaultState, action = {}) => {
    return {
    chatLog: chatLog(state.chatLog, action),
    statusMessage: statusMessage(state.statusMessage, action),
    userName: userName(state.userName, action)
    }};
    这样一个大的Reducer函数就拆分成三个小函数,每个小函数负责生成对应属性。这样就可以把小的函数理解成子reducer函数。
    这就与react的根组件与子组件的概念吻合,根组件对应最终生成的大的Reducer,而子组件对应每个子reducer。
    
    拆开成子reducer
    function todos(state = [], action) {
      switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
     }
      }
     function visibilityFilter(state = SHOW_ALL, action) {
       switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
      }
      }
      function todoApp(state = {}, action) {
        return {
       visibilityFilter: visibilityFilter(state.visibilityFilter, action),
       todos: todos(state.todos, action)
    }}
    
    通过使用redux提供的combinReducers()来实现上面todoApp做的事情
    import { combineReducers } from 'redux';
      const todoApp = combineReducers({
      visibilityFilter,
      todos
     })
     上面的代码等价于下面的代码
     export default function todoApp(state = {}, action) {
       return {
         visibilityFilter: visibilityFilter(state.visibilityFilter, action),
         todos: todos(state.todos, action)
      }}
    

    export default todoApp;

    Redux提供combineReducers方法,用于将定义的各个子Reducer函数合并成一个大的Reducer.

    import { combineReducers } from 'redux';
    const chatReducer = combineReducers({
      chatLog,
      statusMessage,
      userName
    })
    

    理解

    import { createStore } from 'redux';
    const store = createStore(reducer);
    store.subscribe(listener); 
    

    通过 reducer 创建一个 store ,每当我们在 store 上 dispatch 一个 action ,store 内的数据就会相应地发生变化。

    Store提供了三种方法:store.getState(),store.dispatch(),store.subscribe().
    getState()用来获取store里面当前的state。
    dispatch()用来传递action到store里面,可以在任何地方调用 store.dispatch(action),包括组件中、XHR 回调中、甚至定时器中。
    subscribe()用来监听事件,接受的参数应当是个函数,该函数应当是在这里获得store的最新state然后用来改变组件state的。

    正常的思路应该是view发出action通过dispatch()传递给reducer进行相关的计算从而得出新的state。观察上面创建store的代码,可以发现我们是
    先把reducer这个计算函数“放入”store里面,所以我们就可以实现当我们把action通过dispatch()传递给store后,不需要自己手动去调用reducer,
    store会自己自动调用。

    怎么使用subscribe()订阅来更新ui,connect如何使用

    使用方法

    明智的做法是只在最顶层组件

    使用React-redux

    首先在最外层容器中,把所有内容包裹在 Provider 组件中,将之前创建的 store作为 prop 传给 Provider 。

    参考理解

    const App = () => {
      return (
        <Provider store={store}>
          <Comp/>
        </Provider>
      )
    };
    

    Provider 内的任何一个组件(比如这里的 Comp ),如果需要使用 state 中的数据,就必须是「被 connect 过的」组件——使用 connect 方法对「你编写的组件( MyComp )」进行包装后的产物。

    class MyComp extends Component {
         // content...
    }
    

    const Comp = connect(...args)(MyComp);

    connect的使用方法 参考理解

    connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options])
    

    connect的作用:连接React组件与Redux store会自己自动调用。
    connect的第一个参数是mapStateToProps函数, 该函数允许我们将我们将store中的数据作为props绑定到组件上。

    mapStateToProps(state, ownProps) : stateProps。
    const mapStateToProps = (state) => {
       return {
          count: state.count
        }
    }
    
    1. mapStateToProps函数的第一个参数就是Redux的store(整个state)从上面的示例我们可以看出我们没有将store中所有的数据(state)全部
      传入connect的组件,我们可以根据所要连接组件所需的props,从store中的state动态的输出该组件需要的props.
    2. mapStateToProps函数的第二个参数ownProps,是连接组件的props.

    使用ownProps示例:(比如点击人员列表查看人员详细信息,点击事件传递组件(该组件只维护一个用户信息)人员Id属性props,然后可以通过这个组件自己的props去store获取对应数据)

    const mapStateToProps = (state, ownProps) => {
      // state 是 {userList: [{id: 0, name: '王二'}]}
        return {
            user: _.find(state.userList, {id: ownProps.userId})
         }
       }
    class MyComp extends Component {
     static PropTypes = {
        userId: PropTypes.string.isRequired,
        user: PropTypes.object
      };
    render(){
        return <div>用户名:{this.props.user.name}</div>
        }
    }
    

    const Comp = connect(mapStateToProps)(MyComp);

    当Redux中的store(state)变化或者ownProps变化的时候,mapStateToProps都会被调用,计算出一个新的stateProps,(再与ownProps merge后)更新给组件.如果省略了这个参数,你的组件将不会监听 Redux store.

    mapDispatchToProps(dispatch, ownProps): dispatchProps
    

    connect 的第二个参数是 mapDispatchToProps,它的功能是,将 action 作为 props 绑定到组件上.如果省略这个 mapDispatchToProps 参数,默认情况下,dispatch 会注入到你的组件 props 中。该函数如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux action creator,而且这个对象会与 Redux store 绑定在一起,其中所定义的方法名将作为属性名,合并到组件的 props 中。如果传递的是一个函数,该函数将接收一个 dispatch 函数,然后由你来决定如何返回一个对象,这个对象通过 dispatch 函数与 action creator 以某种方式绑定在一起

    不管是 stateProps 还是 dispatchProps,都需要和 ownProps merge 之后才会被赋给组件。connect 的第三个参数就是用来做这件事。通常情况下,你可以不传这个参数,connect 就会使用 Object.assign 替代该方法

    代码示例

    import React, {Component} from 'react'
    class Counter extends Component {
        render() {
            //从组件的props属性中导入四个方法和一个变量
            const {increment, decrement, counter} = this.props;
            //渲染组件,包括一个数字,四个按钮
            return (
                <p>
                    Clicked: {counter} times
                    {' '}
                    <button onClick={increment}>+</button>
                    {' '}
                    <button onClick={decrement}>-</button>
                    {' '}
                </p>
            )
        }
    }
    export default Counter;
    
    import { connect } from 'react-redux'
    import Counter from '../components/Counter'
    import actions from '../actions/counter';
    //将state.counter绑定到props的counter. 哪些 Redux 全局的 state 是我们组件想要通过 props 获取的?
    const mapStateToProps = (state) => {
        return {
            counter: state.counter
        }
    };
    //将action的所有方法绑定到props上.哪些 action 创建函数是我们想要通过 props 获取的?
    const mapDispatchToProps = (dispatch, ownProps) => {
        return {
            increment: (...args) => dispatch(actions.increment(...args)),
            decrement: (...args) => dispatch(actions.decrement(...args))
        }
    };
    //通过react-redux提供的connect方法将我们需要的state中的数据和actions中的方法绑定到props上
    export default connect(mapStateToProps, mapDispatchToProps)(Counter)
    

    Middleware

    作用的位置:位于action被发起之后,到达reducer之前的扩展点

    Redux moddleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer

    理解:在action和reducer中间插入middleware,通过改变数据流,实现异步action.

    可以干什么:进行日志记录、创建崩溃报告、调用异步接口或者路由

    怎么使用Middleware:redux 提供了applyMiddleware这个api来加载middleware

    项目中使用Middleware的方式:

    import { createStore, applyMiddleware } from 'redux'
    import thunkMiddleware from 'redux-thunk'
    import createLogger from 'redux-logger'
    import rootReducer from './reducers'
    
    const loggerMiddleware = createLogger()
    
    const createStoreWithMiddleware = applyMiddleware(
      thunkMiddleware,
      loggerMiddleware
    )(createStore)
    
    export default function configureStore(initialState) {
      return createStoreWithMiddleware(rootReducer, initialState)
    }
    

    applyMiddleware源码:

    export default function applyMiddleware(...middlewares) {            return (next)  => 
            (reducer, initialState) => {
    
                  var store = next(reducer, initialState);
                  var dispatch = store.dispatch;
                  var chain = [];
    
                  var middlewareAPI = {
                    getState: store.getState,
                    dispatch: (action) => dispatch(action)
                  };
    
                  chain = middlewares.map(middleware =>
                                middleware(middlewareAPI));
                  dispatch = compose(...chain, store.dispatch);
                  return {
                    ...store,
                    dispatch
                  };
               };
    }
    

    applyMiddleware源码使用了柯里化(理解:将多参数函数转化为单参数函数执行,然后返回结果接受剩下的参数继续执行)。
    柯里化的过程存在闭包,因而store是最新并且相同的。

    //  柯里化简单示例
    function add(x){
       return function(y){
          return x+y;
       }
    }
    console.log(add(5)(10)); // 15
    

    对于中间件的理解:同步action:action发出后,reducer立即算出state。异步action:action发出以后,过一段时间再执行reducer。怎样是reducer在异步操作结束后自动执行?使用的解决方法就是:中间件。

    同步action只要发出一种action,异步action一般要发出三种action。

    异步action一般要发出的三种action:

    1. 操作发起时的action
    2. 操作成功时的action
    3. 操作失败时的action

    同步action的执行流程:

    action-->dispatch-->reducers

    当我们执行异步action的时候可能还需要在action传递到reducer这个过程中处理一些其他的事,执行一些额外的函数。
    执行流程:

    action-->dispatch-->额外的函数代码-->reducer

    引入中间件,将额外需要执行的函数放到中间件中执行

    action-->dispatch-->middleware1-->...-->middleware3-->reducer

    我们都知道action creator是一个纯js函数,返回的是一个对象。将action creator通过dispacth传递到reducer从而改变state.
    而通过使用中间件,action creator返回的将是一个函数(或者其他的类似Promise对象等),返回的函数的参数是dispatch和getState这个两个redux方法。执行异步请求的时候,第一个action表示操作开始,该操作类似同步action操作。第二个action的发出是在返回的函数中(因为返回的函数的参数含有getState,所有state是最新的。而参数中又含有dispatch,所有可以通过该方法执行第二个action)。

    参考资料一;参考资料二;
    参考资料三

    相关文章

      网友评论

          本文标题:Redux初步理解

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