Redux 简介

Redux

Store

创建

Redux 是一个状态管理框架,可以与包括 React 在内的许多不同的 Web 技术一起使用。

在 Redux 中,有一个状态对象负责应用程序的整个状态, 这意味着如果有一个包含十个组件且每个组件都有自己的本地状态的 React 项目,那么这个项目的整个状态将通过 Redux store 被定义为单个状态对象, 这是学习 Redux 时要理解的第一个重要原则:Redux store 是应用程序状态的唯一真实来源。

这也意味着,如果应用程序想要更新状态,只能通过 Redux store 执行, 单向数据流可以更轻松地对应用程序中的状态进行监测管理。

Redux store 是一个保存和管理应用程序状态的state, 可以使用 Redux 对象中的 createStore() 来创建一个 redux store, 此方法将 reducer 函数作为必需参数, 它只需将 state 作为参数并返回一个 state 即可。

const reducer = (state = 5) => {
  return state;
}
let store = Redux.createStore(reducer);

获取状态

Redux store 对象提供了几种与之交互的方法, 比如,可以使用 getState() 方法检索 Redux store 对象中保存的当前 state

const store = Redux.createStore(
  (state = 5) => state
);

let currentState = store.getState();

Store 监听器

在 Redux store 对象上访问数据的另一种方法是 store.subscribe()。 这允许将监听器函数订阅到 store,只要 action 被 dispatch 就会调用它们。 这个方法的一个简单用途是为 store 订阅一个函数,它只是在每次收到一个 action 并且更新 store 时记录一条消息。

const ADD = 'ADD';

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

const store = Redux.createStore(reducer);

let count = 0;
const addOne = () => (count += 1);
store.subscribe(addOne);

store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);

Action

由于 Redux 是一个状态管理框架,因此更新状态是其核心任务之一。 在 Redux 中,所有状态更新都由 dispatch action 触发, action 只是一个 JavaScript 对象,其中包含有关已发生的 action 事件的信息。 Redux store 接收这些 action 对象,然后更新相应的状态。 有时,Redux action 也会携带一些数据。 例如,在用户登录后携带用户名, 虽然数据是可选的,但 action 必须带有 type 属性,该属性表示此 action 的类型。

可以将 Redux action 视为信使,将有关应用程序中发生的事件信息提供给 Redux store, 然后 store 根据发生的 action 进行状态的更新。

let action = {
  type: 'LOGIN'
}

Action Creator

创建 action 后要将 action 发送到 Redux store,以便它可以更新其状态。 在 Redux 中,可以定义动作创建器来完成此任务, action creator 只是一个返回动作的 JavaScript 函数。 换句话说,action creator 创建表示动作事件的对象。

function actionCreator() {
  return action;
}

Action Event

dispatch 方法用于将 action 分派给 Redux store, 调用 store.dispatch() 将从 action creator 返回的值发送回 store。

下面的行是等效的,两者都会调度类 LOGIN 类型的 action:

store.dispatch(actionCreator());
store.dispatch({ type: 'LOGIN' });
const store = Redux.createStore(
  (state = {login: false}) => state
);

const loginAction = () => {
  return {
    type: 'LOGIN'
  }
};

store.dispatch(loginAction());

Store 里处理 Action (Reducer)

在一个 action 被创建并 dispatch 之后,Redux store 需要知道如何响应该操作。 这就是 reducer 函数存在的意义。 Redux 中的 Reducers 负责响应 action 然后进行状态的修改。 reducerstateaction 作为参数,并且它总是返回一个新的 state。 要知道这是 reducer 的唯一的作用。 它不应有任何其他的作用:比如它不应调用 API 接口,也不应存在任何潜在的副作用。 reducer 只是一个接受状态和动作,然后返回新状态的纯函数。

Redux 的另一个关键原则是 state 是只读的。 换句话说,reducer 函数必须始终返回一个新的 state,并且永远不会直接修改状态。 Redux 不强制改变状态,但是需要在 reducer 函数的代码中强制执行它。

请注意,当前 state 和 dispatch 的 action 将被传递给 reducer,因此可以使用 action.type 直接获取 action 的类型。

