redux入门
什么是redux
Redux
是 JavaScript 状态容器,提供可预测化的状态管理。
什么时候使用redux
-
某个组件的状态,需要共享
-
某个状态需要在任何地方都可以拿到
-
一个组件需要改变全局状态
-
一个组件需要改变另一个组件的状态
如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。
基础和概念
state
当使用普通对象来描述应用的 state 时:
{
loginName: '',
visibilityFilter: 'SHOW_COMPLETED'
}
这个对象就像 “Model”,区别是它并没有 setter(修改器方法)。因此其它的代码不能随意修改它,造成难以复现的 bug。
action
-
Action
是把数据从应用传到 store 的有效载荷。 -
它是 store 数据的唯一来源。
-
要想更新 state 中的数据,你需要发起一个
action
。
Action
就是一个普通 JavaScript 对象,像这样:
{type: 'SET_NAME', text: 'tom'},
{type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL'}
强制使用 action 来描述所有变化带来的好处是可以清晰地知道应用中到底发生了什么。如果一些东西改变了,就可以知道为什么变。action 就像是描述发生了什么的指示器。
注:
在redux中约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。
reducer
把 action 和 state 串起来,开发一些函数,这就是 reducer。
reducer 只是一个接收 state 和 action,并返回新的 state 的函数。
function loginName(state = '', action) {
if (action.type === 'SET_NAME') {
return action.filter;
} else {
return state;
}
}
function visibilityFilter(state = 'SHOW_ALL', action) {
if (action.type === 'SET_VISIBILITY_FILTER') {
return action.filter;
} else {
return state;
}
}
再开发一个 reducer 调用这两个 reducer,进而来管理整个应用的 state:
function reducers(state = {}, action) {
return {
loginName: loginName(state.loginName, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action)
};
}
注:
reducer
纯净非常重要。永远不要在 reducer 里做这些操作:
-
修改传入参数;
-
执行有副作用的操作,如 API 请求和路由跳转;
-
调用非纯函数,如 Date.now() 或 Math.random()。
每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。
这差不多就是 Redux 思想的全部。redux 就是提供一些简单的工具来简化这种模式。
redux 三大原则
-
单一数据源
整个应用的
state
被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个store
中。 -
State 是只读的
唯一改变 state 的方法就是触发
action
,action 是一个用于描述已发生事件的普通对象。在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state。不要使用 Object.assign(state, newData),应该使用 Object.assign({}, state, newData) -
使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写
reducers
redux API
createStore(reducer, [preloadedState], enhancer)
创建一个 Redux store
来以存放应用中所有的 state。
应用中应有且仅有一个 store。
参数
-
reducer
(Function): 接收两个参数,分别是当前的 state 树和要处理的action
,返回新的state 树
-
preloadedState
(any): 初始时的 state。如果你使用combineReducers
创建reducer
,它必须是一个普通对象,与传入的 keys 保持同样的结构。否则,你可以自由传入任何reducer
可理解的内容。 -
enhancer
(Function): Store enhancer 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator。这与 middleware 相似,它也允许你通过复合函数改变 store 接口。
返回值
Store
: 保存了应用所有 state 的对象。
Store
Store 就是用来维持应用所有的state 树 的一个对象。
Store 不是类。它只是有几个方法的对象。 要创建它,只需要把根部的 reducing 函数 传递给 createStore
。
Store 方法 | 介绍 | 参数 | 返回值 |
---|---|---|---|
getState() |
得到state | -- | (any): 应用当前的 state 树。 |
dispatch |
分发 action。这是触发 state 变化的惟一途径。 | action (Object) | (Object): 要 dispatch 的 action。 |
subscribe |
一个变化监听器。每当 dispatch action 的时候就会执行,state 树中的一部分可能已经变化。 |
listener (Function): 每当 dispatch action 的时候都会执行的回调。state 树中的一部分可能已经变化。你可以在回调函数里调用 getState() 来拿到当前 state。 |
(Function): 一个可以解绑变化监听器的函数。 |
replaceReducer(nextReducer) |
替换 store 当前用来计算 state 的 reducer。这是一个高级 API。只有在你需要实现代码分隔,而且需要立即加载一些 reducer 的时候才可能会用到它。 | reducer (Function) store 会使用的下一个 reducer。 | --- |
combineReducers(reducers)
combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数。
合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。
rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})
// rootReducer 将返回如下的 state 对象
{
potato: {
// ... potatoes, 和一些其他由 potatoReducer 管理的 state 对象 ...
},
tomato: {
// ... tomatoes, 和一些其他由 tomatoReducer 管理的 state 对象,比如说 sauce 属性 ...
}
}
参数
reducers (Object): 一个对象,它的值(value)对应不同的 reducer 函数,这些 reducer 函数后面会被合并成一个。
返回值
(Function):一个调用 reducers 对象里所有 reducer 的 reducer,并且构造一个与 reducers 对象结构相同的 state 对象。
注:
每个传入 combineReducers 的 reducer 都需满足以下规则:
-
所有未匹配到的 action,必须把它接收到的第一个参数也就是那个 state 原封不动返回。
-
永远不能返回 undefined。当过早 return 时非常容易犯这个错误,为了避免错误扩散,遇到这种情况时 combineReducers 会抛异常。
-
如果传入的 state 就是 undefined,一定要返回对应 reducer 的初始 state。根据上一条规则,初始 state 禁止使用 undefined。使用 ES6 的默认参数值语法来设置初始 state 很容易,但你也可以手动检查第一个参数是否为 undefined。
示例
//reducers/todos.js
export default function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.text])
default:
return state
}
}
//reducers/counter.js
export default function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
//reducers/index.js
import { combineReducers } from 'redux'
import todos from './todos'
import counter from './counter'
export default combineReducers({
todos,
counter
})
//App.js
import { createStore } from 'redux'
import reducer from './reducers/index'
let store = createStore(reducer)
console.log(store.getState())
// {
// counter: 0,
// todos: []
// }
store.dispatch({
type: 'ADD_TODO',
text: 'Use Redux'
})
console.log(store.getState())
// {
// counter: 0,
// todos: [ 'Use Redux' ]
// }
applyMiddleware(...middlewares)
使用包含自定义功能的 middleware 来扩展 Redux 是一种推荐的方式。Middleware 可以让你包装 store 的 dispatch
方法来达到你想要的目的。
参数
-
...middlewares
(arguments): 遵循 Redux middleware API 的函数。每个 middleware 接受Store
的dispatch
和getState
函数作为命名参数,并返回一个函数。该函数会被传入 被称为next
的下一个 middleware 的 dispatch 方法,并返回一个接收 action 的新函数,这个函数可以直接调用next(action)
,或者在其他需要的时刻调用,甚至根本不去调用它。调用链中最后一个 middleware 会接受真实的 store 的dispatch
方法作为next
参数,并借此结束调用链。所以,middleware 的函数签名是({ getState, dispatch }) => next => action
。
使用
import { applyMiddleware, createStore, compose } from 'redux'
import reducers from './reducers'
import {createLogger} from 'redux-logger'
import middlePromise from 'redux-promise'
// import thunk from 'redux-thunk'
// 模拟 logger
const logger = store => next => action =>{
console.log('prev state',store.getState())
console.log('dispatch',action);
let result = next(action);
console.log('next state',store.getState());
return result;
}
const store = createStore(reducers, applyMiddleware(middlePromise, logger))
export default store
// 异步
import { applyMiddleware, createStore, compose } from 'redux'
import reducers from './reducers'
import {createLogger} from 'redux-logger'
// import middlePromise from 'redux-promise'
import thunk from 'redux-thunk'
// 模拟 logger
const logger = store => next => action =>{
console.log('prev state',store.getState())
console.log('dispatch',action);
let result = next(action);
console.log('next state',store.getState());
return new Promise((resolve, reject) => {
resolve(result)
});
}
const store = createStore(reducers, compose(applyMiddleware(thunk, logger)))
export default store
注:
- 有的中间件有次序要求,使用前要查一下文档
bindActionCreators(actionCreators, dispatch)
把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch对每个 action creator 进行包装,以便可以直接调用它们。
参数
-
actionCreators
(Function or Object): 一个 action creator,或者一个 value 是 action creator 的对象。 -
dispatch
(Function): 一个由 Store实例提供的 dispatch函数。
返回值
(Function or Object): 一个与原对象类似的对象,只不过这个对象的 value 都是会直接 dispatch 原 action creator 返回的结果的函数。如果传入一个单独的函数作为 actionCreators,那么返回的结果也是一个单独的函数。
示例在react-redux中讲解
compose(...functions)
从右到左来组合多个函数。
当需要把多个store
增强器 依次执行的时候,需要用到它。
参数
(arguments): 需要合成的多个函数。预计每个函数都接收一个参数。它的返回值将作为一个参数提供给它左边的函数,以此类推。例外是最右边的参数可以接受多个参数,因为它将为由此产生的函数提供签名。(compose(funcA, funcB, funcC) 形象为 compose(funcA(funcB(funcC()))))
返回值
(Function): 从右到左把接收到的函数合成后的最终函数。
react-redux 基本使用
React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
UI 组件
有以下几个特征。
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态(即不使用this.state这个变量)
- 所有数据都由参数(this.props)提供
- 不使用任何 Redux 的 API
示例
const Title = props => <h1>{props.title}</h1>;
容器组件
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 API
React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。
connect()
React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。
示例
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(Title)
connect方法接受两个参数:mapStateToProps
和mapDispatchToProps
。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
connect参数 | 介绍 | 参数 |
---|---|---|
mapStateToProps | mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。 | 第一个永远是state ;第二个参数ownProps :代表容器组件的props对象 |
mapDispatchToProps | 用来建立 UI 组件的参数到store.dispatch方法的映射 |
dispatch 和ownProps (容器组件的props对象)两个参数 |
示例
const mapStateToProps = (state, ownProps) => {
return {
state: state,
title: state.increment + ownProps.title
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
actions: bindActionCreators(actionCreators, dispatch),
ownPropsClick: () => {
dispatch(actionCreators.increment())
console.log(ownProps.title)
}
}
}
// mapDispatchToProps 也可以是一个对象
// const mapDispatchToProps = {
// ownPropsClick: (filter) => {
// return {
// type: 'INCREMENT',
// filter: filter
// }
// }
// }
export default connect(mapStateToProps, mapDispatchToProps)(Test)
<Provider store>
<Provider store> 使组件层级中的 connect() 方法都能够获得 Redux store。正常情况下,你的根组件应该嵌套在 <Provider> 中才能使用 connect() 方法。
属性
-
store
(Redux Store): 应用程序中唯一的 Redux store 对象 -
children
(ReactElement) 组件层级的根组件。
示例
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
rootEl
)
redux的使用先讲到这里,如有错误之处希望大家积极留言!