Redux第一课

React火了这么久了,但是因为其门槛,很多希望了解它的前端后端都因为其繁杂的配置,或者学习成本望而却步,这里我就简单的总结下,怎么样入门这一高大上的技术栈。就算工作中不使用,了解其运行原理也是很好的。

推荐学习的网站:

Redux中文官方示例

redux-tutorial-cn

阮一峰的博客

React

这里我不会从入门开始讲解react的组件和其语法。所以基本的react知识大家还是要具备。react作为mv*中的view层,学习起来还说不难的,可以参看官方文档。

Redux

初接触redux的同学可能真的对他真是一头雾水,不知所云。阮一峰文章中的一句话把redux的核心说的非常透彻。

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

做过游戏开发的同学对状态机这种概念肯定不会陌生。在游戏实现功能的过程中,可能存在大量的事件触发机制,我们用状态机来管理这种事件机制,这样只需判断状态的变化从执行相应的事件而不用去管理触发机制的对象本身。这是用来简化事件逻辑的一种方法。

如今redux也采用了这样一种思想。就是在整个webapp设置一个状态机。redux通过不同的store来存储其中的所有状态。用action来作为一种消息方式,来触发各种不同的状态。而状态的最终处理则是在redux函数之中,他会返回处理之后新的状态,再根据这种新的状态数据来渲染整个页面。

我这里盗来峰哥的流程图:

image

其中的dispatch就像事件的分发一样,用户的action通过这中方式到达reducer中进行处理,从而返回新的state更新react dom。

要注意的是,redux并不只是适用于react之中,他作为一个完整的状态机解决方案,可以嵌入各种框架甚至原声js的web中。这点还是很不错的。

环境的搭建

这里我没有自己配置webpack或者gulp这些东西。本来大家对redux就不熟悉,还搞这么多自动化工具,配置起来太痛苦了。我们用他们官方案例中所使用的react-scripts这个环境(其实也是内置webpack)来做为我们的最初学习环境。

redux github官方地址

找到counter这个example,把他的package.json弄下来。我们就用这个来搭建环境。

写一个Counter计数器

计数器实例在example中是自带的demo,但刚刚接触redux的初学者可能难以看懂,这里我就再重写一次,并附上注释

我们首先创建一个项目,然后建立如下目录:

image

在’public/index.html’中添加代码:

1
2
3
4
5
6
7
8
9
10
11
 
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>redux第一课</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

这里的html只是一个模版,我们要用react的组件来渲染他。

接下来打开’src/index.js’,这个是我们的程序入口,添加代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
import React from 'react'
import ReactDOM from 'react-dom'

const rootEl = document.getElementById('root');

var HelloWorld = React.createClass({
render: function () {
return <h1>Redux第一课!</h1>
}
});

const render = () => ReactDOM.render(
<HelloWorld />, rootEl
);

render();

react全面拥抱es6,看不懂es6语法的同学赶紧去补课。

我们在命令行环境中只要输入’npm start’,整个项目便跑起来了。打开页面如下:

image

我们只是简单的定义了HelloWorld这个组件,然后通过render函数来在调用这个组件,最后渲染到页面上。

ok,现在我们并没有使用到redux,而仅仅只使用了react的基本渲染。不用着急,我们开始下一步。

我们需要把组件部分移到’src/components’下面来统一管理,我们新建一个Counter.js,添加代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

import React, { Component, PropTypes } from 'react'

// Counter组件
class Counter extends Component {
// 属性
static propTypes = {
value: PropTypes.number.isRequired,
onIncrement: PropTypes.func.isRequired,
onDecrement: PropTypes.func.isRequired
}

// 条件增加
incrementIfOdd = () => {
if (this.props.value % 2 !== 0) {
this.props.onIncrement()
}
}

// 异步增加
incrementAsync = () => {
setTimeout(this.props.onIncrement, 1000)
}

render() {
const { value, onIncrement, onDecrement } = this.props
return (
<p>
Clicked: {value} times
{' '}
<button onClick={onIncrement}>
+
</button>
{' '}
<button onClick={onDecrement}>
-
</button>
{' '}
<button onClick={this.incrementIfOdd}>
Increment if odd
</button>
{' '}
<button onClick={this.incrementAsync}>
Increment async
</button>
</p>
)
}
}

