工作流程
- 用戶(通過View)發出Action,發出方式就用到了dispatch方法。
- Store自動調用Reducer,並且傳入兩個參數:當前State和收到的Action,Reducer會返回新的State
- State一旦有變化,Store就會調用監聽函數,來更新View。
可以看到,在整個流程中數據都是單向流動的,這種方式保證了流程的清晰。
名詞解釋
createStore 創建 store 對象,包含 getState, dispatch, subscribe, replaceReducer
reducer reducer 是一個計劃函數,接收舊的 state 和 action,生成新的 state
action action 是一個對象,必須包含 type 字段
dispatch dispatch( action ) 觸發 action,生成新的 state
subscribe 實現訂閱功能,每次觸發 dispatch 的時候,會執行訂閱函數
combineReducers 多 reducer 合併成一個 reducer
replaceReducer 替換 reducer 函數
middleware 擴展 dispatch 函數!
redux 和 react 沒有關係,redux 可以用在任何框架中。
connect 不屬於 redux,它其實屬於 react-redux
UI組件和容器組件
React-Redux 將所有組件分成兩大類:UI 組件(presentational component)和容器組件(container component)。
UI組件:
- 只負責 UI 的呈現,不帶有任何業務邏輯
- 沒有狀態(即不使用this.state這個變量)
- 所有數據都由參數(this.props)提供
- 不使用任何 Redux 的 API
const Title = value => <h1>{value}</h1>;
容器組件:
- 負責管理數據和業務邏輯,不負責 UI 的呈現
- 帶有內部狀態
- 使用 Redux 的 API
如果一個組件既有 UI 又有業務邏輯,那怎麼辦?
將它拆分成下面的結構:外面是一個容器組件,裏面包了一個UI 組件。前者負責與外部的通信,將數據傳給後者,由後者渲染出視圖。(其實就是我們最常見的class組件)
connect()
作用:將 UI 組件生成容器組件
下面 TodoList 是 UI 組件,VisibleTodoList 就是生成的容器組件。
import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);
但是,因爲沒有定義業務邏輯,上面這個容器組件毫無意義,只是 UI 組件的一個單純的包裝層。爲了定義業務邏輯,需要給出下面兩方面的信息。
- 輸入邏輯:外部的數據(即state對象)如何轉換爲 UI 組件的參數
- 輸出邏輯:用戶發出的動作如何變爲 Action 對象,從 UI 組件傳出去。
因此,connect方法的完整 API 如下:
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
前者 mapStateToProps 負責輸入邏輯,即將state映射到 UI 組件的參數(props)
後者 mapDispatchToProps 負責輸出邏輯,即將用戶對 UI 組件的操作映射成 Action。
mapStateToProps()
第一個參數總是state對象,還可以使用第二個參數,代表容器組件的props對象。
mapStateToProps 執行後應該返回一個對象,裏面的每一個鍵值對就是一個映射。
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
後面的 getVisibleTodos 函數是可以根據 state 計算出 todos 的值。
mapDispatchToProps()
建立 UI 組件的參數到store.dispatch方法的映射。也就是說,它定義了哪些用戶的操作應該當作 Action,傳給 Store。它可以是一個函數,也可以是一個對象。
如果mapDispatchToProps是一個函數,會得到dispatch和ownProps(容器組件的props對象)兩個參數。
const mapDispatchToProps = (
dispatch,
ownProps
) => {
return {
onClick: () => {
dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: ownProps.filter
});
}
};
}
返回一個對象,該對象的每個鍵值對都是一個映射,定義了 UI 組件的參數怎樣發出 Action。它的每個鍵名也是對應 UI 組件的同名參數,鍵值應該是一個函數,會被當作 Action creator ,返回的 Action 會由 Redux 自動發出。
<Provider>
組件
connect方法生成容器組件以後,需要讓容器組件拿到state對象,才能生成 UI 組件的參數。
一種解決方法是將state對象作爲參數,傳入容器組件。但是,這樣做比較麻煩,尤其是容器組件可能在很深的層級,一級級將state傳下去就很麻煩。
React-Redux 提供Provider組件,可以讓容器組件拿到state。
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')
)
這樣一來,App的所有子組件就默認都可以拿到state了。
它的原理是React組件的context屬性:
class Provider extends Component {
getChildContext() {
return {
store: this.props.store
};
}
render() {
return this.props.children;
}
}
Provider.childContextTypes = {
store: React.PropTypes.object
}
舉例:
class VisibleTodoList extends Component {
componentDidMount() {
const { store } = this.context;
this.unsubscribe = store.subscribe(() =>
this.forceUpdate()
);
}
render() {
const props = this.props;
const { store } = this.context;
const state = store.getState();
// ...
}
}
VisibleTodoList.contextTypes = {
store: React.PropTypes.object
}
計時器完整代碼例子
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider, connect } from 'react-redux'
// React component
class Counter extends Component {
render() {
const { value, onIncreaseClick } = this.props
return (
<div>
<span>{value}</span>
<button onClick={onIncreaseClick}>Increase</button>
</div>
)
}
}
Counter.propTypes = {
value: PropTypes.number.isRequired,
onIncreaseClick: PropTypes.func.isRequired
}
// Action
const increaseAction = { type: 'increase' }
// Reducer
function counter(state = { count: 0 }, action) {
const count = state.count
switch (action.type) {
case 'increase':
return { count: count + 1 }
default:
return state
}
}
// Store
const store = createStore(counter)
// Map Redux state to component props
function mapStateToProps(state) {
return {
value: state.count
}
}
// Map Redux actions to component props
function mapDispatchToProps(dispatch) {
return {
onIncreaseClick: () => dispatch(increaseAction)
}
}
// Connected Component
const App = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
React-Router 路由庫
使用React-Router的項目,與其他項目沒有不同之處,也是使用Provider在Router外面包一層,畢竟Provider的唯一功能就是傳入store對象。
const Root = ({ store }) => (
<Provider store={store}>
<Router>
<Route path="/" component={App} />
</Router>
</Provider>
);