深入Redux架构

关于redux

之前写了一篇通过一个demo了解Redux,但对于redux的核心方法没有进行深入剖析,在此重新总结学习,完整的代码看这里。(参考了React 技术栈系列教程)

什么情况需要用redux?

  • 用户的使用方式复杂
  • 不同身份的用户有不同的使用方式(比如普通用户和管理员)
  • 多个用户之间可以协作
  • 与服务器大量交互,或者使用了WebSocket
  • View要从多个来源获取数据

简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。多交互、多数据源场景就比较适合使用Redux。

设计思想:

  • Web 应用是一个状态机,视图与状态是一一对应的。
  • 所有的状态,保存在一个对象里面。

Redux工作流程:

首先,用户发出 Action。

store.dispatch(action);

然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。

let nextState = todoApp(previousState, action);

State 一旦有变化,Store 就会调用监听函数。

// 设置监听函数store.subscribe(listener);

listener可以通过store.getState()得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。

function listerner() {
  let newState = store.getState();
  component.setState(newState);   
}

如果现在没理解以上流程,不要急,看完以下API就差不多能懂得Redux的核心机制了。

回到顶部(go to top)

API

Store

Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。

Redux 提供createStore这个函数,用来生成 Store。

下面代码中,createStore函数接受另一个函数作为参数,返回新生成的 Store 对象。

import { createStore } from ‘redux‘;
const store = createStore(fn);

State

Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。

当前时刻的 State,可以通过store.getState()拿到。

import { createStore } from ‘redux‘;
const store = createStore(fn);

const state = store.getState();

Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样,反之亦然。

Action

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

Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。其他属性可以自由设置,社区有一个规范可以参考。

const action = {
  type: ‘ADD_TODO‘,
  payload: ‘Learn Redux‘};

上面代码中,Action 的名称是ADD_TODO,它携带的信息是字符串Learn Redux。

可以这样理解,Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store。

Action Creator

View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator。

const ADD_TODO = ‘添加 TODO‘;function addTodo(text) {  return {
    type: ADD_TODO,
    text
  }
}

const action = addTodo(‘Learn Redux‘);

store.dispatch()

store.dispatch()是 View 发出 Action 的唯一方法。

import { createStore } from ‘redux‘;
const store = createStore(fn);

store.dispatch({
  type: ‘ADD_TODO‘,
  payload: ‘Learn Redux‘});

上面代码中,store.dispatch接受一个 Action 对象作为参数,将它发送出去。

结合 Action Creator,这段代码可以改写如下。

store.dispatch(addTodo(‘Learn Redux‘));

Reducer

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

Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。下面是一个实际的例子

const defaultState = 0;
const reducer = (state = defaultState, action) => {  switch (action.type) {    case ‘ADD‘:      return state + action.payload;    default: 
      return state;
  }
};

const state = reducer(1, {
  type: ‘ADD‘,
  payload: 2});

上面代码中,reducer函数收到名为ADD的 Action 以后,就返回一个新的 State,作为加法的计算结果。其他运算的逻辑(比如减法),也可以根据 Action 的不同来实现。

实际应用中,Reducer 函数不用像上面这样手动调用,store.dispatch方法会触发 Reducer 的自动执行。为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。

import { createStore } from ‘redux‘;
const store = createStore(reducer);

上面代码中,createStore接受 Reducer 作为参数,生成一个新的 Store。以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。

store.subscribe()

Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。

import { createStore } from ‘redux‘;
const store = createStore(reducer);

store.subscribe(listener);

显然,只要把 View 的更新函数(对于 React 项目,就是组件的render方法或setState方法)放入listen,就会实现 View 的自动渲染。

store.subscribe方法返回一个函数,调用这个函数就可以解除监听。

let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
);

unsubscribe();

回到顶部(go to top)

中间件与异步操作

一个关键问题没有解决:异步操作怎么办?Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。

怎么才能 Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件(middleware)。

为了理解中间件,让我们站在框架作者的角度思考问题:如果要添加功能,你会在哪个环节添加?

