首先,一張 Redux 解釋圖鎮(zhèn)樓:
【回顧】Redux 的核心: store 是什么?(createStore 函數(shù)的實現(xiàn))
const store = createStore(reducer);
store 是一個對象,包含3個方法:getState
、dispatch
、subscribe
// createStore 函數(shù)實現(xiàn)
const createStore = (reducer) => {
let state;
let listeners = [];
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener());
}
const subscribe = (listener) => { // listener 就是一個要執(zhí)行的函數(shù)
listeners.push(listener);
return () => { // 采用柯里化方式注銷監(jiān)聽器,用法:store.subscribe(listener)();
listeners = listeners.filter(l => l != listener);
}
}
dispatch({}); // 初始化state
return { getState, dispatch, subscribe }
}
由函數(shù)可知,當(dāng)用戶
dispatch
一個 action 時,會自動調(diào)用reducer
從而得到最新的 state,該 state 可通過getState
函數(shù)獲取,并且會執(zhí)行所有已注冊的函數(shù)。
所以,redux 的套路就是(參考 React小書 ):
// 定一個 reducer
function reducer (state, action) {
/* 初始化 state 和 switch case */
}
// 生成 store
const store = createStore(reducer)
// 監(jiān)聽數(shù)據(jù)變化重新渲染頁面,即更新狀態(tài)的過程
store.subscribe(() => renderApp(store.getState()))
// 首次渲染頁面
renderApp(store.getState())
// 后面可以隨意 dispatch 了,頁面自動更新
store.dispatch(...)
【問題】:React 和 Redux 之間是如何連接?
從圖中可以看到,Store 通過 Provider 傳遞給了我們的 React 組件,因此,使得組件能夠獲取到 store。那么它是如何將做到的呢?
為了弄明白 React 和 Redux 之間是如何連接的,我們需要了解以下一些內(nèi)容(參考 React小書 ):
一、背景:React 中父組件 context 的作用,用以擺脫狀態(tài)提升
在 React 中,父組件使用 getChildContext(),可以將 store 放到它的 context 里面,相當(dāng)于給子組件設(shè)置了一個全局變量,這樣每個子組件就都可以獲取到 store。
// 父組件
class Index extends Component {
// 提供 context 的組件必須提供 childContextTypes 作為 context 的聲明和驗證
static childContextTypes = {
store: PropTypes.object
}
// 一個組件可以通過 getChildContext 方法返回一個對象,這個對象就是子樹的 context
getChildContext () {
return { store }
}
render () {
return (
<div>
<Header />
<Content />
</div>
)
}
}
// 子組件
class Header extends Component {
// 聲明想要的 context 里面的哪些狀態(tài),以便通過 this.context 進(jìn)行訪問
// 子組件要獲取 context 里面的內(nèi)容的話,就必須寫 contextTypes 來聲明和驗證你需要獲取的狀態(tài)的類型
static contextTypes = {
store: PropTypes.object
}
constructor () {
super()
this.state = { themeColor: '' }
}
componentWillMount () {
this._updateThemeColor()
}
_updateThemeColor () {
// 子組件可以訪問到父組件 context 里面的內(nèi)容
const { store } = this.context
const state = store.getState()
this.setState({ themeColor: state.themeColor })
}
render () {
return (
<h1 style={{ color: this.state.themeColor }}>React.js 小書</h1>
)
}
}
如果一個組件設(shè)置了 context,那么它的子組件都可以直接訪問到里面的內(nèi)容,它就像這個組件為根的子樹的全局變量。任意深度的子組件都可以通過 contextTypes 來聲明你想要的 context 里面的哪些狀態(tài),然后可以通過 this.context 訪問到那些狀態(tài)。
context 存在的問題:首先,它是一個試驗性的API,不穩(wěn)定,可能會改變,雖然好多庫都用到了這個特性;其次它是脆弱的,如果在層級中的任何一個組件執(zhí)行了 shouldComponentUpdate 返回 false,context 則不會傳遞給其之后所有的子組件。
二、react-redux 的誕生
因為 context 是一個比較危險的特性,我們不想在自己寫組件的時候被其污染,我們需要將其剝離出來,因此,react-redux 誕生了,其中的 Provider
以及 connect
就幫助我們將 React 的組件和 Redux 的 store 進(jìn)行了連接。
1. Provider 的實現(xiàn)
作用:充當(dāng)父組件的作用,把 store 放到自己的 context 里面,讓子組件 connect 的時候獲取。
export class Provider extends Component {
static propTypes = {
store: PropTypes.object,
children: PropTypes.any
}
static childContextTypes = {
store: PropTypes.object
}
getChildContext () {
return {
store: this.props.store
}
}
render () {
return (
<div>{this.props.children}</div>
)
}
}
2. 高階組件 connect(connect 實現(xiàn))
高階組件:高階組件是一個接受一個組件為參數(shù),并返回一個被包裝過的組件的函數(shù),即返回傳入props的原組件。
connect 的作用:和 React 的 context 打交道,將 context 中的數(shù)據(jù)取出來,并以 prop 的形式傳遞給 Dumb 組件。
const mapStateToProps = (state) => { themeColor: state.themeColor }
const mapDispatchToProps = (dispatch) => ({
onSwitchColor(color) => {
dispatch({ type: 'CHANGE_COLOR', themeColor: color })
}
});
// connect 實現(xiàn)
// connect 接受 mapStateToProps 和 mapDispatchProps 參數(shù)后,返回的函數(shù)是高階組件,該高階組件接受一個組件作為參數(shù),然后用 Connect 包裝之后返回
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor () {
super()
this.state = {
allProps: {}
}
}
componentWillMount () {
const { store } = this.context
this._updateProps()
store.subscribe(() => this._updateProps())
}
_updateProps () {
const { store } = this.context
let stateProps = mapStateToProps
? mapStateToProps(store.getState(), this.props)
: {} // 防止 mapStateToProps 沒有傳入
let dispatchProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, this.props)
: {} // 防止 mapDispatchToProps 沒有傳入
this.setState({
allProps: {
...stateProps,
...dispatchProps,
...this.props
}
})
}
render () {
return <WrappedComponent {...this.state.allProps} />
}
}
return Connect
}
connect 接口:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])(Component)