Redux探索之路

Redux简介

Redux从设计之初就不是为了编写最短、最快的代码,他是为了解决 “当有确定的状态发生改变时,数据从哪里来” 这种可预测行为的问题的,所以当你使用Redux传值时,代码或许会很复杂,这时候请你不要去怀疑是不是不该使用Redux。

Redux和Vuex一样 是 JavaScript 状态容器,提供可预测化的状态管理。实际学习起这两种状态管理器来,我自个感觉Redux相对难学一点,Redux不止可以用于React的状态管理,它也可以用于Vue,只是当他用于React的时候,还必须依 React 绑定库开发者工具

npm install --save react-redux
npm install --save-dev redux-devtools

它和Vuex一样有着store 理念,且action都不能直接修改state上面的状态,Readux必须借助于reducers,所不同的是,Vuex里面是由action发起,然后再调用mutation修改(此处直接表明修改state上的何种属性);Redux则直接由action修改,但必须由reducers去根据action.type去判断修改的是state上面的哪个属性。

Redux也不支持多个Store,它只有一个单一的 store 和一个根级的 reduce 函数(reducer),随着应用不断变大,你应该把根级的 reducer 拆成多个小的 reducers,分别独立地操作 state 树的不同部分,而不是添加新的 stores。这就像一个 React 应用只有一个根级的组件,这个根组件又由很多小组件构成。

为什么要用状态管理

当你一个state变化需要引起第一个view视图层改变,接着第一个的改变引起第二个,以此类推,如果这个过程涉及到的组件很多,那么交给你的任务将很庞大,太繁琐,往深了说如果这个过程是可逆的,那将是你的噩梦,而Redux的出现就是来解决这个问题的。

核心概念

state

可以把它理解成一个普通的对象,由键值对构成,并且对键值对不做任何类型限制;
state是只读的,唯一改变 state 的方法就是触发 action

{ key : value , key1 : value1 }

action

它也是一个普通的JS对象,只不过他有固定的键type
强制使用 action 来描述所有变化带来的好处是可以清晰地知道应用中到底发生了什么。如果一些东西改变了,就可以知道为什么变。action 就像是描述发生了什么的指示器

{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }

reducer

最终,为了把 action 和 state 串起来,开发一些函数,就需要用到 reducer。

//其一
 function userInfo (state = [],action){
    switch (action.type){
        case actionType.USERINFO_LOGIN:
            return action.data;
        case actionType.TENANTID:
            return action.data;
        case actionType.USERINFO_MENU:
            return action.data;
        default:
            return state;
    }
}
//其二
function visibilityFilter(state = 'SHOW_ALL', action) {
  if (action.type === 'SET_VISIBILITY_FILTER') {
    return action.filter;
  } else {
    return state;
  }
}
//汇总
 function todoApp(state = {}, action) {
  return {
    userInfo : userInfo (state.userInfo , action),
    visibilityFilter: visibilityFilter(state.visibilityFilter, action)
  };
}

另一种汇总的写法

import { combineReducers, createStore } from 'redux'
let reducer = combineReducers({ visibilityFilter, userInfo })
let store = createStore(reducer)

纯函数:对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不会依赖外部环境的状态。
注意 : 永远不要在 reducer 里做这些操作:

  • 修改传入参数;
  • 执行有副作用的操作,如 API 请求和路由跳转;
  • 调用非纯函数,如 Date.now() 或 Math.random()。

方法

bindActionCreators(actionCreators, dispatch)

把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch 对每个 action creator 进行包装,以便可以直接调用它们。

一般情况下你可以直接在 Store 实例上调用 dispatch。如果你在 React 中使用 Redux,react-redux 会提供 dispatch 函数让你直接调用它 。

惟一会使用到 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 dispatch 或 Redux store 传给它。

为方便起见,你也可以传入一个函数作为第一个参数,它会返回一个函数。

参数

  1. actionCreators (Function or Object): 一个 action creator,或者一个 value 是 action creator 的对象。

  2. dispatch (Function): 一个由 Store 实例提供的 dispatch 函数。

返回值

(Function or Object): 一个与原对象类似的对象,只不过这个对象的 value 都是会直接 dispatch 原 action creator 返回的结果的函数。如果传入一个单独的函数作为 actionCreators,那么返回的结果也是一个单独的函数。

combineReducers(reducers)

combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法。

技巧

{ ...state, visibilityFilter: action.filter }

  • 缩减样板代码

1.将每个 action type 定义为 string 常量:const ADD_TODO = 'ADD_TODO';const REMOVE_TODO = 'REMOVE_TODO';
这样做的优势是:

  • 帮助维护命名一致性,因为所有的 action type 汇总在同一位置。
  • 有时,在开发一个新功能之前你想看到所有现存的 actions 。而你的团队里可能已经有人添加了你所需要的action,而你并不知道。
  • Action types 列表在 Pull Request 中能查到所有添加,删除,修改的记录。这能帮助团队中的所有人及时追踪新功能的范围与实现。
  • 如果你在 import 一个 Action 常量的时候拼写错了,你会得到 undefined 。在 dispatch 这个 action 的时候,Redux 会立即抛出这个错误,你也会马上发现错误。