export default Counter

这里我们定义了一个Counter组件类,并在组件中定义了他的属性和方法。最后通过export方法导出。

ok,我们回到程序入口的’src/index.js’,修改代码:

1
2
3
4
5
6
7
8
9
10
11
12

import React from 'react'
import ReactDOM from 'react-dom'
import Counter from './components/Counter'

const rootEl = document.getElementById('root');

const render = () => ReactDOM.render(
<Counter />, rootEl
);

render();

我们引入了Counter来替代我们之前所渲染的HelloWorld组件。细心的同学可能会注意到,虽然组件成功的渲染了出来,但是由于其方法参数我们还没有传进去,所以控制台会报出相应的错误,这里我们可以暂时不理会,如果有强迫症,先用空函数代替也可以。

ok,接下来就要开始引入和redux相关的代码了。

还记得我们之前所说的。redux是一个状态机。所有的状态都保存在一个Store中。所以,整个webapp中,Store只能有一个。

我们在’src/index.js’中引入createStore函数:

1
2

import {createStore} from 'redux'

这个函数的作用就是创建一个新的Store。Store是整个webapp的状态容器,我们无法直接去修改Store,必须通过reducer这个函数来对Store进行改变,也就是说reducer是处理状态并返回新状态的一个函数。

这里我们再写一个reducer,reducer是根据通过响应action来作出状态的改变的:

1
2
3
4
5
6
7
8
9
10
11
12
13

let reducer = (state = {},action)=>{
switch(action.type){
case 'SAY':
console.log("After dispatch");
return {
...state,
say:"hello world"
}
default:
return state
}
}

这个根据传进来的action来处理状态,最后返回一个新的状态。代码还是比较好理解的。这里需要注意下,action对象下的type属性是必须的,不能随意替换更改。

我们再更新下’src/index.js’:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
import Counter from './components/Counter'

const rootEl = document.getElementById('root');

let reducer = (state = {},action)=>{
switch(action.type){
case 'SAY':
console.log("After dispatch");
return {
...state,
say:"hello world"
}
default:
return state
}
}

// 创建状态容器,redecuer为参数
const store = createStore(reducer);

// 获取应用当前状态
console.log(store.getState());

// action的分发
let action = {
"type":"SAY"
}
store.dispatch(action);

console.log(store.getState());

const render = () => ReactDOM.render(
<Counter />, rootEl
);

render();

最后输出的结果如图:

image

这里我们实现了一个简单的reducer,他能接受type为’SAY’的消息,并对store的state作出修改反馈。还记得我们现在要实现的是一个简单计数器,那么我们继续往下,我们在’src/reducer’中创建一个index.js:

1
2
3
4
5
6
7
8
9
10
11
12

// state增减逻辑
export default (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}

这个和我们之前写的reducer是不是很相似,只是对state的操作不同而已。

我们修改’src/index.js’将这个新的reducer引入,并且在Couter组件中设置好各个函数参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import Counter from './components/Counter'
import reducer from './reducers'

const rootEl = document.getElementById('root');

// 创建状态容器,redecuer为参数
const store = createStore(reducer);

const render = () => ReactDOM.render(
<Counter
value={store.getState()}
onIncrement={() => store.dispatch({ type: 'INCREMENT' })} // action在这里传入
onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
/>,
rootEl
);

render();
// 最后是一个订阅函数,用来接受store的变化之后的回调,我们这里是重新渲染页面
store.subscribe(render)

至此,所有关于Counter计数器的讲解就结束了。

小结

react+redux的整个开发思维还是很有意思的,将游戏开发中的常用套路运用到了web开发之中。不过只要我们认清了其本质,学习起来还说不难的。看到这里,稍微理清了 store action reducer 这三者之间的关系之后,我们再看阮一峰和官方的案例应该会觉得清晰一些。

最后放一个demo地址,如果你懒得从react的官方去下载,直接看看这个也是一样的。