const defaultState = {
  login: false
};

const reducer = (state = defaultState, action) => {
  if (action.type === 'LOGIN') return ({
    login: true
  });
  else return state;
};

const store = Redux.createStore(reducer);

const loginAction = () => {
  return {
    type: 'LOGIN'
  }
};

Switch 处理 Action

可以定义 Redux store 处理多种 action 类型。 假设在 Redux store 中管理用户身份验证。 希望用状态表示用户登录和注销。 使用 state 的 authenticated 属性表示它。 还需要使用 action creators 创建与用户登录和用户注销相对应的 action,以及 action 对象本身。

可以在 reducer 里通过使用 JavaScript 的 switch 来响应不同的 action 事件。 这是编写 Redux reducers 的标准模式。 switch 语句应该切换 action.type 并返回适当的身份验证状态。

另外,不要忘记在 switch 语句中写一个 default case,返回当前的 state。 这是很重要的,因为当程序有多个 reducer,当每一个 action 被 dispatch 时它们都会运行,即使 action 与该 reducer 无关。 在这种情况下,要确保返回当前的 state

const defaultState = {
  authenticated: false
};

const authReducer = (state = defaultState, action) => {
  switch(action.type) {
    case "LOGIN": return {authenticated: true};
    case "LOGOUT": return {authenticated: false}
    default: return state;
  }
};

const store = Redux.createStore(authReducer);

const loginUser = () => {
  return {
    type: 'LOGIN'
  }
};

const logoutUser = () => {
  return {
    type: 'LOGOUT'
  }
};

Action Types

在使用 Redux 时的一个常见做法是将操作类型指定为只读,然后在任何使用它们的地方引用这些常量。 可以通过将 action types 使用 const 声明重构正在使用的代码。

const LOGIN = "LOGIN";
const LOGOUT = "LOGOUT";


const defaultState = {
  authenticated: false
};

const authReducer = (state = defaultState, action) => {

  switch (action.type) {
    case LOGIN: 
      return {
        authenticated: true
      }
    case LOGOUT: 
      return {
        authenticated: false
      }
    default:
      return state;
  }
};

const store = Redux.createStore(authReducer);

const loginUser = () => {
  return {
    type: 'LOGIN'
  }
};

const logoutUser = () => {
  return {
    type: 'LOGOUT'
  }
};

注意: 通常以全部大写形式写出常量,这也是 Redux 的标准做法。

发送 Action Data 给 Store

到目前为止,这些 action 并未包含除 type之外的任何信息。 还可以和把 action 和特定数据一起发送。 事实上,这是非常常见的,因为 action 通常源于一些用户交互,并且往往会携带一些数据, Redux store 经常需要知道这些数据。

const ADD_NOTE = 'ADD_NOTE';

const notesReducer = (state = 'Initial State', action) => {
  switch(action.type) {
  
case ADD_NOTE: return action.text;
    
    default:
      return state;
  }
};

const addNoteText = (note) => {
 
  return {
    type: ADD_NOTE,
    text: note
  }
 
};

const store = Redux.createStore(notesReducer);

console.log(store.getState());
store.dispatch(addNoteText('Hello!'));
console.log(store.getState());

组合多个 Reducers

当应用程序的状态开始变得越来越复杂时,可能会将 state 分成多个块。 相反,请记住 Redux 的第一个原则:所有应用程序状态都保存在 store 中的一个简单的 state 对象中。 因此,Redux 提供 reducer 组合作为复杂状态模型的解决方案。 定义多个 reducer 来处理应用程序状态的不同部分,然后将这些 reducer 组合成一个 root reducer。 然后将 root reducer 传递给 Redux createStore()方法。

为了将多个 reducer 组合在一起,Redux 提供了combineReducers()方法。 该方法接受一个对象作为参数,在该参数中定义一个属性,该属性将键与特定 reducer 函数关联。 Redux 将使用给定的键值作为关联状态的名称。

