think
think copied to clipboard
Redux是什么?
redux是一个构建大型react应用的前端架构,结合了flux和函数式编程的思想。
flux
flux将一个应用分成四个部分:
- View 视图层
- Action 视图层发出的消息
- Dispatcher 接收视图层发出的actions,执行回调函数
- Store 存放应用的状态,发生变动时,更新View
各个部分关系大概如下:
函数式编程
举例:
function forEach(array,func){
for(var i = 0; i < array.length; i++){
func(array[i]);
}
}
var a = ["a","b","c"];
forEach(a,function(obj){print(obj);});
forEach(a,function(obj){print(obj + 1);});
//输出结果
a
b
c
a1
b1
c1
Redux设计思想
- web应用是一个状态机,视图和状态是一一对应的。
- 所有的状态保存在一个对象里边。
Redux概念和API
-
action
:应用数据的改变的描述 -
reducer
:决定应用数据新状态的函数,接收应用之前的状态和一个action返回的新的数据状态 -
store
:应用数据的存储中心 -
middleware
: redux提供中间件的方式,完成一些flux流程的自定义控制,同时形成其插件体系
action
action的作用就是定义store将要更新数据的信息(对应的动作,对应的参数值),也就是UI组件发出的通知,告知store数据的state将要改变了,所以基本的结构可以像这样(其实可以理解为它就是定义store的一个更新数据方法的参数):
const ADD_TODO = "ADD_TODO";
store.dispatch({
type:ADD_TODO, //这个字段名必须要有
text:"这个字段的名字随意,按照需要来"
});
因为一个应用action会非常之多,如果每次分发调用我们都要写一遍,肯定不值当,所以我们可以提供一个高阶函数来作为action的创建者:
function addTodo(text){
return {
type:"ADD_TODO",
text:text
}
}
store.dispatch(addTodo(text));
reducer
action描述了数据发生变化的事实,但却没有去更新数据。更新数据的操作就落到了reducer身上。
reducer是一个纯函数,它的作用是利用上一个状态的信息和action信息来产生下一个新状态。
(prevState,action) => newState
纯函数就是有什么样的输入有什么样的输出,纯函数是函数式编程的概念,遵循一些约束:
- 不得改写参数
- 不能异步调用(异步API,I/O,路由转换等)
- 不能调用不纯的函数(Date.noew(),Math.random())
具体实现:
/**
* reducer 描述
* @param prevState [之前状态]
* @param action [redux action]
* @return nextState [新状态]
*/
function reducer(prevState, action) {
var nextState = {...};
switch(action.type) {
case 'ADD_TODO': return Object.assign({}, prevState, nextState);
case 'REMOVE_TODO': return Object.assign({}, prevState, nextState);
......
default: return prevState;
}
}
应用刚创建时候的初始状态为undefined的,所以我们可以需要对它进行处理,也可以趁机赋默认值:
var initialState = {todos:...}
function reducer(prevState,action){
if(typeof prevState === 'undefined'){
return initialState;
}
switch(action.type){...}
return prevState;
}
当一个应用的reducer太多的时候,我们需要对reducer进行拆分,然后再进行组合:
function add(prevState, action) {}
function filter(prevState, action) {}
......
var reducers = redux.combineReducers({
add:add,
filter:filter
});
combineReducers方法是redux已经实现了的方法。
reducers不再负责整个应用状态的修改,而是将责任分配给其它的子reducer,每个子reducer相互独立,各有责任,也即分而治之的思想。
store
store是一个单例,存放了整个应用的所有状态数据,它有维护一颗状态树。
它连接了action,reducer和组件。
它提供了以下API:
* store.getState() 获取store里边的数据
* store.dispatch(action) 触发action来更新数据,是唯一的触发状态更新操作的方法。
* store.subscribe(listener) 设置监听函数,一旦某一个state改变,就自动执行这个函数,可以通过它返回的方法取消订阅
* store.replaceReducer(nextReducer) 允许你动态的去修改正在计算state的reducer
store是由redux模块的createStore方法创建的,可以通过一个简单实现来加深理解:
const createStore = (reducer)=>{
let state;
let listeners = [];
const getState = ()=>state;
const dispatch = (action)=>{
state = reducer(state,action);
listeners.forEach(listener => listener());
};
const subscribe = (listener)=>{
listeners.push(listener);
return ()=>{
listeners = listeners.filter(l => l!==listener);
}
};
const replaceReducer = (nextReducer)=>{
reducer = nextReducer;
}
dispatch({});
return {getState,dispatch,subscribe,replaceReducer}
}
middleware
middleware就是一个函数,它对store.dispatch进行了改造,在发出action和执行reducer这两步之间,添加一些扩展的功能。 简单的类似这个:
let next = store.dispatch();
store.dispatch = (action)=>{
console.log('current action',action);
next(action);
console.log('next state',store.getState());
}
中间件工作流:
中间件用法:
import { applyMiddleware,createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();
const store = createStore(reducer,applyMiddleware(logger));
applyMiddleware
是redux提供传递中间件的方法。
有许多开源的中间件已经实现了很多事情了,如果没有,可能你需要自己实现一个。
Redux数据流动
Redux三大原则
单一数据源
整个应用的state被存储在一棵对象树种,并且这棵树值存在于唯一的一个store中。
- 便于同构应用的开发。(后端服务序列化注入客户端数据)
- 调试容易。(本地保存state)
State是只读的
唯一改变state的方法就是dispatch action,action是一个用于藐视已发生事件的普通对象。
- 集中化处理操作,避免竞态出现。
- action可以被日志打印,序列化,储存,后期调试或测试回放出来
使用纯函数来修改State
使用纯函数reducer来计算新的state。而且reducer可以被拆分成多个部分处理不同的业务逻辑,符合分而治之的思想。
Redux使用场景
你可能不需要redux:
- 用户使用方式非常简单
- 用户之间没有协作
- 不需要与服务器的大量交互,也没有使用WebSocket
- 视图层(View)只从单一来源获取数据
也就是说UI层非常简单,没有很多互动的时候都不许要Redux,使用了反而会增加应用的复杂度。
如果有以下场景则可以考虑使用redux(由此说明,redux并不是必须,还可以有其它的一些结构来代替)
- 某个组件状态需要共享给其它组件(比如兄弟子组件)
- 某个状态需要在任何地方都可以拿到(类似用户的登录状态在多个页面需要使用的情况等)
- 一份组件需要改变全局状态
- 一个组件需要改变另一个组件的状态,而且这两个组件之间并没有“血缘关系”
React-redux
redux和react之间没有关系。redux支持React,Vue,Angular,Ember,jQuery甚至原生JavaScript。
安装:
npm i react-redux --save
首先了解一个开发思想,容器组件和展示组件(纯组件)相分离的开发思想。
也就是UI组件复制UI的呈现,容器组件负责管理数据和逻辑。
react-redux提供connect()方法,用来连接UI组件和容器。
import {connect} from 'react-redux';
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList);
mapStateToProps() 建立一个state对象(外部的,UI组件的state)到props对象的映射关系。
const mapStateToProps = (state)=>{
return{
todos: appTodos(state.todos,state.text)
}
}
这个方法订阅store,当state更新时自动执行,重新计算UI组件的参数,触发UI的rerender。
connect可以省略这个参数,这时UI组件不订阅store,store更新不会引起UI组件的更新。
mapDispatchToProps() 建立UI组件的参数到store.dispatch的映射,它定义了哪些用户的操作应该当做action传给store,可以是函数也可以是对象。
作为一个函数,返回一个对象,该对象的每一个键值对都是一个映射,定义了UI组件的参数怎样发出Action。
const mapDispatchToProps = (dispatch,ownProps)=>{
return {
onClick:()=>{
dispatch({
type:'ADD_TODO',
text:ownProps.text
})
}
}
}
作为一个对象,它的每一个键名应该对应UI组件的同名参数,键值应该是一个函数,会被当做Action creator,返回的Action则由redux自动发出。
const mapDispatchToProps = {
onClick:(filter) =>{
type:'ADD_TODO',
text:text
}
}
这个方法订阅store,当state更新时自动执行,重新计算UI组件的参数,触发UI的rerender。
connect可以省略这个参数,这时UI组件不订阅store,store更新不会引起UI组件的更新。
<Provider>
组件
connect方法生成容器组件之后,需要让容器组件获得state对象,才能生成UI组件的参数。
react-redux提供Provider组件,可以让容器拿到state。
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import todoApp from './reducers/todoApp';
import App from './components/App';
let store = createStore(todoApp);
render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('app')
);
另外,Provider组件可以和路由Router搭配使用时是没有任何问题的。