美文网首页
Redux基础

Redux基础

作者: 放风筝的小小马 | 来源:发表于2017-09-17 22:50 被阅读65次

    Actions

    Actions是用于存放数据的载体,通过store.dispatch()函数来将数据从app发送到store

    Actions 是一个Js对象,它有一个默认的属性:type,它用于表明action的类型,它的值是一个字符串常量

    定义一个Actions:

    addTodo = {
      type: 'ADD_TODO',
      text: 'Build my first Redux app'
    }
    

    Action Creators

    上面我们定义了一个actions,现在需要一个Action Creators,用于创造actions
    在Redux中creators 仅仅是返回一个action, 如下:

    function addTodo(text) {
      return {
        type: ADD_TODO,
        text
      }
    }
    

    在传统的 Flux 中,当action cretors被调用时 常常用于触发一个dispatch,如下:

    function addTodoWithDispatch(text) {
      const action = {
        type: ADD_TODO,
        text
      }
      dispatch(action)
    }
    

    在Redux中不是这样的,它并不与dispatch绑定,如下:

    dispatch(addTodo(text))
    dispatch(completeTodo(index))
    

    另外,我们可以将其封装一下,如下:

    const boundAddTodo = text => dispatch(addTodo(text))
    const boundCompleteTodo = index => dispatch(completeTodo(index))
    

    这样,我们就可以直接调用他们,如下:

    boundAddTodo(text)
    boundCompleteTodo(index)
    

    actions.js部分

    /*
     * action types
     */
    
    export const ADD_TODO = 'ADD_TODO'
    export const TOGGLE_TODO = 'TOGGLE_TODO'
    export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'
    
    /*
     * other constants
     */
    
    export const VisibilityFilters = {
      SHOW_ALL: 'SHOW_ALL',
      SHOW_COMPLETED: 'SHOW_COMPLETED',
      SHOW_ACTIVE: 'SHOW_ACTIVE'
    }
    
    /*
     * action creators
     */
    
    export function addTodo(text) {
      return { type: ADD_TODO, text }
    }
    
    export function toggleTodo(index) {
      return { type: TOGGLE_TODO, index }
    }
    
    export function setVisibilityFilter(filter) {
      return { type: SET_VISIBILITY_FILTER, filter }
    }
    

    Reducers

    Actions描述了有事情发生,但是没有指定在响应中,app的state应该如何改变,这个工作由 Reducers来完成

    设计state

    在Redux中,所有的state都被存储到一个单一的对象上,因此在写代码之前,先思考状态的设计,是一个良好的习惯,

    在实际应用中,我们经常需要存储一些数据,以及一些UI state在state tree上,最好做到:保持数据与UI状态分离

    设state时,尽量要保持标准化,不要有任何嵌套,参考JSON数据标准化

    处理状态

    reducer函数是一个纯函数,它接收两个参数:prestate和一个action,然后返回下一个state,如下:
    (previousState, action) => newState

    纯函数:就是传入什么,输出什么,不会改变输入,没有副作用

    注意:一定要保证reducer是一个纯函数,有以下几点要注意:

    • 不要修改它的传入参数
    • 不要执行API的调用和路由转换等有副作用的操作
    • 不要调用不纯的函数,如:Date.now() 、Math.random()

    也就是:给reducer参数,它应该计算出next state,然后返回next state,不能有副作用、不能有API调用,仅仅只是计算

    接下来定义一个reducer,并设置它的初始状态, 如下:

    import { VisibilityFilters } from './actions'
    
    const initialState = {
      visibilityFilter: VisibilityFilters.SHOW_ALL,
      todos: []
    }
    
    function todoApp(state = initialState, action) {
      switch (action.type) {
        case SET_VISIBILITY_FILTER:
          return Object.assign({}, state, {
            visibilityFilter: action.filter
          })
        default:
          return state
      }
    }
    

    最终分割reducer之后的代码,如下:
    reducers.js

    import { combineReducers } from 'redux'
    import {
      ADD_TODO,
      TOGGLE_TODO,
      SET_VISIBILITY_FILTER,
      VisibilityFilters
    } from './actions'
    const { SHOW_ALL } = VisibilityFilters
    
    function visibilityFilter(state = SHOW_ALL, action) {
      switch (action.type) {
        case SET_VISIBILITY_FILTER:
          return action.filter
        default:
          return state
      }
    }
    
    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
      }
    }
    
    const todoApp = combineReducers({
      visibilityFilter,
      todos
    })
    
    export default todoApp
    

    Store

    之前讲过Actions表示发生了什么,Reducers就是根据Actions来更新state,
    Store是将它们组合在一起,

    Store有以下职责:

    • Holds application state;
    • Allows access to state via getState();
    • Allows state to be updated via dispatch(action);
    • Registers listeners via subscribe(listener);
      Handles unregistering of listeners via the function returned by subscribe(listener).

    需要注意的是在一个Redux应用中,只有一个Store,如果需要分割应用的数据逻辑,可以使用reducer composition 来实现

    使用createStore,如下:

    import { createStore } from 'redux'
    import todoApp from './reducers'
    let store = createStore(todoApp)
    

    Data Flow 数据流向

    Redux遵循严格的单向数据流,这意味着所有的数据在APP中的流向具有相同的生命周期,且它们是可预测的,一个数据的生命周期主要包括以下四个阶段:

    1. 调用 store.dispatch(action)
      我们可以在任何地方调用该函数;
    2. Redux的store调用我们提供的reducer函数
      store将传递两个参数给reducer:当前state treeaction, reducer函数会返回下一个start,它是一个纯函数
      例如,上面的todo代码
     // The current application state (list of todos and chosen filter)
     let previousState = {
       visibleTodoFilter: 'SHOW_ALL',
       todos: [
         {
           text: 'Read the docs.',
           complete: false
         }
       ]
     }
    
     // The action being performed (adding a todo)
     let action = {
       type: 'ADD_TODO',
       text: 'Understand the flow.'
     }
    
     // Your reducer returns the next application state
     let nextState = todoApp(previousState, action)
    

    注意: reducer函数是可预测的,当输入相同的参数,调用它很多次,它应该输出相同的值

    1. root reducer 可以将多个reducer输出组合到一个state tree中
      root reducer的结构完全由我们自己来决定,Redux提供了一个combineReducers()函数来帮助我们将多个reducer合并为一个,如下所示:
     function todos(state = [], action) {
       // Somehow calculate it...
       return nextState
     }
    
     function visibleTodoFilter(state = 'SHOW_ALL', action) {
       // Somehow calculate it...
       return nextState
     }
    
     let todoApp = combineReducers({
       todos,
       visibleTodoFilter
     })
    

    当触发一个action时,todoApp通过combineReducers将会返回下面两个reducers:

     let nextTodos = todos(state.todos, action)
     let nextVisibleTodoFilter = visibleTodoFilter(state.visibleTodoFilter, action)
    

    然后它将所有的结果合并到一个的单一的state tree中:

    return {
       todos: nextTodos,
       visibleTodoFilter: nextVisibleTodoFilter
     }
    

    combineReducers()是一个很方便的函数,可以充分利用它

    1. Redux store 将root reducer返回的state tree保存起来
      这个新的state tree 就是app的下一个 state tree,接下来,每一个通过store.subscribe(listener)注册的listener都将被调用,listener可以通过调用store.getstate()去获取最新的state tree

    现在,如果使用了react,那么可以通过component.setState(newState)来更新UI

    与React配合使用

    展示和容器组件(Presentational and Container Component)

    Presentational Component Container Component
    Purpose How thing look(markup, styles) How thing work(data fetching,data updates
    Awre of Redux No Yes
    To read data Read data from pops Subscribe to Redux data
    To change data Invoke callbacks from props Dispatch Redux actions
    Are written By hand Usually generated by React Redux

    注意一点:Redux配合React使用时,加强了展示与容器组件分离这一原则
    关于两者的对比如下:

    Presentational Component Container Component
    Purpose How thing look(markup, styles) How thing work(data fetching,data updates
    Awre of Redux No Yes
    To read data Read data from pops Subscribe to Redux data
    To change data Invoke callbacks from props Dispatch Redux actions
    Are written By hand Usually generated by React Redux

    大多数情况下,我们写的都是Presentation Component,而展示组件本身并不存储数据,它通过Props来获得数据,因此,还需要编写一些Container Component,通过它从Redux获取数据,并传给Presentation Component;注意:虽然我们可以将store.subscribe()写入Container component来获取数据,但是不推荐这样做,因为Redux在内部做了很多性能优化,而这些优化是我们手动无法达成的,最好的办法是:使用Redux提供的connect()方法来生成,而不是写入Container component

    Designing Component Hierarchy

    设计组件的层级时,我们要将其匹配root state object,在设计之前,对功能做一个简短的陈述很有必要

    设计Presentation component

    几个原则:

    • 当在设计展示组件时,不要考虑它如何获取数据,例如它是如何绑定Redux,先不需要考虑这些j,这样便可以将其与Container component 分离开来,这些Presentation component就成为一个独立的组件,没有任何牵连,提供了它的复用性
    • 一般来说,Presentation component 本身是不保持状态的(stateless component)或者说是不存储数据,所以,我们在最开始可以将它们设计为一个function而不是class,当随着程序的扩展,它们需要local state 或者使用 lifecycle methods,我们可以将它们转换为class

    Implementing Container Components

    一般说来,Container componennt是一个React组件,它主要通过调用store.subscribe()来从Redux中获取state,并通过props将state传入到它负责渲染的Presentation component中,然后Presentation component 根据传入的props进行渲染;虽然,我们可以自己编写Container component,但是推荐使用Redux提供的connetc()来进行编写。

    对于Container component 来说,它与Redux进行通信,无非就是两种操作:

    1. 从Redux获取state,然后将state 传输到 Presentation component中
    2. 因为Presentation component是最接近用户的,因此肯定需要处理一些用户的操作,当用户操作了,便需要对Redux中的state 进行更新,而Presentation component不直接对state进行更新,而是通过调用dispatch来分发事件来对state进行更新,而这个事件也是通过Container来定义,并通过props传递个Presentation component

    React是基于MVC架构的,所以可以通过MVC模型来理解这个过程:
    Presentation component: 是View层
    Container component:是Controller层
    Redux:是Model层

    View层不能直接与Model层进行交互,只能通过Controller来连接View层和Model层
    明确这点之后,再看我们这个例子,我们在Container component 中定义了一个方法来从Redux中获取数据(Controller 从 Model中取数据),然后,我们需要将这个数据传输到Presentation component(Controller 传输数据到 View层),Redux提供了一个接口mapStateToProps,来便于我们高效的进行传输,这是第一种操作。

    Presentation component(View层)是最接近用户的,因此,它需要处理用户的操作;而我们知道Presentation component是不保存数据,而用户的操作可能需要更改数据,因此它需要通过Container component来处理Presentation component的操作,对Redux中的数据进行更改。

    对应到这个例子中就是:我们在Container component中定义一些事件处理函数,并将其绑定到Presentation component中(通过props传递),在Redux中,对数据更新是通过store.dispatch()来分发action处理的, 所以我们的事件处理函数就是要通过store.dispatch()来分发action;Redux提供了一个接口mapDispacthToProps(),来便于我们将dispatch通过props传输到Presentation component中

    View层要处理用户的操作,这个过程就是View 通知 Controller 有事件发生了,Controller再通知Redux进行数据更新,

    从Redux获取数据
    通过 mapStateToProps 将数据传入Presentation component中
    为了使用connect(),我们需要定义一个叫做mapStateToProps的函数,该函数会告诉我们如何将当前的Redux store state 传递到该Container component 负责渲染的 presentation component的props中

    例如:VisibleTodoList组件(它是Container component)需要根据传入的TodoList计算出todos,因此,我们定义一个函数,它根据state.visibilityFilter来过滤出state.todos,然后在它的mapStateToProps中调用它,如下:

    const getVisibleTodos = (todos, filter) => {
      switch (filter) {
        case 'SHOW_ALL':
          return todos
        case 'SHOW_COMPLETED':
          return todos.filter(t => t.completed)
        case 'SHOW_ACTIVE':
          return todos.filter(t => !t.completed)
      }
    }
    
    const mapStateToProps = state => {
      return {
        todos: getVisibleTodos(state.todos, state.visibilityFilter)
      }
    }
    

    分发事件来更新Redux

    const mapDispatchToProps = dispatch => {
      return {
        onTodoClick: id => {
          dispatch(toggleTodo(id))
        }
      }
    }
    
    

    通过 connnet() 来传输mapStateToPropsmapDispatchToProps

    import { connect } from 'react-redux'
    
    const VisibleTodoList = connect(
      mapStateToProps,
      mapDispatchToProps
    )(TodoList)
    
    export default VisibleTodoList
    

    这个便是我们的一个 Container component,也就是Controller 层

    containers/VisibleTodoList.js

    import { connect } from 'react-redux'
    import { toggleTodo } from '../actions'
    import TodoList from '../components/TodoList'
    
    const getVisibleTodos = (todos, filter) => {
      switch (filter) {
        case 'SHOW_ALL':
          return todos
        case 'SHOW_COMPLETED':
          return todos.filter(t => t.completed)
        case 'SHOW_ACTIVE':
          return todos.filter(t => !t.completed)
      }
    }
    
    const mapStateToProps = state => {
      return {
        todos: getVisibleTodos(state.todos, state.visibilityFilter)
      }
    }
    
    const mapDispatchToProps = dispatch => {
      return {
        onTodoClick: id => {
          dispatch(toggleTodo(id))
        }
      }
    }
    
    const VisibleTodoList = connect(
      mapStateToProps,
      mapDispatchToProps
    )(TodoList)
    
    export default VisibleTodoList
    

    关于connect()方法的使用

    connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
    作用:将React 组件与Redux store 连接起来
    它不会更改传入的组件,而是将一个与Redux绑定的新的组件返回给你

    Arguments:

    • [mapStateToProps(state, [ownProps]):stateProps](function):如果这个参数被指定,那么返回的新的组件将会subscribe(订阅) Redux store的更新,也就是说不管Redux store什么时候被更新,mapStateToProps都将会被调用,它是一个纯对象,会被合并新组件的props上,如果你不想要订阅 Redux store 的更新,那么就在该参数位置填写: nullundefined

    • [mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function):

    相关文章

      网友评论

          本文标题:Redux基础

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