(1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。

(2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。

(3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。

想来想去,只有发送 Action 的这个步骤,即store.dispatch()方法,可以添加功能。

中间件的用法

本文不涉及如何编写中间件,因为常用的中间件都有现成的,只要引用别人写好的模块即可。比如,上一节的日志中间件,就有现成的redux-logger模块。这里只介绍怎么使用中间件。

import { applyMiddleware, createStore } from ‘redux‘;
import createLogger from ‘redux-logger‘;
const logger = createLogger();

const store = createStore(
  reducer,
  applyMiddleware(logger)
);

上面代码中,redux-logger提供一个生成器createLogger,可以生成日志中间件logger。然后,将它放在applyMiddleware方法之中,传入createStore方法,就完成了store.dispatch()的功能增强。

这里有两点需要注意:

(1)createStore方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware就是第三个参数了。

const store = createStore(
  reducer,
  initial_state,
  applyMiddleware(logger)
);

(2)中间件的次序有讲究。

const store = createStore(
  reducer,
  applyMiddleware(thunk, promise, logger)
);

上面代码中,applyMiddleware方法的三个参数,就是三个中间件。有的中间件有次序要求,使用前要查一下文档。比如,logger就一定要放在最后,否则输出结果会不正确。

回到顶部(go to top)

异步操作的基本思路

理解了中间件以后,就可以处理异步操作了。

同步操作只要发出一种 Action 即可,异步操作的差别是它要发出三种 Action。

  • 操作发起时的 Action
  • 操作成功时的 Action
  • 操作失败时的 Action

以向服务器取出数据为例,三种 Action 可以有两种不同的写法。

// 写法一:名称相同,参数不同{ type: ‘FETCH_POSTS‘ }
{ type: ‘FETCH_POSTS‘, status: ‘error‘, error: ‘Oops‘ }
{ type: ‘FETCH_POSTS‘, status: ‘success‘, response: { ... } }// 写法二:名称不同{ type: ‘FETCH_POSTS_REQUEST‘ }
{ type: ‘FETCH_POSTS_FAILURE‘, error: ‘Oops‘ }
{ type: ‘FETCH_POSTS_SUCCESS‘, response: { ... } }

除了 Action 种类不同,异步操作的 State 也要进行改造,反映不同的操作状态。下面是 State 的一个例子。

let state = {  // ... 
  isFetching: true,
  didInvalidate: true,
  lastUpdated: ‘xxxxxxx‘};

上面代码中,State 的属性isFetching表示是否在抓取数据。didInvalidate表示数据是否过时,lastUpdated表示上一次更新时间。

现在,整个异步操作的思路就很清楚了。

  • 操作开始时,送出一个 Action,触发 State 更新为"正在操作"状态,View 重新渲染
  • 操作结束后,再送出一个 Action,触发 State 更新为"操作结束"状态,View 再一次重新渲染

redux-thunk中间件

异步操作至少要送出两个 Action:用户触发第一个 Action,这个跟同步操作一样,没有问题;如何才能在操作结束时,系统自动送出第二个 Action 呢?

奥妙就在 Action Creator 之中。

class AsyncApp extends Component {
  componentDidMount() {
    const { dispatch, selectedPost } = this.props
    dispatch(fetchPosts(selectedPost))
  }// ...

上面代码是一个异步组件的例子。加载成功后(componentDidMount方法),它送出了(dispatch方法)一个 Action,向服务器要求数据 fetchPosts(selectedSubreddit)。这里的fetchPosts就是 Action Creator。

下面就是fetchPosts的代码,关键之处就在里面。

const fetchPosts = postTitle => (dispatch, getState) => {
  dispatch(requestPosts(postTitle));  return fetch(`/some/API/${postTitle}.json`)
    .then(response => response.json())
    .then(json => dispatch(receivePosts(postTitle, json)));
  };
};// 使用方法一store.dispatch(fetchPosts(‘reactjs‘));// 使用方法二store.dispatch(fetchPosts(‘reactjs‘)).then(() =>
  console.log(store.getState())
);

上面代码中,fetchPosts是一个Action Creator(动作生成器),返回一个函数。这个函数执行后,先发出一个Action(requestPosts(postTitle)),然后进行异步操作。拿到结果后,先将结果转成 JSON 格式,然后再发出一个 Action( receivePosts(postTitle, json))。

上面代码中,有几个地方需要注意。

(1)fetchPosts返回了一个函数,而普通的 Action Creator 默认返回一个对象。

(2)返回的函数的参数是dispatch和getState这两个 Redux 方法,普通的 Action Creator 的参数是 Action 的内容。

(3)在返回的函数之中,先发出一个 Action(requestPosts(postTitle)),表示操作开始。

(4)异步操作结束之后,再发出一个 Action(receivePosts(postTitle, json)),表示操作结束。

这样的处理,就解决了自动发送第二个 Action 的问题。但是,又带来了一个新的问题,Action 是由store.dispatch方法发送的。而store.dispatch方法正常情况下,参数只能是对象,不能是函数。

这时,就要使用中间件redux-thunk。

import { createStore, applyMiddleware } from ‘redux‘;
import thunk from ‘redux-thunk‘;
import reducer from ‘./reducers‘;// Note: this API requires [email protected]>=3.1.0const store = createStore(
  reducer,
  applyMiddleware(thunk)
);

上面代码使用redux-thunk中间件,改造store.dispatch,使得后者可以接受函数作为参数。

因此,异步操作的第一种解决方案就是,写出一个返回函数的 Action Creator,然后使用redux-thunk中间件改造store.dispatch。

回到顶部(go to top)

React-Redux的用法

为了方便使用,Redux 的作者封装了一个 React 专用的库 React-Redux,本文主要介绍它。

这个库是可以选用的。实际项目中,你应该权衡一下,是直接使用 Redux,还是使用 React-Redux。后者虽然提供了便利,但是需要掌握额外的 API,并且要遵守它的组件拆分规范。

React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。

UI组件

UI 组件有以下几个特征。

  • 只负责 UI 的呈现,不带有任何业务逻辑
  • 没有状态(即不使用this.state这个变量)
  • 所有数据都由参数(this.props)提供
  • 不使用任何 Redux 的 API

下面就是一个 UI 组件的例子。

const Title =
  value => <h1>{value}</h1>;

因为不含有状态,UI 组件又称为"纯组件",即它纯函数一样,纯粹由参数决定它的值。

容器组件

容器组件的特征恰恰相反。

  • 负责管理数据和业务逻辑,不负责 UI 的呈现
  • 带有内部状态
  • 使用 Redux 的 API

总之,只要记住一句话就可以了:UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。

你可能会问,如果一个组件既有 UI 又有业务逻辑,那怎么办?回答是,将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。

React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。

connect()

React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。

connect方法的完整 API 如下。

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

上面代码中,TodoList是 UI 组件,VisibleTodoList就是由 React-Redux 通过connect方法自动生成的容器组件。connect方法接受两个参数:mapStateToProps和mapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。

mapStateToProps

mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。

作为函数,mapStateToProps执行后应该返回一个对象,里面的每一个键值对就是一个映射。请看下面的例子。

const mapStateToProps = (state) => {  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

上面代码中,mapStateToProps是一个函数,它接受state作为参数,返回一个对象。这个对象有一个todos属性,代表 UI 组件的同名参数,后面的getVisibleTodos也是一个函数,可以从state算出 todos 的值。

下面就是getVisibleTodos的一个例子,用来算出todos。

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)    default:      throw new Error(‘Unknown filter: ‘ + filter)
  }
}

mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。

mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象。

// 容器组件的代码//    <FilterLink filter="SHOW_ALL">//      All//    </FilterLink>const mapStateToProps = (state, ownProps) => {  return {
    active: ownProps.filter === state.visibilityFilter
  }
}

使用ownProps作为参数后,如果容器组件的参数发生变化,也会引发 UI 组件重新渲染。

connect方法可以省略mapStateToProps参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。

mapDispatchToProps()

mapDispatchToProps是connect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。

如果mapDispatchToProps是一个函数,会得到dispatch和ownProps(容器组件的props对象)两个参数。

const mapDispatchToProps = (
  dispatch,
  ownProps
) => {  return {
    onClick: () => {
      dispatch({
        type: ‘SET_VISIBILITY_FILTER‘,
        filter: ownProps.filter
      });
    }
  };
}

从上面代码可以看到,mapDispatchToProps作为函数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action。

如果mapDispatchToProps是一个对象,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出。举例来说,上面的mapDispatchToProps写成对象就是下面这样。

const mapDispatchToProps = {
  onClick: (filter) => {
    type: ‘SET_VISIBILITY_FILTER‘,
    filter: filter
  };
}

<Provider>组件

connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。React-Redux 提供Provider组件,可以让容器组件拿到state。

import { Provider } from ‘react-redux‘import { createStore } from ‘redux‘import todoApp from ‘./reducers‘import App from ‘./components/App‘let store = createStore(todoApp);

render(  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById(‘root‘)
)

上面代码中,Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。

React-Router路由库

使用React-Router的项目,与其他项目没有不同之处,也是使用Provider在Router外面包一层,毕竟Provider的唯一功能就是传入store对象。

const Root = ({ store }) => (  <Provider store={store}>
    <Router>
      <Route path="/" component={App} />
    </Router>
  </Provider>
);

时间: 2024-11-15 01:29:30

深入Redux架构的相关文章

基于Redux架构的单页应用开发总结(二)

写在前面 这次重点介绍基于Redux架构的单页应用代码的组织方式 关于less的组织 作为一个后端出身的前端工程师,写简单的css实在没有那种代码可配置和结构化的快感.所以引入less是个不错的选择,无论是针对代码后期的管理,还是提高代码的复用能力. global.less 这个是全局都可以调用的方法库,我习惯把 项目的配色.各种字号.用于引入混出的方法等写在这里,其他container页面通过@import方式引入它,就可以使用里面的东西.不过定义它时要注意以下两点: 第一,这个less里只能

