1.Redux 设计理念
Web 应用是一个状态机,视图与状态是一一对应的
所有的状态,保存在一个对象里面
2.基本概念和API
Redux 的核心就是 store, action, reducer store.dispatch(action) ——> reducer(state, action) ——> final state
(1)store 就是保存数据的地方,redux 提供createStore 函数,生成Store
store = redux.createStore(reducer, []);
store.getState() //返回store的当前状态
Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
store.subscribe(listener);
store.subscribe 方法返回一个函数,调用这个函数就可以解除监听
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
);
unsubscribe(); //解除监听
Store 的实现
store.getState() //获取当前状态 store.dispatch() //触发action store.subscribe() //监听state状态 import { createStore } from ‘redux’; let { subscribe, dispatch, getState } = createStore(reducer, window.STATE_FORM_SERVER); window.STATE_FORM_SERVER //是整个应用的初始状态值
(2)action 是一个普通的object,必须有一个type属性,表明行为的类型。
const action = {
type: ’add_todo’,
text: ‘read’,
time
…
}
action描述当前发生的事情,改变State的唯一方法就是通过action,会将数据送到store。
一般用actionCreator 工厂模式产生,View要发出的消息类型对应action的类型,手写起来很费劲。
const ADD_TODO = “添加 todo”;
function addTodo(text){
return {
type: ADD_TODO,
text
}
}
const action = addTodo(‘Learn’);
addTodo 方法就是一个Action Creator
View 发出Action的唯一途径 store.dispatch(action) //触发事件
(3)reducer 其实就是一个普通函数,主要用来改变state. Store 收到View 发出的Action 以后,必须返回一个新的State,View 才会发生变化。 而这个计算新的State的过程就叫Reducer.
const reducer = function(state, action){
switch(state.text){
case ‘add_todo’:
return state.contact(‘…’);
default:
return state;
}
}
当然实际开发不像上面例子这么简单,需要在创建state的时候就知道state的计算规则,将reducer传入:
store = redux.createStore(reducer);
Reducer 纯函数,只要有同样的输入必然返回同样的输出。不能改变原来的state而是通过Reducer返回一个新的state。
//state 是一个对象 function reducer(state, action){ return Object.assign({},state, {thingToChange}); return {…state, …newState}; } //state 是一个数组 function reducer(state, action){ return […state, newItem]; }
3.Reducer的拆分和合并
在实际项目中,reducer 很庞大,不易阅读管理,我们可以将reducer 拆分成小的函数,不同的函数对应处理不同的属性。然后将其合并成一个大的reducer
Reducer 提供了一个方法combineReducers方法来合并reducer.
const chatReducer = (state = defaultState, action = {}) => { return { chatLog: chatLog(state.chatLog, action), statusMessage: statusMessage(state.statusMessage, action), userName: userName(state.userName, action) } }; import { combineReducers } from 'redux'; const chatReducer = combineReducers({ chatLog, statusMessage, userName }) export default todoApp;
你可以把所有子reducers 放在一个文件夹里,然后统一引入。
import { combineReducers } from 'redux'
import * as reducers from './reducers'
const reducer = combineReducers(reducers)
4.中间件和异步操作
我们使用redux ,用户发出action,Reducer算出新的state,然后重新渲染界面。这里Reducer是立即算出state,立即响应的,同步执行的顺序。
可是如果我们需要执行异步实现,Reducer执行完之后,自动执行呢? 这里就需要用到middleWare(中间件)。
中间件加在什么地方合适?我们来简单分析下。
1. Reducer 是纯函数,用来计算state,相同的输入必然得到相同的输出,理论上纯函数不允许读写操作。
2. View和state是一一对应,是state的呈现,没有处理能力。
3. Action 是存放数据的对象,即消息的载体,被触发操作。
最后发现,只有加在store.dispatch() 比较合适。添加日志功能,把 Action 和 State 打印出来,可以对store.dispatch
进行如下改造。
let next = store.dispatch; store.dispatch = function dispatchAndLog(action) { console.log('dispatching', action); next(action); console.log('next state', store.getState()); }
总结:中间件其实就是一个函数,对store.dispatch方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。
中间件的用法:
const store = createStore(
reducer,
initial_state,
applyMiddleware(logger)
);
createStore方法可以接受整个应用的初始状态作为参数,将中间件(logger)放在applyMiddleware
方法之中,传入createStore
方法,就完成了store.dispatch()
的功能增强。
applyMiddleware 是Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。下面是它的源码:
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer); var dispatch = store.dispatch; var chain = []; var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }; chain = middlewares.map(middleware => middleware(middlewareAPI)); dispatch = compose(...chain)(store.dispatch); return {...store, dispatch} } }
可以看到,中间件内部(middlewareAPI)可以拿到getState和dispatch这两个方法。
异步操作的一个解决方案,就是让 Action Creator 返回一个 Promise 对象。看一下redux-promise
的源码:
export default function promiseMiddleware({ dispatch }) { return next => action => { if (!isFSA(action)) { return isPromise(action) ? action.then(dispatch) : next(action); } return isPromise(action.payload) ? action.payload.then( result => dispatch({ ...action, payload: result }), error => { dispatch({ ...action, payload: error, error: true }); return Promise.reject(error); } ) : next(action); }; }
从上面代码可以看出,如果 Action 本身是一个 Promise,它 resolve 以后的值应该是一个 Action 对象,会被dispatch
方法送出(action.then(dispatch)
),但 reject 以后不会有任何动作;如果 Action 对象的payload
属性是一个 Promise 对象,那么无论 resolve 和 reject,dispatch
方法都会发出 Action。
5.React+Redux
redux将所有组件分为UI组件和容器组件。
UI 组件有以下几个特征。
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态(即不使用this.state这个变量)
- 所有数据都由参数(this.props)提供
- 不使用任何 Redux 的 API
UI 组件又称为"纯组件",即它纯函数一样,纯粹由参数决定它的值。
容器组件的特征恰恰相反。
1.负责管理数据和业务逻辑,不负责 UI 的呈现
2.带有内部状态
3.使用 Redux 的 API
React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
上面代码中,connect方法接受两个参数:mapStateToProps和mapDispatchToProps。
它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
6. react+redux 的 simple 示例
原理图:
<div id="container"></div>
<script type="text/babel">
//工厂Action
var addTodoAction = function(text){
return {
type: 'add_todo',
text: text
}
};
var todoReducer = function(state,action){
if(typeof state === 'undefined') return [];
switch(action.type){
case 'add_todo':
return state.slice(0).concat({
text: action.text,
completed:false
});
default:
return state;
}
};
var store = redux.createStore(todoReducer);
var App = React.createClass({
//获取初始状态
getInitialState: function(){
return {
items: store.getState()
};
},
componentDidMount: function(){
var unsubscribe = store.subscribe(this.onChange);
},
onChange: function(){
this.setState({
items: store.getState()
});
},
handleAdd: function(){
var input = ReactDOM.findDOMNode(this.refs.todo);
var value = input.value.trim();
if(value){
store.dispatch(addTodoAction(value));
}
input.value = '';
},
render: function(){
return(
<div>
<input ref="todo" type="text" placeholder="input todo type"/>
<button onClick ={this.handleAdd}>Add</button>
<ul>
{
this.state.items.map(function(item){
return <li>{item.text}</li>
})
}
</ul>
</div>
);
}
});
ReactDOM.render(
<App />,
document.getElementById('container')
)
</script>