Redux基础

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):

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容