Fork me on GitHub

Redux入门

1
2
3
http://redux.js.org/
http://www.redux.org.cn/
http://www.imooc.com/learn/744 视频教程

简介

Redux 由 Flux 演变而来,但受 Elm 的启发,避开了 Flux 的复杂性

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
import { createStore } from 'redux';

/**
* 这是一个 reducer,形式为 (state, action) => state 的纯函数。
* 描述了 action 如何把 state 转变成下一个 state。
*
* state 的形式取决于你,可以是基本类型、数组、对象、
* 甚至是 Immutable.js 生成的数据结构。惟一的要点是
* 当 state 变化时需要返回全新的对象,而不是修改传入的参数。
*
* 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper)
* 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}

// 创建 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter);

// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() =>
console.log(store.getState())
);

// 改变内部 state 惟一方法是 dispatch 一个 action。
// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1

Redux 没有 Dispatcher 且不支持多个 store。相反,只有一个单一的 store 和一个根级的 reduce 函数(reducer)

image

中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
redux-thunk — 用最简单的方式搭建异步 action 构造器
redux-promise — 遵从 FSA 标准的 promise 中间件
redux-axios-middleware — 使用 axios HTTP 客户端获取数据的 Redux 中间件
redux-observable — Redux 的 RxJS 中间件
redux-rx — 给 Redux 用的 RxJS 工具,包括观察变量的中间件
redux-logger — 记录所有 Redux action 和下一次 state 的日志
redux-immutable-state-invariant — 开发中的状态变更提醒
redux-unhandled-action — 开发过程中,若 Action 未使 State 发生变化则发出警告
redux-analytics — Redux middleware 分析
redux-gen — Redux middleware 生成器
redux-saga — Redux 应用的另一种副作用 model
redux-action-tree — Redux 的可组合性 Cerebral-style 信号
apollo-client — 针对 GraphQL 服务器及基于 Redux 的 UI 框架的缓存客户端

三大原则

Redux 可以用这三个基本原则来描述:

单一数据源

整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中

State 是只读的

惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象

使用纯函数来执行修改

为了描述 action 如何改变 state tree ,你需要编写 reducers;Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state

总结

  1. 应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中
  2. 惟一改变 state 的办法是触发 action,一个描述发生什么的对象
  3. 为了描述 action 如何改变 state 树,你需要编写 reducers
  4. 编写专门的函数来决定每个 action 如何改变应用的 state,这个函数被叫做 reducer

安装

  1. 安装redux:npm install redux --save
  2. 使用 React 绑定库和开发者工具:npm install react-redux --save;npm install redux-devtools --save-dev

原理

image

Action

Action 是把数据从应用传到 store 的有效载荷,它是 store 数据的唯一来源。一般来说会通过 store.dispatch() 将 action 传到 store。

action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作.多数情况下,type 会被定义成字符串常量,建议使用单独的模块或文件来存放 action。除了 type 字段外,action 对象的结构完全由自己决定

1
import { ADD_TODO, REMOVE_TODO } from '../actionTypes'

Action 创建函数

Action 创建函数 就是生成 action 的方法

1
2
3
4
5
6
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}

bindActionCreators() 可以自动把多个 action 创建函数 绑定到 dispatch() 方法上。

Reducer

永远不要在 reducer 里做这些操作:

  1. 修改传入参数;
  2. 执行有副作用的操作,如 API 请求和路由跳转;
  3. 调用非纯函数,如 Date.now() 或 Math.random()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
case ADD_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
{
text: action.text,
completed: false
}
]
})
default:
return state
}
}
  1. 不要修改 state。 使用 Object.assign() 新建了一个副本。不能这样使用 Object.assign(state, { visibilityFilter: action.filter }),因为它会改变第一个参数的值。你必须把第一个参数设置为空对象。你也可以开启对ES7提案对象展开运算符的支持, 从而使用 { …state, …newState } 达到相同的目的。
  2. 在 default 情况下返回旧的 state
  3. Object.assign() 是 ES6 特性,但多数浏览器并不支持。你要么使用 polyfill,Babel 插件,或者使用其它库如 _.assign() 提供的帮助方法。
  4. 新的 todos 对象就相当于旧的 todos 在末尾加上新建的 todo。而这个新的 todo 又是基于 action 中的数据创建的

Store

Store 有以下职责:

  1. 维持应用的 state;
  2. 提供 getState() 方法获取 state;
  3. 提供 dispatch(action) 方法更新 state;
  4. 通过 subscribe(listener) 注册监听器;
  5. 通过 subscribe(listener) 返回的函数注销监听器

搭配react

Redux 和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。

连接到 Redux:我们需要做出两个变化,将 App 组件连接到 Redux 并且让它能够 dispatch actions 以及从 Redux store 读取到 state

  1. 获取从之前安装好的 react-redux 提供的 Provider,并且在渲染之前将根组件包装进
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//index.js
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './containers/App'
import todoApp from './reducers'

let store = createStore(todoApp);

let rootElement = document.getElementById('root')
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
)
  1. 通过 react-redux 提供的 connect() 方法将包装好的组件连接到Redux
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { addTodo, completeTodo, setVisibilityFilter, VisibilityFilters } from '../actions';
import AddTodo from '../components/AddTodo';
import TodoList from '../components/TodoList';
import Footer from '../components/Footer';

class App extends Component {
render() {
// 通过调用 connect() 注入:
const { dispatch, visibleTodos, visibilityFilter } = this.props
return (
<div>
<AddTodo
onAddClick={text =>
dispatch(addTodo(text))
} />
<TodoList
todos={this.props.visibleTodos}
onTodoClick={index =>
dispatch(completeTodo(index))
} />
<Footer
filter={visibilityFilter}
onFilterChange={nextFilter =>
dispatch(setVisibilityFilter(nextFilter))
} />
</div>
)
}
}

App.propTypes = {
visibleTodos: PropTypes.arrayOf(PropTypes.shape({
text: PropTypes.string.isRequired,
completed: PropTypes.bool.isRequired
})),
visibilityFilter: PropTypes.oneOf([
'SHOW_ALL',
'SHOW_COMPLETED',
'SHOW_ACTIVE'
]).isRequired
}

function selectTodos(todos, filter) {
switch (filter) {
case VisibilityFilters.SHOW_ALL:
return todos;
case VisibilityFilters.SHOW_COMPLETED:
return todos.filter(todo => todo.completed);
case VisibilityFilters.SHOW_ACTIVE:
return todos.filter(todo => !todo.completed);
}
}

// 基于全局 state ,哪些是我们想注入的 props ?
// 注意:使用 https://github.com/reactjs/reselect 效果更佳。
function select(state) {
return {
visibleTodos: selectTodos(state.todos, state.visibilityFilter),
visibilityFilter: state.visibilityFilter
};
}

// 包装 component ,注入 dispatch 和 state 到其默认的 connect(select)(App) 中;
export default connect(select)(App);

本文标题:Redux入门

文章作者:行锋

发布时间:2018年10月26日 - 15:10

最后更新:2019年01月11日 - 00:01

原始链接:https://chetaofeng.github.io/2018/10/26/react-rn-2018-10-26-Redux入门/

许可协议: 署名-非商业性使用 转载请保留原文链接及作者

-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!