Redux这个npm包,提供若干API让我们使用reducer创建store,并能更新store中的数据或获取store中最新的状态。“Redux应用”指使用redux结合视图层实现(React)及其他前端应用必备组件(路由库、Ajax请求库)组成的完成类Flux思想的前端应用。
Redux 三大原则
-
单一数据源
Redux思想中,一个应用永远只有唯一的数据源。combineReducers化解了数据源对象过于庞大的问题。 -
状态是只读的
Redux中,我们并不会自己用代码来定义一个store。取而代之的是,我们定义一个rducer,它的功能是根据当前触发的action对当前应用的状态(state)进行迭代,这里我们斌没有直接修改应用的状态,而是返回一份全新的状态。
Redux提供的createStore方法会根据reducer生成store。最后,我们利用 store.dispatch 方法来达到修改状态的目的。 -
状态修改均有纯函数完成
在Redux中,我们通过定义reducer来确定状态的修改。
Redux核心API
Redux的核心是一个store,这个store由Redux提供的 createStore(reducers, [,initialState])方法生成。
Redux里,负责响应action并修改数据的角色是reducer。其函数签名为reducer(previousState, action ) => newState
。所以,reducer的职责就是根据 previousState 和 action 计算出新的 newState。
// MyReducer.js
const initialState = {
todos: [],
}
// 我们定义的todos这个reducer在第一次执行的时候,会返回 { todos: [] }作为初始化状态
function todos(previousState = initalState, action) {
switch(action.type) {
case 'xx': {
// 具体的业务逻辑
}
default:
return previousState;
}
}
Redux = Reducer + Flux
通过createStore方法创建的store是一个对象,它本身又包含4个方法
- getState():获取store中当前的状态
- dispatch(action): 分发一个action,并返回这个action,这是唯一能改变 store 中数据的方式
- subscribe(listener):注册一个监听者,它在store发生变化时被调用
- replaceReducer(nextReducer):更新当前store里的reducer,一般只会在开发模式中调用该方法
与React绑定
react-redux提供了一个组件和一个API帮助Redux和React进行绑定,一个是React组件<Provider />,一个是 connect(). <Provider />接受一个store作为props,是整个Redux应用的顶层组件,connect()提供整个React应用的任意组件中获取store中数据的功能。
Redux middleware
它提供了一个分类处理action的机会。
Redux同步数据流动
面对多样的业务场景,单纯地修改 dispatch 或 reducer 的代码显然不具有普适性,我们需要的是可以组合的、自由插拔的插件机制,这一点 Redux借鉴了 Koa (它是用于构建 Web 应用的Node.js 框架)里 middleware 的思想。
应用middleware后Redux处理事件的逻辑
理解middleware机制
Redux提供了applyMiddleware方法来加载middleware。
Redux中的applyMiddleware源码
import compose from './compose'
export default function applyMiddleware(...middlewares) {
return (next) => (reducer, initialState) => {
let store = next(reducer, initialState)
let dispatch = store.dispatch
let chain = []
// 把store的getState方法和dispatch方法分别直接或间接赋值给middlewareAPI
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// 让每个middleware带着middlewareAPI这个参数分别执行一遍
// 执行完后,获得数组 [f1,f2,f3,f4,...,fn]
// middlewareAPI第二个箭头函数返回的匿名函数,因为闭包,每个匿名函数都可以访问相同的store,即middlewareAPI
/*
middlewareAPI中的dispatch为什么要用匿名函数包裹呢?
我们用 applyMiddleware 是为了改造 dispatch,所以 applyMiddleware 执行完后,dispatch 是 变化了的,而 middlewareAPI 是 applyMiddleware 执行中分发到各个 middleware 的,所以 必须用匿名函数包裹 dispatch,这样只要 dispatch 更新了,middlewareAPI 中的 dispatch 应 用也会发生变化。
*/
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
- 函数式编程思想设计
middleware设计的是一层层包裹的匿名函数,这其实是函数式编程中的currying,他是一种私用匿名单参数函数来实现多参数函数的方法。
currying的middleware结构的好处有以下两点:
- 易串联:currying函数具有延迟执行的特性,通过不断currying形成的middleware可以累积参数,再配合组合(compose)的方式,很容易形成pipeline来处理数据流
- 共享store:在applyMiddleware执行的过程中,store还是旧的,但是因为闭包的存在,applyMiddleware完成后,所有的middleware内部拿到的store是最新且相同的。
import { createStore, applyMiddleware, compose } from 'Redux';
import rootReducer from '../reducers';
const finalCreateStore = compose(
// 在开发环境中使用 middleware
applyMiddleware(d1, d2, d3),
DevTools.instrument()
)
-
给middleware分发store
let newStore = applyMiddleware(mid1,mid2,mid3,...)(createStore)(reducer, null)
看注释。 -
组合串联middleware
dispatch = compose(...chain)(store.dispatch);
// compose 实现方式
function compose(...funs) {
return arg => func.reduceRight((composed, f) => f(composed), arg)
}
compose(...funcs)返回一个匿名函数,其中funcs就是chain数组。当调用reduceRight时,依次从funcs数组的右端取一个函数fx拿来执行,fx的参数composed就是前一次fx+1执行的结果,而第一次执行的fn(n代表chain的长度)的参数arg就是 store.dispath.假设n=3
dispatch = f1(f2(f3(store.dispatch)))
这时调用新dispatch时,每一个middleware都依次执行了。
- 在middleware中调用dispatch会发声什么
compose之后,所有的middleware算是串联起来了。可是还有一个问题,在分发store时,我们提到过每个middleware都可以访问store,即middlewareAPI这个变量,也可以拿到store的dispatch属性。那么,在middleware中调用store.dispatch()会发生什么,和调用next()有什么区别?
const logger = store => next => action => {
console.log('dispatch:', action);
next(action);
console.log('finish:', action);
};
const logger = store => next => action => { c
onsole.log('dispatch:', action);
store.dispatch(action);
console.log('finish:', action);
};
在分发 store 时我们解释过,middleware 中 store 的 dispatch 通过匿名函数的方式和最终 compose 结束后的新 dispatch 保持一致,所以,在 middleware 中调用 store.dispatch() 和在其他 任何地方调用的效果一样。而在 middleware 中调用 next(),效果是进入下一个 middleware。
这就是一个洋葱模型
next代表下一个执行的中间件,每次return回去的都是一个未执行的函数,只有最后调用才能执行。
// 实现之后的效果
// 这时候返回的是一个经过层层中间件封装的dispatch,新的dispatch函数
newDispatch = M1(M2(M3(dispatch)))
// M1中的next 就是M2
// M2中的next就是M3
// M3中的next就是dispatch,执行dispatch
// 调用
newDispatch(action)
Redux middleware流程图
正常情况下,如上图左,我们分发一个action时,middleware通过next(action)一层层传递和处理action直到Redux原生的dispathc。当某个middleware使用store.dispatch(action)分发action,会发声右图的情况,就会形成无限循环。那么store.dispatch(action)的用武之地在哪里呢?
异步请求的时候,使用到Redux Thunk
const thunk = store => next => action => {
typeof action === 'function'?
action(store.dispatch, store.getState) : next(action)
}
Redux Thunk会判断action是否是函数。如果是,执行action,否则继续传递action到下一个middleware。
const getThenShow = (dispatch, getState) => {
const url = 'http://xxx.json'
fetch(url)
.then((res) => {
dispatch({
type: 'SHOW_MESSAGE_FOR_ME',
message: res.json(),
})
}).catch( ()=> {
dispatch({
type: 'FETCH_DATA_FAIL',
message: 'error'
})
} )
}
// 再应用中调用 store.dispatch(getThenShow)
Redux异步流
使用middleware简化异步请求
- redux-thunk
Thunk函数实现上就是针对多参数的currying以实现对函数的惰性求值。任何函数,只要参数有回调函数,就能写成Thunk函数的形式。
redux-thunk的源代码:
function createThunkMiddleware(extraArg) {
return ({dispath, getState} => next => action => {
if ( typeof action === 'function' ) {
return action(dispatch, getState, extraArg)
}
return next(action)
})
}
- redux-promise
抽象promise来解决异步流问题。
redux-promise 兼容了 FSA 标准,也就是说将返回的结果保存在 payload 中。实现过程非常容易理解,即判断 action 或action.payload是否为 promise,如果是,就执行 then,返回的结果再发送一次 dispatch。
使用ES7的async和await语法,简化异步过程
const fetchData = (url, params) => fetch(url, params);
async function getWeather(url, params) {
const result = await fetchData(url, params);
if( result.error ) {
return {
type: 'GET_WEATHER_ERROR',
error: result.error
}
}
return {
type: 'GET_WEATHER_SUCCESS',
payload: result
}
}
- redux-composable-fetch
实际请求中,加上loading状态
这时候异步请求的action
{
url: '/api/weather.json',
params: {
city: encodeURI(city),
},
types: ['GET_WEATHER', 'GET_WEATHER_SUCESS', 'GET_WEATHER_ERROR'],
}
和FSA不一样了,没有types,有了url和type代表请求状态
const fetchMiddleware = store => next => action => {
if (!action.url || !Array.isArray(action.types)) {
return next(action);
}
const [LOADING, SUCCESS, ERROR] = action.types;
next({
type: LOADING,
loading: true,
...action,
});
fetch(action.url, { params: action.params })
.then(result => {
next({
type: SUCCESS,
loading: false,
payload: result,
});
})
.catch(err => {
next({
type: ERROR,
loading: false,
error: err,
});
});
}
使用middleware处理复杂异步流
1.轮询
-
多异步串联
使用Promise -
redux-saga
最优雅通用的解决方法,有灵活而强大的协程机制,可以解决任何复杂的异步交互。
Redux和路由
我们需要一个这样的路由系统,它既能利用React Router 的声明式特性,又能将路由信息整合进 Redux store 中。
React Router
1.基本原理
React Router流程图
-
React Router特性
React Router与React对比
- 声明式的路由
// 实例
import { Router, Route, browserHistory } from 'react-router';
const routes = (
<Router history={browserHistory}>
<Route path='/' component{App} />
</Router>
)
- 嵌套路由及路径匹配
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
const routes = (
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={MailList} />
<Route path="/mail/:mailId" component={Mail} />
</Route>
</Router>
);
App 组件承载了显示顶栏和侧边栏的功能,而 React Router 会根据当前的 url 自动判断该显示邮件列表页还是详情页:
. 当 url 为 / 时,显示列表页;
. 当 url 为 /mail/123 时,显示详情页。
- 支持多种路由切换方式
hashChange 或是 history.pushState。hashChange 的方式拥有良好的浏览器兼容性,但是 url 中却多了丑陋的 /#/ 部分;而 history.pushState 方法则能给我们提供优雅的 url,却需要额外的服务端配置解决任意路径刷新的问题。
React Router Redux
当我们采用 Redux 架构时,所有的应用状态必须放在一个单一的 store 中管理,路由状态也不例外。而这就是 React Router Redux 为我们实现的主要功能。
- React Router与Redux store绑定
React Router Redux 提供了简单直白的 API——syncHistoryWithStore 来完成与 Redux store的绑定工作。
import { browserHistory } from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'
import reducers from '<project-path>/reducers'
const store = createStore(reducers);
const history = syncHistoryWithStore(browserHistory, store);
- 用Redux的方式改变路由
对Redux的store进行增强,以便分发的action能被正确识别
import { browserHistory } from 'react-router';
import { routerMiddleware } from 'react-router-redux';
const middleware = routerMiddleware(browserHistory);
const store = createStore(
reducers,
applyMiddleware(middleware)
);
使用
import { push } from 'react-router-redux';
// 切换路由到 /home
store.dispatch(push('/home'));
Redux 与 组件
对比容器组件和展示型组件Redux中,强调了3中不同类型的布局组件:Layouts、Views和Components。它常常是无状态函数,传入主体内容的children属性。
const Layout = ({ children } => {
<div className = 'container'>
<Header />
<div className="contaier">
{ children }
</div>
</div>
})
- Views
子路由入口组件,描述子路由入口的基本结构,包含此路由下所有的展示型组件。
@connect((state) => {
//...
})
class HomeView extends Component {
render() {
const { sth, changeType } = this.props;
const cardProps = { sth, changeType };
return (
<div className="page page-home">
<Card {...cardProps} />
</div>
);
}
}
3、Components
末级渲染组件,描述了从路由以下的子组件。包含具体的业务逻辑和交互,但所有的数据和action都是油Views传下来。
class Card extends Components {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(opts) {
const { type } = opts;
this.props.changeType(type);
}
render() {
const { sth } = this.props;
return (
<div className="mod-card">
<Switch onChange={this.handleChange}>
// ...
</Switch>
{sth}
</div>
);
}
}
网友评论