react-redux高階元件connect方法使用介紹以及實現原理
redux
講connect
之前先來回顧一下redux
的基本用法, 見下面的例子:
import { createStore } from 'redux';
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
// 建立 Redux store 來存放應用的狀態。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter);
// 可以手動訂閱更新,也可以事件繫結到檢視層。
store.subscribe(() =>
console.log(store.getState())
);
// 改變內部 state 惟一方法是 dispatch 一個 action。
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1
以上總結起來就只有下面四個方法
let store = createStore(reducer); // 建立store
store.getState( ); // 獲取state值
store.dispatch({ type: "text" }); // 使用action更改在reducer中定義好的更改store的更改策略
store.subScribe(render); // 設定監聽函式, 在更改store之後觸發
關於redux
的更多內容, 可以參閱官方文件.
好了, 進入正題
react-redux 高階元件 connect方法介紹以及實現原理
在使用介紹connect
之前, 先簡單介紹一下什麼是高階元件.
高階元件
高階元件就是一個函式,傳給它一個元件,它返回一個新的元件。 實際上就是一個類工廠, 見下面的一個例子.
import React, { Component } from 'react'
export default (People) => {
class Star extends Component {
// 可以做很多自定義邏輯
render () {
return <People/>
}
}
return Star
}
這就好比一個普通的人, 經過公司的包裝之後, 變成一個會很多種技能的明星一樣.
import React, { Component } from 'react'
export default (People, things) => {
class Star extends Component {
constructor () {
super()
this.state = { data: null }
}
componentWillMount () {
ajax.get('/data/' + things, (data) => {
this.setState({ data })
})
}
render () {
return <People data={this.state.data} />
}
}
return Star
}
在People
推上市場之前, 先告訴公司他需要什麼things
, 然後公司在componentWillMount
階段對他需要的things
進行準備, 最後返回一個Star
React.js 的 context
還要再說一個小概念, 如果不提這個無法講述connect的實現原理, 因此如果不想看原理只想看使用方法的話, 這一部分可以跳過, 不過博主會用簡單的語言描述這個問題, 最好看一下.ヾ(◍°∇°◍)ノ゙
某個元件只要往自己的 context
裡面放了某些狀態,這個元件之下的所有子元件都直接訪問這個狀態而不需要通過中間元件的傳遞。一個元件的 context
只有它的子元件能夠訪問,它的父元件是不能訪問到的,你可以理解每個元件的 context
就是瀑布的源頭,只能往下流不能往上飛。
那麼怎麼設定context
呢? 見下面的程式碼
class Parent extends Component { // 父元件
static childContextTypes = {
tags: PropTypes.string
}
constructor () {
super()
this.state = { tags: 'hello' }
}
getChildContext () {
return { index: this.state.tags }
}
render () {
return (
<div>
<Title />
</div>
)
}
}
class Title extends Component {
static contextTypes = {
tags: PropTypes.string
}
render () {
return (
<h1>{ this.context.tags }</h1>
)
}
}
總的來說:
一個元件可以通過 getChildContext
方法返回一個物件, 這個物件就是子樹的 context
, 提供 context
的元件必須提供 childContextTypes
作為 context
的宣告和驗證.
如果一個元件設定了 context
, 那麼它的子元件都可以直接訪問到裡面的內容, 它就像這個元件為根的子樹的全域性變數。任意深度的子元件都可以通過 contextTypes
來宣告你想要的 context
裡面的哪些狀態, 然後可以通過 this.context
訪問到那些狀態.
connect
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
connect
有四個引數, 但是後兩個引數用到的很少, 所以本篇部落格之探討前兩個引數.
connect 的第一個引數是 mapStateToProps
這個函式允許我們將 store 中的資料作為 props 繫結到元件上
const mapStateToProps = (state) => { // 正常我們在react-redux中會這樣書寫
return {
themeColor: state.themeColor
}
}
People = connect(mapStateToProps)(People) // connect返回來的是一個函式, 因此還要再一次呼叫傳入元件
實現主要原理, 就是將需要繫結的props
作為一個函式傳過來, 在connect
中傳給mapStateToProps
一個真實的store
的資料
const connect = (mapStateToProps) => (People) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor () {
super()
this.state = { allProps: {} }
}
componentWillMount () {
const { store } = this.context
this.setProps()
}
setProps () {
const { store } = this.context
let stateProps = mapStateToProps(store.getState(), this.props) // 額外傳入 props
this.setState({
allProps: { // 整合普通的 props 和從 state 生成的 props
...stateProps,
...this.props
}
})
}
render () {
return <People {...this.state.allProps} />
}
}
return Connect
}
connect 的第二個引數是 mapDispatchToProps
由於更改資料必須要觸發action
, 因此在這裡的主要功能是將 action
作為props
繫結到 元件上
const mapDispatchToProps = (dispatch, ownProps) => {
return {
increase: (...args) => dispatch(actions.increase(...args)),
decrease: (...args) => dispatch(actions.decrease(...args))
}
}
class People extends Component {
render(){
const {count, increase, decrease} = this.props;
return (<div>
<div>計數:{this.props.count}次</div>
<button onClick={increase}>增加</button>
<button onClick={decrease}>減少</button>
</div>)
}
}
const NiuPeople = connect(mapStateToProps, mapDispatchToProps)(People);
這裡的實現原理和上面的相差不多, 主要是將action
和props
一起傳到元件裡.
const connect = (mapStateToProps, mapDispatchToProps) => (People) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor () {
super()
this.state = {
allProps: {}
}
}
componentWillMount () {
const { store } = this.context
this.setProps()
store.subscribe(() => this.setProps())
}
setProps () { // 做了一下完整性的判斷
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 <People {...this.state.allProps} />
}
}
return Connect
}
Provider
這是最後一個要說的問題, 講到這裡可能有一個疑問, 就是context是什麼時候設定的呢. 下面要說的就是這個問題.
Provider
就是react-redux
中的一個元件, Provider
做的事情也簡單, 它就是一個容器元件, 會把巢狀的內容原封不動作為自己的子元件渲染出來. 它還會把外界傳給它的 props.store
放到 context
, 這樣子元件 connect
的時候都可以獲取到. 見下面程式碼.
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>
)
}
}