通常情况下,当它们在某种程度上是独一无二的,为每个应用程序的 state 创建一个 reducer 是一个很好的做法。 例如,在一个带有用户身份验证的记笔记应用程序中,一个 reducer 可以处理身份验证而另一个处理用户提交的文本和注释。 对于这样的应用程序,可能会编写 combineReducers() 方法,如下所示:

const rootReducer = Redux.combineReducers({
  auth: authenticationReducer,
  notes: notesReducer
});
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

const counterReducer = (state = 0, action) => {
  switch(action.type) {
    case INCREMENT:
      return state + 1;
    case DECREMENT:
      return state - 1;
    default:
      return state;
  }
};

const LOGIN = 'LOGIN';
const LOGOUT = 'LOGOUT';

const authReducer = (state = {authenticated: false}, action) => {
  switch(action.type) {
    case LOGIN:
      return {
        authenticated: true
      }
    case LOGOUT:
      return {
        authenticated: false
      }
    default:
      return state;
  }
};

const rootReducer = Redux.combineReducers({
  auth: authReducer,
  count: counterReducer
});

const store = Redux.createStore(rootReducer);

中间件 Middleware

目前为止的挑战都在避免讨论异步操作,但它们是 Web 开发中不可避免的一部分。 在某些时候,需要在 Redux 应用程序中使用异步请求,那么如何处理这些类型的请求? Redux 中间件专为此目的而设计,称为 Redux Thunk 中间件。 这里简要介绍如何在 Redux 中使用它。

如果要使用 Redux Thunk 中间件,请将其作为参数传递给 Redux.applyMiddleware()。 然后将此函数作为第二个可选参数提供给 createStore() 函数, 看一下编辑器底部的代码。 然后,要创建一个异步的 action,需要在 action creator 中返回一个以 dispatch 为参数的函数。 在这个函数中,可以 dispatch action 并执行异步请求。

在此示例中,使用 setTimeout() 模拟异步请求。 通常在执行异步行为之前 dispatch action,以便应用程序状态知道正在请求某些数据(例如,这个状态可以显示加载图标)。 然后,一旦收到数据,就会发送另一个 action,该 action 的 data 是请求返回的数据同时也代表 API 操作完成。

请记住,正在将 dispatch 作为参数传递给这个特殊的 action creator。 需要 dispatch action 时只需将 action 直接传递给 dispatch,中间件就可以处理其余的部分。

const REQUESTING_DATA = 'REQUESTING_DATA'
const RECEIVED_DATA = 'RECEIVED_DATA'

const requestingData = () => { return {type: REQUESTING_DATA} }
const receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users} }

const handleAsync = () => {
  return function(dispatch) {
 

    dispatch(requestingData());

    setTimeout(function() {
      let data = {
        users: ["Jeff", "William", "Alice"]
      };
  

      dispatch(receivedData(data));
    }, 2500);
  }
};

const defaultState = {
  fetching: false,
  users: []
};

const asyncDataReducer = (state = defaultState, action) => {
  switch(action.type) {
    case REQUESTING_DATA:
      return {
        fetching: true,
        users: []
      }
    case RECEIVED_DATA:
      return {
        fetching: false,
        users: action.users
      }
    default:
      return state;
  }
};

const store = Redux.createStore(
  asyncDataReducer,
  Redux.applyMiddleware(ReduxThunk.default)
);

计数器

const INCREMENT = 'INCREMENT'; 
const DECREMENT = 'DECREMENT'; 

const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case INCREMENT:
      return state + 1;

    case DECREMENT:
      return state - 1;

    default:
      return state;
  }
};

const incAction = () => {
  return {
    type: INCREMENT
  };
}; 

const decAction = () => {
  return {
    type: DECREMENT
  };
}; 

const store = Redux.createStore(counterReducer); 

永不改变状态 Never Mutate State

最后的几个例子描述了在 Redux 中强制执行状态不变性关键原则的几种方法。 不可变状态意味着永远不直接修改状态,而是返回一个新的状态副本。

如果拍摄 Redux 应用程序一段时间状态的快照,会看到类似 state 1state 2state 3state 4...等等,每个状态可能与最后一个状态相似,但每个状态都是一个独特的数据。 事实上,这种不变性提供了时间旅行调试等功能。

