彻底捋顺redux
仔细算来,redux用了也有一年多了,但一直是用的时候捡起来,不用又忘了的情况,处在似懂非懂的阶段。这篇文章用来对redux做一个总结,彻底搞懂redux。
Redux是JavaScript状态容器,提供可预测化的状态管理。redux提出的目的是为了解决大型的复杂应用中组件之间的通信困难、代码结构混乱等问题。具体来说就是以下几种场景:
就使用场景来说,
- 用户的使用方式复杂
- 不同身份的用户有不同的使用方式(比如普通用户和管理员)
- 多个用户之间可以协作
- 与服务器大量交互,或者使用了WebSocket
- View要从多个来源获取数据
就组件常见来说,
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
发生上面情况时,如果不使用 Redux 或者其他状态管理工具,不按照一定规律处理状态的读写,代码很快就会变成一团乱麻。你需要一种机制,可以在同一个地方查询状态、改变状态、传播状态的变化。
开始之前先来了解几个概念
Redux 由以下组件组成:
- Action,这是一个用来描述发生了什么事情的对象。
- Reducer,接收action并更新Store。
- Store,整个程序的状态/对象树保存在Store中。
- View,只显示 Store 提供的数据。
Redux有三大原则
- 单一数据源store:store是整个应用的数据存储中心(store tree),集中大部分页面需要的状态数据;
- state是只读的,所以需要view触发action去更新store;
- 使用纯函数Reducer执行state更新。
下面开始逐一介绍
- Store
Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。Redux 提供createStore这个函数,用来生成 Store。store提供getState方法获取所有的state
import { createStore } from 'redux';
const store = createStore(reducer);
const state = store.getState();
- Action
上面说过state是只读的,State 的变化会导致 View 的变化。但是用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
const action = {
type: 'ADD',
payload: 1
};
Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store。
定义好Action了,怎么发出呢?store.dispatch()是 View 发出 Action 的唯一方法。
import { createStore } from 'redux';
const store = createStore(reducer);
store.dispatch(action);
- reducer
Reducer 是一个纯函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
// 这里reducer也可以进行拆分,放到单独一个文件中
import { createStore } from 'redux';
const defaultState = 0;
const increaseReducer = (state = defaultState, action) => {
switch (action.type) {
case 'ADD':
return state + action.payload;
default:
return state;
}
}
const store = createStore(increaseReducer);
上面代码中,由于store.dispatch方法会触发 Reducer 的自动执行,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。
createStore接受 Reducer 作为参数,生成一个新的 Store。以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。
Reducer 函数最重要的特征就是它必须是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。纯函数是函数式编程的概念,必须遵守一些约束:
- 不得改写参数
- 不能调用系统 I/O 的API
- 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果
ps: reducer拆分
试想一下,在一个大型应用中 State 必然十分庞大,导致 Reducer 函数也十分庞大。在上面的例子中我们提到reducer也可以进行拆分,放在一个文件里面,然后统一引入。Redux 提供了一个combineReducers方法,用于 Reducer 的拆分。你只要定义各个子 Reducer 函数,然后用这个方法,将它们合成一个大的 Reducer。
import { combineReducers } from 'redux'
import increase from './increase'
import decrease from './decrease'
const reducer = combineReducers({
value: increase,
decrease // 这种写法有一个前提,就是 State 的属性名必须与子 Reducer 同名。否则就用上面这种写法
})
export default reducer;
- React-Redux 的用法
React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
UI 组件有以下几个特征:
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态(即不使用this.state这个变量)
- 所有数据都由参数(this.props)提供
- 不使用任何 Redux 的 API
容器组件的特征如下:
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 API
connect
React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。
import { connect } from 'react-redux'
import mapStateToProps from ''
import mapDispatchToProps from ''
const App= connect(
mapStateToProps, // value 来自这里
mapDispatchToProps // onIncreaseClick 来自这里
)(Counter)
class Counter extends Component {
render() {
const { value, onIncreaseClick } = this.props
return (
<div>
<span>{value}</span>
<button onClick={onIncreaseClick}>Increase</button>
</div>
)
}
}
export default App
上面代码中,Counter是 UI 组件,App就是由 React-Redux 通过connect方法自动生成的容器组件。
mapStateToProps
mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从state对象(外部的)到props对象(UI 组件的)的映射关系。mapStateToProps执行后返回一个对象,里面的每一个键值对就是一个映射。
const mapStateToProps = state => {
return {
value: state.value
}
}
mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。
mapDispatchToProps
mapDispatchToProps是connect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。
const mapDispatchToProps = dispatch=> {
return {
onIncreaseClick: () => {
dispatch({
type: 'ADD',
payload: 1
});
}
};
}
<Provider> 组件
connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。如果一级级将state传下去就很麻烦,React-Redux 提供Provider组件,可以让容器组件拿到state。
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import reducer from './reducers'
import App from './app'
let store = createStore(reducer);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
上面代码中,Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了
redux可以使代码结构更加规范,代码可读性更强。因为React提出将展示组件/UI组件(业务逻辑)与容器组件(数据源)分离的思想,所以降低了React 与Redux之间的耦合度。
为什么我能够看得更远,那是因为我站在巨人的肩上,以上内容来源:
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html