在 React Native 中使用 Redux 架构

前言 Redux 架构是 Flux 架构的一个变形,相对于 Flux,Redux 的复杂性相对较低,而且最为巧妙的是 React 应用可以看成由一个根组件连接着许多大大小小的组件的应用,Redux 也只有一个 Store,而且只用一个 state 树来管理组件的状态.随着应用逐渐变得复杂,React 将组件看成状态机的优势仿佛变成了自身的绊脚石.因为要管理的状态将会越来越多,直到你搞不清楚某个状态在不知道什么时候,由于什么原因,发生了什么变化.Redux 试图让状态的变化变得可预测.Redux

Redux生态系统

生态系统 Redux 是一个体小精悍的库,但它相关的内容和 API 都是精挑细选的,足以衍生出丰富的工具集和可扩展的生态系统. 如果需要关于 Redux 所有内容的列表,推荐移步至 Awesome Redux.它包含了示例.样板代码.中间件.工具库,还有很多其它相关内容.要想学习 React 和 Redux ,React/Redux Links 包含了教程和不少有用的资源,Redux Ecosystem Links 则列出了 许多 Redux 相关的库及插件. 本页将只列出由 Redux 维护者

react+redux渲染性能优化原理

大家都知道,react的一个痛点就是非父子关系的组件之间的通信,其官方文档对此也并不避讳: For communication between two components that don't have a parent-child relationship, you can set up your own global event system. Subscribe to events in componentDidMount(), unsubscribe in componentWillU

应用数据流状态管理框架Redux简介、核心概念及工作流

前几天给大家谈了谈React 不过它只是一个侧重于UI的框架 只能算作是MVC中的V(View视图) 而且只是DOM的一个抽象层,不是Web应用完整解决方案 如果仅仅用它构建大型项目 你会非常的吃力 简介 14年,Facebook提出Flux架构意图解决这个问题 15年,Dan Abramov将 Flux 与函数式编程相结合,创造了Redux,由于简单易学就开始流行起来 16年,Dan Abramov被facebook挖走了 Redux体积很小,如果删掉源码的空行和注释,连500行代码都不到 别

Unity 之 Redux 模式 (Flux)

作者:软件猫 日期:2016年12月6日 转载请注明出处:http://www.cnblogs.com/softcat/p/6135195.html 在朋友的怂恿下,终于开始学 Unity 了,于是有了这篇文章. 本文讲述了如何在 Unity 中实现 Redux 架构. 关于 Flux.单向数据流是什么,请参考 阮一峰 大大的文章 http://www.ruanyifeng.com/blog/2016/01/flux.html Redux 是什么鬼 Reflux是根据 Facebook 提出的

从零开始的Android新项目10 - React Native &amp; Redux

本篇来讲讲 React Native 和 Redux,和其他一上来就啪啪啪丢上来一堆翻译的东西不同,本文会从简单的例子入手,让大家能快速地明白 React Native 是什么,Redux 和常见的 MVC.MVP 等有什么区别,怎么去组织一个 Redux 架构的 React Native 项目. 为避免大家还没入门就放弃,预计下一篇才会从我们项目中的实践出发,讲讲更复杂的应用场景. 什么是React Native React Native 使你能够基于 JavaScript 和 React 在

【前端】react学习阶段总结,学习react、react-router与redux的这些事儿

前言 借用阮一峰的一句话:真正学会 React 是一个漫长的过程. 这句话在我接触react深入以后,更有感触了.整个react体系都是全新的,最初做简单的应用,仅仅使用react-tools打包jsx时,我被react的函数式语法吸引,从而跳入这个圈子.一直到搭建webpack.react.react-router.redux架构,做了几个SPA项目,我还是感觉自己懂的太少,还在第一阶段徘徊,在这里暂时做一个阶段性总结,接下来将继续更深入了解react——不排除再转回vue.嘿嘿. 函数式组件

深入浅出React和Redux pdf

下载地址:网盘下载 内容简介  · · · · · · 本书作者是资深开发人员,有过多年的开发经验,总结了自己使用React和Redux的实战经验,系统分析React和Redux结合的优势,与开发技巧,为开发大型系统提供参考.主要内容包括:React的基础知识.如何设计易于维护的React组件.如何使用Redux控制数据流.React和Redux的相结合的方式.同构的React和Redux架构.React和Redux的性能优化.组件的测试等. 作者简介  · · · · · · 程墨 资深架构师