Skip to content

Commit

Permalink
lodash deepClone(X) => cloneDeep(V)
Browse files Browse the repository at this point in the history
  • Loading branch information
kenberkeley committed Sep 1, 2016
1 parent ed11af3 commit 49a1f5e
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 117 deletions.
230 changes: 115 additions & 115 deletions README.md
Expand Up @@ -12,7 +12,7 @@
### ⊙ 需求 1:在控制台上记录用户的每个动作

不知道您是否有后端的开发经验,后端一般会有记录访问日志的**中间件**
例如,在 Express 中实现一个简单的 Logger 如下:
例如,在 Express 中实现一个简单的 Logger 如下:

```js
var loggerMiddleware = function(req, res, next) {
Expand All @@ -23,7 +23,7 @@ var loggerMiddleware = function(req, res, next) {
app.use(loggerMiddleware)
```

每次访问的时候,都会在控制台中留下类似下面的日志便于追踪调试:
每次访问的时候,都会在控制台中留下类似下面的日志便于追踪调试:

```
[Logger] GET /
Expand Down Expand Up @@ -59,8 +59,8 @@ methods: {
}
```

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

### ⊙ 需求 2:在上述需求的基础上,记录用户的操作时间
> 哼!最讨厌就是改需求了,这种简单的需求难道不是应该一开始就想好的吗?
Expand Down Expand Up @@ -91,7 +91,7 @@ methods: {
}
```

而后端的童鞋只需要稍微修改一下原来的中间件即可:
而后端的童鞋只需要稍微修改一下原来的中间件即可:

```js
var loggerMiddleware = function(req, res, next) {
Expand Down Expand Up @@ -126,7 +126,7 @@ app.use(loggerMiddleware)
### ※ 小结

为何前后端对于这类需求的处理竟然大相径庭?后端为何可以如此优雅?
原因在于,后端具有**统一的入口****统一的状态管理(数据库)**,因此可以引入**中间件机制****统一**实现某些功能
原因在于,后端具有**统一的入口****统一的状态管理(数据库)**,因此可以引入**中间件机制****统一**实现某些功能

多年来,前端工程师忍辱负重,操着卖白粉的心,赚着买白菜的钱,一直处于程序员鄙视链的底层
于是有大牛就把后端 MVC 的开发思维搬到前端,**将应用中所有的动作与状态都统一管理**,让一切**有据可循**
Expand All @@ -141,9 +141,9 @@ app.use(loggerMiddleware)
## § Store
首先要区分 `store``state`

`state` 是应用的状态,一般本质上是一个普通**对象**
`state` 是应用的状态,一般本质上是一个普通**对象**
例如,我们有一个 Web APP,包含 计数器 和 待办事项 两大功能
那么我们可以为该应用设计出对应的存储数据结构(应用初始状态):
那么我们可以为该应用设计出对应的存储数据结构(应用初始状态):

```js
/** 应用初始 state,本代码块记为 code-1 **/
Expand All @@ -153,7 +153,7 @@ app.use(loggerMiddleware)
}
```

`store` 是应用状态 `state` 的管理者,包含下列四个函数:
`store` 是应用状态 `state` 的管理者,包含下列四个函数:

* `getState() # 获取整个 state`
* `dispatch(action) # ※ 触发 state 改变的【唯一途径】※`
Expand All @@ -163,7 +163,7 @@ app.use(loggerMiddleware)
二者的关系是:`state = store.getState()`

Redux 规定,一个应用只应有一个单一的 `store`,其管理着唯一的应用状态 `state`
Redux 还规定,不能直接修改应用的状态 `state`,也就是说,下面的行为是不允许的:
Redux 还规定,不能直接修改应用的状态 `state`,也就是说,下面的行为是不允许的:

```js
var state = store.getState()
Expand All @@ -176,7 +176,7 @@ state.counter = state.counter + 1 // 禁止在业务逻辑中直接修改 state
> 例如 `{ type: 'INCREMENT' }`
上面提到,`state` 是通过 `store.getState()` 获取,那么 `store` 又是怎么来的呢?
想生成一个 `store`,我们需要调用 Redux 的 `createStore`
想生成一个 `store`,我们需要调用 Redux 的 `createStore`

```js
import { createStore } from 'redux'
Expand All @@ -188,7 +188,7 @@ const store = createStore(reducer, initialState) // store 是靠传入 reducer
## § Action
上面提到,`action`(动作)实质上是包含 `type` 属性的普通对象,这个 `type` 是我们实现用户行为追踪的关键
例如,增加一个待办事项 的 `action` 可能是像下面一样:
例如,增加一个待办事项 的 `action` 可能是像下面一样:

```js
/** 本代码块记为 code-2 **/
Expand All @@ -203,9 +203,9 @@ const store = createStore(reducer, initialState) // store 是靠传入 reducer
```

当然,`action` 的形式是多种多样的,唯一的约束仅仅就是包含一个 `type` 属性罢了
也就是说,下面这些 `action` 都是合法的:
也就是说,下面这些 `action` 都是合法的:

```js
```js
/** 如下都是合法的,但就是不够规范 **/
{
type: 'ADD_TODO',
Expand All @@ -226,7 +226,7 @@ const store = createStore(reducer, initialState) // store 是靠传入 reducer

> 虽说没有约束,但最好还是遵循[规范][flux-action-pattern]
如果需要新增一个代办事项,实际上就是将 `code-2` 中的 `payload` **“写入”**`state.todos` 数组中(如何“写入”?在此留个悬念):
如果需要新增一个代办事项,实际上就是将 `code-2` 中的 `payload` **“写入”**`state.todos` 数组中(如何“写入”?在此留个悬念):

```js
/** 本代码块记为 code-3 **/
Expand All @@ -245,8 +245,8 @@ const store = createStore(reducer, initialState) // store 是靠传入 reducer
### ⊙ Action Creator
> Action Creator 可以是同步的,也可以是异步的
顾名思义,Action Creator 是 `action` 的创造者,本质上就是一个**函数**,返回值是一个 `action`**对象**
例如下面就是一个 “新增一个待办事项” 的 Action Creator:
顾名思义,Action Creator 是 `action` 的创造者,本质上就是一个**函数**,返回值是一个 `action`**对象**
例如下面就是一个 “新增一个待办事项” 的 Action Creator:

```js
/** 本代码块记为 code-4 **/
Expand All @@ -263,23 +263,23 @@ function addTodo(content) {
}
```

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

```html
```html
<--! 本代码块记为 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` 就变成了:
在输入框中输入 “待办事项2” 后,点击一下提交按钮,我们的 `state` 就变成了:

```js
/** 本代码块记为 code-6 **/
Expand All @@ -299,37 +299,37 @@ $('#btn').on('click', function() {

> 通俗点讲,Action Creator 用于绑定到用户的操作(点击按钮等),其返回值 `action` 用于之后的 `dispatch(action)`
刚刚提到过,`action` 明明就没有强制的规范,为什么 `store.dispatch(action)` 之后,
刚刚提到过,`action` 明明就没有强制的规范,为什么 `store.dispatch(action)` 之后,
Redux 会明确知道是提取 `action.payload`,并且是对应写入到 `state.todos` 数组中?
又是谁负责“写入”的呢?悬念即将揭晓...

## &sect; Reducer
> Reducer 必须是同步的纯函数
用户每次 `dispatch(action)` 后,都会触发 `reducer` 的执行
`reducer` 的实质是一个**函数**,根据 `action.type`**更新** `state` 并返回 `nextState`
最后会用 `reducer` 的返回值 `nextState` **完全替换掉**原来的 `state`

> Reducer 必须是同步的纯函数
用户每次 `dispatch(action)` 后,都会触发 `reducer` 的执行
`reducer` 的实质是一个**函数**,根据 `action.type`**更新** `state` 并返回 `nextState`
最后会用 `reducer` 的返回值 `nextState` **完全替换掉**原来的 `state`

> 注意:上面的这个 “更新” 并不是指 `reducer` 可以直接对 `state` 进行修改
> Redux 规定,须先复制一份 `state`,在副本 `nextState` 上进行修改操作
> 例如,可以使用 lodash 的 `deepClone`,也可以使用 `Object.assign / map / filter/ ...` 等返回副本的函数
> 例如,可以使用 lodash 的 `cloneDeep`,也可以使用 `Object.assign / map / filter/ ...` 等返回副本的函数
在上面 Action Creator 中提到的 待办事项的 `reducer` 大概是长这个样子 (为了容易理解,在此不使用 ES6 / [Immutable.js][immutable]):

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

function reducer(state, action) {
// ※ 应用的初始状态是在第一次执行 reducer 时设置的 ※
if (!state) state = initState

/** 本代码块记为 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 的深克隆
var nextState = _.cloneDeep(state) // 用到了 lodash 的深克隆
nextState.todos.push(action.payload)
return nextState

Expand All @@ -339,81 +339,81 @@ function reducer(state, action) {
return state
}
}
```

```

> 通俗点讲,就是 `reducer` 返回啥,`state` 就被替换成啥
## &sect; 总结

* `store` 由 Redux 的 `createStore(reducer)` 生成
* `state` 通过 `store.getState()` 获取,本质上一般是一个存储着整个应用状态的**对象**
* `action` 本质上是一个包含 `type` 属性的普通**对象**,由 Action Creator (**函数**) 产生
* 改变 `state` 必须 `dispatch` 一个 `action`
* `reducer` 本质上是根据 `action.type` 来更新 `state` 并返回 `nextState`**函数**

* `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` | 将修改后的记录写回数据库

## &sect; 最简单的例子 ( [在线演示][jsbin] )

```html
<!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>
* 实际上,**`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` | 将修改后的记录写回数据库

## &sect; 最简单的例子 ( [在线演示][jsbin] )

```html
<!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 纯粹只是一个状态管理库,几乎可以搭配任何框架使用
Expand All @@ -430,6 +430,6 @@ console.log( store.getState() ); // { counter: 1 }
[redux]: https://github.com/reactjs/redux
[flux-action-pattern]: https://github.com/acdlite/flux-standard-action
[global-err-handler]: http://stackoverflow.com/questions/5328154/#5328206
[redux-devtools]: https://github.com/gaearon/redux-devtools
[immutable]: https://github.com/facebook/immutable-js
[jsbin]: http://jsbin.com/zivare/edit?html,console
[redux-devtools]: https://github.com/gaearon/redux-devtools
[immutable]: https://github.com/facebook/immutable-js
[jsbin]: http://jsbin.com/zivare/edit?html,console
4 changes: 2 additions & 2 deletions redux-advanced-tutorial.md
Expand Up @@ -340,7 +340,7 @@ function reducer(state, action) {

switch (action.type) {
case 'ADD_TODO':
var nextState = _.deepClone(state) // 用到了 lodash 的深克隆
var nextState = _.cloneDeep(state) // 用到了 lodash 的深克隆
nextState.todos.push(action.payload)
return nextState

Expand All @@ -359,7 +359,7 @@ var initState = { counter: 0, todos: [] }

function reducer(state, action) {
if (!state) return initState // 若是初始化可立即返回应用初始状态
var nextState = _.deepClone(state) // 否则二话不说先克隆
var nextState = _.cloneDeep(state) // 否则二话不说先克隆

switch (action.type) {
case 'ADD_TODO': // 新增待办事项
Expand Down

0 comments on commit 49a1f5e

Please sign in to comment.