Redux 莞式教程 之 简明篇

Redux 简明教程

原文链接(坚持更新):https://github.com/kenberkele…

写在前面

本教程深入浅出,配套 简明教程、进阶教程(源码精读)以及文档解释饱满的 Demo 等一条龙服务

§ 为什么要用 Redux

固然另有 FluxRefluxMobx 等状况治理库可供挑选

抛开需求讲实用性都是耍流氓,因而下面由我饰演您那可亲可爱的产物司理

⊙ 需求 1:在掌握台上纪录用户的每一个行动

不晓得您是不是有后端的开辟履历,后端平常会有纪录接见日记的中间件
比方,在 Express 中完成一个简朴的 Logger 以下:

var loggerMiddleware = function(req, res, next) {
  console.log('[Logger]', req.method, req.originalUrl)
  next()
}
...
app.use(loggerMiddleware)

每次接见的时刻,都邑在掌握台中留下相似下面的日记便于追踪调试:

[Logger] GET  /
[Logger] POST /login
[Logger] GET  /user?uid=10086
...

假如我们把场景转移到前端,叨教该怎样完成用户的行动跟踪纪录?
我们可能会如许写:

/** jQuery **/
$('#loginBtn').on('click', function(e) {
  console.log('[Logger] 用户登录')
  ...
})
$('#logoutBtn').on('click', function() {
  console.log('[Logger] 用户退出登录')
  ...
})

/** MVC / MVVM 框架(这里以纯 Vue 举例) **/
methods: {
  handleLogin () {
    console.log('[Logger] 用户登录')
    ...
  },
  handleLogout () {
    console.log('[Logger] 用户退出登录')
    ...
  }
}

上述 jQuery 与 MV* 的写法并没有本质上的辨别
纪录用户行动代码的侵入性极强,可维护性与扩展性堪忧

⊙ 需求 2:在上述需求的基础上,纪录用户的操纵时候

哼!最憎恶就是改需求了,这类简朴的需求岂非不是应当一开始就想好的吗?
呵呵,假如每位产物司理都能一开始就把需求圆满好,我们就不必加班了好伐

明显地,前端的童鞋又得一个一个去改(固然 编辑器 / IDE 都支撑全局替代):

/** jQuery **/
$('#loginBtn').on('click', function(e) {
  console.log('[Logger] 用户登录', new Date())
  ...
})
$('#logoutBtn').on('click', function() {
  console.log('[Logger] 用户退出登录', new Date())
  ...
})

/** MVC / MVVM 框架(这里以 Vue 举例) **/
methods: {
  handleLogin () {
    console.log('[Logger] 用户登录', new Date())
    ...
  },
  handleLogout () {
    console.log('[Logger] 用户退出登录', new Date())
    ...
  }
}

而后端的童鞋只须要轻微修正一下本来的中间件即可:

var loggerMiddleware = function(req, res, next) {
  console.log('[Logger]', new Date(), req.method, req.originalUrl)
  next()
}
...
app.use(loggerMiddleware)

⊙ 需求 3:正式上线的时刻,把掌握台中有关 Logger 的输出悉数去掉

