Flux架构模式
- Flux的核心思想: 数据和逻辑永远单向流动
Action Creator
负责创建action,作为全部改变和交互的入口。当需要改变应用的状态或有View需要更新时,就需要发出一个action。产生的action就是一个普通对象,带有一个type属性。 action创建好以后,就会传递给Dispatcher。
Dispatcher
Dispatcher就是一个回调函数登记表,保存着有需要发送action的store列表。当action传过来,就会通过查表把这个action传递给所有注册的store。
Store
store保存着整个程序的state,而且更改逻辑都在store里。store存在多个,每一个会使用switch语句来判断action的类型,决定是否对这个action做出响应。如果store关心这个action,就会根据action找出需要变化的部分,更新state。更新state后,会触发一个onchange事件,Controller View会订阅这个事件。
Controller View
Controller View控制着所有的view,并且订阅Store中state的状态,当store中的状态更新完成,就会将view渲染成最新的state,用户看到的页面也就变化了。 另一方面,用户操作页面改变了view,Controller View会给Action Creator发起通知准备action。
因为flux存在很多不足之处,所以就不做代码演示,具体可以查看Andrew Ray 的文章《Flux For Stupid People》。 redux就是基于flux思想与函数式编程结合的热门架构。
Redux
Redux使用场景
- 用户的使用方式复杂
- 不同身份的用户有不同的使用方式(比如普通用户和管理员)
- 多个用户之间可以协作
- 与服务器大量交互,或者使用了WebSocket
- View要从多个来源获取数据
"如果你不知道是否需要 Redux,那就是不需要它。只有遇到 React 实在解决不了的问题,你才需要 Redux 。"
Action
Action是把数据从应用传到store的载体,一定要有一个type属性。Action本质上是一个个js普通对象,可以定义一个函数来生成Action。
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
Reducer
整个应用只有一个单一的reducer函数,这个函数就是传给createStore的第一个参数。reducer就是一个类似 (previousState, action) => newState 特征的纯函数。
项目中应该按照更新逻辑把reducer拆分成多个,每个子reducer负责独立管理state的一部分。
const initialState = {visibilityFilter: filter};
const todos = (state=initialState, action) => {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
case ADD_TODO:
return { ...state, visibilityFilter: action.filter }
default:
return state
}
};
Redux 提供了一个combineReducers方法,用于 Reducer 的拆分。你只要定义各个子 Reducer 函数,然后用这个方法,将它们合成一个大的 Reducer。
import { combineReducers } from 'redux';
const reducer = combineReducers({
todos,
counter
})
Store
Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。
1. 创建Store
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer, window.STATE_FROM_SERVER, applyMiddleware(thunk))
store.dispatch方法会触发 Reducer 的自动执行。为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。中间件 applyMiddleware
createStore()的第二个参数是可选的, 用于设置 state 初始状态。这对开发同构应用时非常有用,服务器端 redux 应用的 state 结构可以与客户端保持一致, 那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化。
2.发起action
import { addTodo } from './action';
import { reducer } from './reducer';
const store = createStore(reducer);
const {dispatch, getState, subscribe} = store;
dispatch(addTodo('Learn Redux'))
3.注册监听器
Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
import { createStore } from 'redux';
const store = createStore(reducer);
store.subscribe(listener);
显然,只要把 View 的更新函数(对于 React 项目,就是组件的render方法或setState方法)放入listen,就会实现 View 的自动渲染。
store.subscribe方法返回一个函数,调用这个函数就可以解除监听。
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
);
unsubscribe();
React-Redux
为了方便使用,Redux的作者封装了一个React专用的库React-Redux
UI组件
React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。
UI 组件有以下几个特征。
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态(即不使用
this.state这个变量)- 所有数据都由参数(
this.props)提供- 不使用任何 Redux 的 API
下面就是一个 UI 组件的例子。
const Title =
value => <h1>{value}</h1>;
因为不含有状态,UI 组件又称为"纯组件",即它纯函数一样,纯粹由参数决定它的值。
容器组件
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 API
React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
import { connect } from 'react-redux'
const mapStateToProps = (state, ownProps) => {
return {
active: ownProps.filter === state.visibilityFilter
}
}
const mapDispatchToProps = (
dispatch,
ownProps
) => {
return {
onClick: () => {
dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: ownProps.filter
});
}
};
}
const VisibleTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList);
- TodoList就是UI组件,VisibleTodoList就是容器组件
- mapStateToProps:将store中的数据作为props绑定到组件上
- mapDispatchToProps: 定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象
Provider组件
connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。
一种解决方法是将state对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state传下去就很麻烦。
React-Redux 提供Provider组件,可以让容器组件拿到state。
- 它的原理是
React组件的context属性
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)