2.通过创建函数生成 action 对象

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

export function editTodo(id, text) {
  return {
    type: 'EDIT_TODO',
    id,
    text
  }
}

也可以用于生成 action creator 的函数

function makeActionCreator(type, ...argNames) {
  return function(...args) {
    let action = { type }
    argNames.forEach((arg, index) => {
      action[argNames[index]] = args[index]
    })
    return action
  }
}
const ADD_TODO = 'ADD_TODO'
export const addTodo = makeActionCreator(ADD_TODO, 'todo')

问题

Redux 只能搭配 React 使用?

不是,他可以Angular、 Angular 2、 Vue、 Mithril 等框架使用。

Redux 需要特殊的编译工具支持吗?

Redux 写法遵循 ES6 语法,但在发布时被 Webpack 和 Babel 编译成了 ES5,所以在使用时可以忽略 JavaScript 的编译过程。

将怎样的数据放入 Redux 的经验法则:

  • 应用的其他部分是否关心这个数据?
  • 是否需要根据需要在原始数据的基础上创建衍生数据?
  • 相同的数据是否被用作驱动多个组件?
  • 能否将状态恢复到特定时间点(在时光旅行调试的时候)?
  • 是否要缓存数据(比如:数据存在的情况下直接去使用它而不是重复去请求他)?

如何在 reducer 之间共享 state? combineReducers 是必须的吗?

要共享state,建议将state再次拆分成小模块;
combineReducers 不是 必须的,它仅仅是通过简单的 JavaScript 对象作为数据,让 state 层能与 reducer 一一关联的函数而已。

如何组织State

设计规范化的State
范式化的数据包含下面几个概念:

  • 任何类型的数据在 state 中都有自己的 “表”。
  • 任何 “数据表” 应将各个项目存储在对象中,其中每个项目的 ID 作为 key,项目本身作为 value。
  • 对单个项目的引用都应该根据存储项目的 ID 来完成。
  • ID 数组应该用于排序

一个项目允许出现多个store吗?

Flux 原始模型中一个应用有多个 “store”,每个都维护了不同维度的数据。这样导致了类似于一个 store “等待” 另一 store 操作的问题。Redux 项目中只有一个store,而且通过将 reducer 分解成多个小而美的 reducer,进而切分数据域,隐含的实现了多个store,但又避免了上述情况的发生。

为何 type 必须是字符串,或者至少可以被序列化? 为什么 action 类型应该作为常量?

无法强制序列化 action,所以 Redux 只会校验 action 是否是普通对象,以及 type 是否定义。其它的都交由你决定,但是确保数据是可序列化将对调试以及问题的重现有很大帮助
虽然可以在任何地方手动创建 action 对象、手动指定 type 值,定义常量的方式使得代码的维护更为方便。

是否存在 reducer 和 action 之间的一对一映射?

不存在。建议的方式是编写独立且很小的 reducer 方法去更新指定的 state 部分,这种模式被称为 “reducer 合成”。一个指定的 action 也许被它们中的全部、部分、甚至没有一个处理到。这种方式把组件从实际的数据变更中解耦,一个 action 可能影响到 state 树的不同部分,对组件而言再也不必知道这些了。有些用户选择将它们紧密绑定在一起,就像 “ducks” 文件结构,显然是没有默认的一对一映射。所以当你想在多个 reducer 中处理同一个 action 时,应当避免此类结构。

为何组件没有被重新渲染、或者 mapStateToProps 没有运行?

如果直接返回同一对象,即使你改变了数据内容,Redux 也会认为没有变化。

为何不在被连接的组件中使用 this.props.dispatch?

connect() 方法有两个主要的参数,而且都是可选的。第一个参数 mapStateToProps 是个函数,让你在数据变化时从 store 获取数据,并作为 props 传到组件中。第二个参数 mapDispatchToProps 依然是函数,让你可以使用 store 的 dispatch 方法,通常都是创建 action 创建函数并预先绑定,那么在调用时就能直接分发 action。

如果在执行 connect() 时没有指定 mapDispatchToProps 方法,React Redux 默认将 dispatch 作为 prop 传入。所以当你指定方法时, dispatch 将 不 会自动注入。如果你还想让其作为 prop,需要在 mapDispatchToProps 实现的返回值中明确指出。

store 里能直接通过 store.dispatch() 调用 dispatch() 方法,但是多数情况下你会使用 react-redux 提供的 connect() 帮助器来调用。bindActionCreators() 可以自动把多个 action 创建函数 绑定到 dispatch() 方法上。

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

推荐阅读更多精彩内容