岂非您认为有了 UglifyJS,设置一个 drop_console: true 就好了吗?图样图森破,拿衣服!
请看清晰了,仅仅是去掉有关 Logger 的 console.log,其他的要保存哦亲~~~
因而前端的童鞋又不得不乖乖地一个一个解释掉(固然也能够设置一个环境变量推断是不是输出,以至能够重写 console.log

而我们后端的童鞋呢?只须要解释掉一行代码即可:// app.use(loggerMiddleware),真可谓是不费吹灰之力

⊙ 需求 4:正式上线后,自动网络 bug,并复原出当时的场景

网络用户报错照样比较简朴的,运用 window.error 事宜,然后依据 Source Map 定位到源码(但平常查不出什么)

但要完全复原出当时的运用场景,险些是不可能的。因为您不晓得这个报错,用户是怎样一步一步操纵得来的
就算晓得用户是怎样操纵得来的,但在您的电脑上,测试永远都是经由过程的(不是我写的顺序有题目,是用户用的体式格局有题目)

相对地,后端的报错的网络、定位以及复原倒是相称简朴。只需一个 API 有 bug,那不管用什么装备接见,都邑取得这个 bug
复原 bug 也是相称简朴:把数据库备份导入到另一台机械,布置一样的运转环境与代码。如无不测,bug 一定能够圆满重现

在这个题目上拿后端跟前端对比,确切有失公道。但为了宣扬 Redux 的优胜,只能勉为其难了

实际上 jQuery / MV* 中也能完成用户行动的跟踪,用一个数组往里面 push 用户行动即可
但如许操纵的意义不大,因为仅仅只要行动,没法反应行动前后,运用状况的更改状况

※ 小结

为什么前后端关于这类需求的处置惩罚居然天差地别?后端为什么能够云云文雅?
缘由在于,后端具有一致的进口一致的状况治理(数据库),因而能够引入中间件机制一致完成某些功用

多年来,前端工程师委曲求全,操着卖白粉的心,赚着买白菜的钱,一向处于顺序员蔑视链的底层
因而有大牛就把后端 MVC 的开辟头脑搬到前端,将运用中一切的行动与状况都一致治理,让统统有据可循

运用 Redux,借助 Redux DevTools 能够完成出“华美如时间游览平常的调试结果”
实际上就是开辟调试过程当中能够打消与重做,而且支撑运用状况的导入和导出(就像是数据库的备份)
而且,因为能够运用日记完全纪录下每一个行动,因而做到像 Git 般,随时随地恢复到之前的状况

因为能够导出和导入运用的状况(包括路由状况),因而还能够完成前后端同构(服务端衬着)
固然,既然有了行动日记以及行动前后的状况备份,那末复原用户报错场景还会是一个困难吗?

§ Store

首先要辨别 storestate

state 是运用的状况,平常本质上是一个一般对象
比方,我们有一个 Web APP,包括 计数器 和 待办事项 两大功用
那末我们可认为该运用设想出对应的存储数据结构(运用初始状况):

/** 运用初始 state,本代码块记为 code-1 **/
{
  counter: 0,
  todos: []
}

store 是运用状况 state 的治理者,包括以下四个函数:

  • getState() # 猎取全部 state

  • dispatch(action) # ※ 触发 state 转变的【唯一门路】※

  • subscribe(listener) # 您能够明白成是 DOM 中的 addEventListener

  • replaceReducer(nextReducer) # 平常在 Webpack Code-Splitting 按需加载的时刻用

两者的关联是:state = store.getState()

Redux 划定,一个运用只应有一个单一的 store,其治理着唯一的运用状况 state
Redux 还划定,不能直接修正运用的状况 state,也就是说,下面的行动是不允许的:

var state = store.getState()
state.counter = state.counter + 1 // 制止在营业逻辑中直接修正 state

若要转变 state,必需 dispatch 一个 action,这是修正运用状况的不二法门

如今您只须要记着 action 只是一个包括 type 属性的一般对象即可
比方 { type: 'INCREMENT' }

上面提到,state 是经由过程 store.getState() 猎取,那末 store 又是怎样来的呢?
想天生一个 store,我们须要挪用 Redux 的 createStore

import { createStore } from 'redux'
...
const store = createStore(reducer, initialState) // store 是靠传入 reducer 天生的哦!

如今您只须要记着 reducer 是一个 函数,担任更新并返回一个新的 state
initialState 重要用于前后端同构的数据同步(详情请关注 React 服务端衬着)

§ Action

上面提到,action(行动)本质上是包括 type 属性的一般对象,这个 type 是我们完成用户行动追踪的症结
比方,增添一个待办事项 的 action 多是像下面一样:

/** 本代码块记为 code-2 **/
{
  type: 'ADD_TODO',
  payload: {
    id: 1,
    content: '待办事项1',
    completed: false
  }
}

固然,action 的情势是多种多样的,唯一的束缚仅仅就是包括一个 type 属性罢了
也就是说,下面这些 action 都是正当的:

/** 以下都是正当的,但就是不够范例 **/
{
  type: 'ADD_TODO',
  id: 1,
  content: '待办事项1',
  completed: false
}

{
  type: 'ADD_TODO',
  abcdefg: {
    id: 1,
    content: '待办事项1',
    completed: false
  }
}

虽然说没有束缚,但最好照样遵照范例

假如须要新增一个代办事项,实际上就是将 code-2 中的 payload “写入”state.todos 数组中(怎样“写入”?在此留个牵挂):

/** 本代码块记为 code-3 **/
{
  counter: 0,
  todos: [{
    id: 1,
    content: '待办事项1',
    completed: false
  }]
}

寻根究底,action 是谁天生的呢?

⊙ Action Creator

Action Creator 能够是同步的,也能够是异步的

望文生义,Action Creator 是 action 的创造者,本质上就是一个函数,返回值是一个 action对象
比方下面就是一个 “新增一个待办事项” 的 Action Creator:

/** 本代码块记为 code-4 **/
var id = 1
function addTodo(content) {
  return {
    type: 'ADD_TODO',
    payload: {
      id: id++,
      content: content, // 待办事项内容
      completed: false  // 是不是完成的标识
    }
  }
}

将该函数运用到一个表单(假定 store 为全局变量,并引入了 jQuery ):

<--! 本代码块记为 code-5 -->
<input type="text" id="todoInput" />
<button id="btn">提交</button>

<script>
$('#btn').on('click', function() {
  var content = $('#todoInput').val() // 猎取输入框的值
  var action = addTodo(content) // 实行 Action Creator 取得 action
  store.dispatch(action) // 转变 state 的不二法门:dispatch 一个 action!!!
})
</script>

在输入框中输入 “待办事项2” 后,点击一下提交按钮,我们的 state 就变成了:

/** 本代码块记为 code-6 **/
{
  counter: 0,
  todos: [{
    id: 1,
    content: '待办事项1',
    completed: false
  }, {
    id: 2,
    content: '待办事项2',
    completed: false
  }]
}

浅显点讲,Action Creator 用于绑定到用户的操纵(点击按钮等),其返回值 action 用于以后的 dispatch(action)

方才提到过,action 明显就没有强迫的范例,为什么 store.dispatch(action) 以后,
Redux 会明白晓得是提取 action.payload,而且是对应写入到 state.todos 数组中?
又是谁担任“写入”的呢?牵挂行将发表…

§ Reducer

Reducer 必需是同步的纯函数

用户每次 dispatch(action) 后,都邑触发 reducer 的实行
reducer 的本质是一个函数,依据 action.type更新 state 并返回 nextState
末了会用 reducer 的返回值 nextState 完全替代掉本来的 state

注重:上面的这个 “更新” 并非指 reducer 能够直接对 state 举行修正
Redux 划定,须先复制一份 state,在副本 nextState 上举行修正操纵
比方,能够运用 lodash 的 deepClone,也能够运用 Object.assign / map / filter/ ... 等返回副本的函数

在上面 Action Creator 中提到的 待办事项的 reducer 大概是长这个模样 (为了轻易明白,在此不运用 ES6 / Immutable.js):

/** 本代码块记为 code-7 **/
var initState = {
  counter: 0,
  todos: []
}

function reducer(state, action) {
  // ※ 运用的初始状况是在第一次实行 reducer 时设置的(除非是服务端衬着) ※
  if (!state) state = initState
  
  switch (action.type) {
    case 'ADD_TODO':
      var nextState = _.deepClone(state) // 用到了 lodash 的深克隆
      nextState.todos.push(action.payload) 
      return nextState

    default:
    // 因为 nextState 会把原 state 全部替代掉
    // 若无修正,必需返回原 state(不然就是 undefined)
      return state
  }
}

浅显点讲,就是 reducer 返回啥,state 就被替代成啥

§ 总结

  • store 由 Redux 的 createStore(reducer) 天生

  • state 经由过程 store.getState() 猎取,本质上平常是一个存储着全部运用状况的对象

  • action 本质上是一个包括 type 属性的一般对象,由 Action Creator (函数) 发生

  • 转变 state 必需 dispatch 一个 action

  • reducer 本质上是依据 action.type 来更新 state 并返回 nextState函数

  • reducer 必需返回值,不然 nextState 即为 undefined

  • 实际上,state 就是一切 reducer 返回值的汇总(本教程只要一个 reducer,重如果运用场景比较简朴)

Action Creator => action => store.dispatch(action) => reducer(state, action) => 原 state state = nextState

⊙ Redux 与传统后端 MVC 的对比

Redux传统后端 MVC
store数据库实例
state数据库中存储的数据
dispatch(action)用户提议要求
action: { type, payload } type 示意要求的 URL,payload 示意要求的数据
reducer路由 + 掌握器(handler)
reducer 中的 switch-case 分支路由,依据 action.type 路由到对应的掌握器
reducer 内部对 state 的处置惩罚掌握器对数据库举行增编削操纵
reducer 返回 nextState将修正后的纪录写回数据库

§ 最简朴的例子 ( 在线演示 )

<!DOCTYPE html>
<html>
<head>
  <script src="//cdn.bootcss.com/redux/3.5.2/redux.min.js"></script>
</head>
<body>
<script>
/** Action Creators */
function inc() {
  return { type: 'INCREMENT' };
}
function dec() {
  return { type: 'DECREMENT' };
}

function reducer(state, action) {
  // 初次挪用本函数时设置初始 state
  state = state || { counter: 0 };

  switch (action.type) {
    case 'INCREMENT':
      return { counter: state.counter + 1 };
    case 'DECREMENT':
      return { counter: state.counter - 1 };
    default:
      return state; // 不管怎样都返回一个 state
  }
}

var store = Redux.createStore(reducer);

console.log( store.getState() ); // { counter: 0 }

store.dispatch(inc());
console.log( store.getState() ); // { counter: 1 }

store.dispatch(inc());
console.log( store.getState() ); // { counter: 2 }

store.dispatch(dec());
console.log( store.getState() ); // { counter: 1 }
</script>
</body>
</html>

由上可知,Redux 并不一定要搭配 React 运用。Redux 地道只是一个状况治理库,险些能够搭配任何框架运用
(上述例子连 jQuery 都没用哦亲)

§ 下一章:Redux 进阶教程

    原文作者:kenberkeley
    原文地址: https://segmentfault.com/a/1190000006701765
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