Redux 并没有主动地在其 store 或者 reducer 中强制执行状态不变性,责任落在程序员身上。 幸运的是,JavaScript(尤其是 ES6)提供了一些有用的工具,可以用来强制执行状态的不变性,无论是 stringnumberarrayobject。 请注意,字符串和数字是原始值,并且本质上是不可变的。 换句话说,3 总是 3, 不能改变数字 3 的值。 然而,arrayobject 是可变的。 实际上,状态可能会包括 arrayobject,因为它们经常用来描述一些代表信息的数据结构。

const ADD_TO_DO = 'ADD_TO_DO';

const todos = [
  'Go to the store',
  'Clean the house',
  'Cook dinner',
  'Learn to code',
];

const immutableReducer = (state = todos, action) => {
  switch(action.type) {
    case ADD_TO_DO:
  return state.concat(action.todo);
 
      return
    default:
      return state;
  }
};

const addToDo = (todo) => {
  return {
    type: ADD_TO_DO,
    todo
  }
}

const store = Redux.createStore(immutableReducer);

数组中使用扩展运算符

ES6 中有助于在 Redux 中强制执行状态不变性的一个解决方案是扩展运算符:...。 扩展运算符具有很多的应用,其中一种非常适合通过一个已有的数组生成一个新数组。 这是相对较新的但常用的语法。

const immutableReducer = (state = ['Do not mutate state!'], action) => {
  switch(action.type) {
    case 'ADD_TO_DO': return [...state, action.todo]
     
    default:
      return state;
  }
};

const addToDo = (todo) => {
  return {
    type: 'ADD_TO_DO',
    todo
  }
}

const store = Redux.createStore(immutableReducer);

从数组中删除项目

const immutableReducer = (state = [0,1,2,3,4,5], action) => {
  switch(action.type) {
    case 'REMOVE_ITEM':
    
       return [
        ...state.slice(0, action.index),
        ...state.slice(action.index + 1, state.length)
      ];

    default:
      return state;
  }
};

const removeItem = (index) => {
  return {
    type: 'REMOVE_ITEM',
    index
  }
}

const store = Redux.createStore(immutableReducer);

Object.assign()

最后几个挑战适用于数组,但是当状态是 object 时,有一些方法可以实现状态不变性。 处理对象的一个常用的方法是 Object.assign()Object.assign() 获取目标对象和源对象,并将源对象中的属性映射到目标对象。 任何匹配的属性都会被源对象中的属性覆盖。 通常用于通过传递一个空对象作为第一个参数,然后是要用复制的对象来制作对象的浅表副本。 下面是一个示例:

const newObject = Object.assign({}, obj1, obj2);
const defaultState = {
  user: 'CamperBot',
  status: 'offline',
  friends: '732,982',
  community: 'freeCodeCamp'
};

const immutableReducer = (state = defaultState, action) => {
  switch(action.type) {
    case 'ONLINE':
      return Object.assign({}, state, {status: "online"});
    default:
      return state;
  }
};

const wakeUp = () => {
  return {
    type: 'ONLINE'
  }
};

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

推荐阅读更多精彩内容

  • 写在开头 前置知识内容,闭包,高阶函数,函数式编程思想,redux核心概念。 redux文档:https://ww...
    前端开发爱好者阅读 692评论 0 1
  • Redux 是一个独立的 JavaScript 状态管理库,不依赖于任何其他库 1. 安装 npm i redux...
    啦啦小胸弟阅读 274评论 0 1
  • 写在开头 本片内容主要为本人在阅读redux官方文档中基础和进阶部分的学习笔记。由于本人能力有限,所以文章中可能会...
    前端开发爱好者阅读 1,192评论 0 4
  • 1.使用场景 redux虽然好,也并不是什么情况下都要使用,如果在项目中遇到一下场景,你会自发的寻找一个工具来解决...
    TingsLee阅读 814评论 0 0
  • Redux React-redux React-router Redux 1、基本用法: Redux中存在几个概念...
    子木话阅读 376评论 0 0