react redux 的 Connect 和 Provider的原理

這裏以 App組件向Title組件經過傳遞爲例react

connect的做用是對子組件進行一個包裝, 減小每一個子組件都要寫 contextTypes, 減小代碼重複。數組

prop-types 這個插件是用來從父組件向子組件傳遞數據的 包含一個方法, 兩個屬性bash

父組件使用:閉包

static childContextTypes={
        數據名: propTypes.數據類型
    }
    
    getChildContext() {
        return {
            數據名: 數據值
        }
    }
複製代碼

子組件使用:app

static contextTypes = {
    數據名: propTypes.數據類型
}
複製代碼

下面是app.jsdom

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
import Title from './Tittle';

//父組件向子組件傳遞數據用的組件 
import propTypes from "prop-types";

//createStore 函數
var createStore = function (appReducer) {
    var state = null
    var listeners = [];
    var dispatch = function (action) {
        state = appReducer(state, action);
        listeners.forEach(e => e());
    }
    dispatch({})
    var subscribe = function (listener) {
        listeners.push(listener)
    }
    var getState = function () {
        return state;
    }
    return {
        dispatch,
        subscribe,
        getState
    }
}

// reducer  定義store裏面數據用的參數函數 
let appReducer = (state, action) => {
    if (!state) {
        return {
            title: "這個是標題",
        }
    }
    switch (action.type) {
        case "CHANGE_TITLE":
            return {
                ...state,
                title: action.newTitle
            }
        default:
            return state;
    }
}

//設置store
var store = createStore(appReducer)

// app組件
class App extends Component {

    // 數據容器 父組件用的: childContextTypes
    //          子組件用的 : contextTypes
    //          在contextTypes, childContextTypes 中定義數據類型
    // -->>>> console.log(Title.contextTypes.store == App.childContextTypes.store)
    // 返回值是true
    //--->>> childContextTypes 是用來傳遞的對象, 裏面的store 和Title 組件contextTypes的store 都是一個
    static childContextTypes = {
        store: propTypes.object,
    }
    
    // 用來賦值要傳遞的數據
    getChildContext() {
        return {
            store: store
        }
    }
    
    constructor() {
        super();
        //訂閱觸發渲染的事件 this.setState
        store.subscribe(() => {
            this.setState(store.getState)
        })
    }
    
    _chengeTitle(newTitle) {
        store.dispatch({
            type: "CHANGE_TITLE",
            newTitle: newTitle
        })
    }
    
    render() {
        const { title, content } = store.getState();

        console.info(store.getState())
        return (
            < div >
                <Title store={store}></Title>
                <button onClick={() => this._chengeTitle('您點擊了按鈕,標題已經被修改')}>點擊</button>
            </div >
        )

    }
}

ReactDOM.render(<App />, document.getElementById('root'));

serviceWorker.unregister();
複製代碼

下面是 Title.jside

import React, { Component } from 'react'
import propTypes from 'prop-types'

//---->>>> 簡易版的connect
//用來作子組件包裝的函數, 他負責以context方式收取, 再以props方式發送給子組件
let Connect = (WrapComponent) => {
  return (
    class Connect extends Component {
        // 以context形式收取父組件發過來的信息
      static contextTypes = {
        store: propTypes.object
      }
      render() {
        const { store } = this.context;
        return (
            //把store以props形式發給子組件
          <WrapComponent store={store}>
          </WrapComponent>
        )
      }
    }
  )
}

// 真正的子組件
class Title extends Component {
  _changeTitle(newTitle) {
    // 以props形式收取connect組件件發過來的信息
    const { store } = this.props;
    store.dispatch({
      type: "CHANGE_TITLE",
      title: newTitle
    })
  }
  render() {
   // 以props形式收取connect組件件發過來的信息
    const { store } = this.props;
    const { title } = store.getState();
    return (
      <h3 onClick={() => this._changeTitle('新的標題')}>{title} 點一下換個title</h3>

    )
  }
}

// 輸出包裝過的Title組件
export default Connect(Title)
複製代碼

如今這個組件Connect 已經實現了store的傳遞,可是是整個store的傳遞,接下來實現,經過閉包的形式實現選擇性的傳遞函數

封裝connect.js

import React, { Component } from 'react';
import propTypes from 'prop-types';

let Connect = (mapStateToProps, mapDispatchToProps) => {

    return (WrapComponent) => {

        return (

            class Connect extends Component {

                static contextTypes = {
                    store: propTypes.object
                }

                constructor() {
                    super();
                    
                    //設置一個組件本身的state, 稍後加入子組件選擇好的props傳給子組件
                    this.state = {
                        allProps: {}
                    }
                }

                componentWillMount() {
                    //渲染前執行, 並觸發更新事件
                    const { store } = this.context;
                    //渲染前進行第一次的初始化執行,保證被他包裹的組件能夠收到props
                    this.updataProps();
                    //將更新props也存到store 的listeners事件數組中
                    // 在setState更新以後會, 也會更新本身的props, 並從新渲染本身的組件,保證被他包裹的組件能夠收到更新事後的props
                    store.subscribe(() => this.updataProps())
                }

                updataProps() {
                    //updateProps在執行後是更新以後的組件渲染
                
                    let { store } = this.context
                    //選擇哪些state經過props傳遞出去

                    // mapDispatchToProps(store.getState(), this.props) 傳入全部的store裏面的state 和在父級傳過來的props。
                    // 他要return所選擇的 store和props 裏面的內容
                    let needStates = mapStateToProps ? mapStateToProps(store.getState(), this.props) : {};

                    // mapDispatchToProps(store.dispatch, this.state)  和在父級傳過來的props。
                    // 他要return所選擇的 store和props 裏面的內容
                    let needDispatch = mapDispatchToProps ? mapDispatchToProps(store.dispatch, this.props) : {};

                    //結構
                    this.setState({
                        allProps: {
                            ...needStates,
                            ...needDispatch,
                            ...this.props
                        }
                    })
                }

                render() {
                    return (
                        <WrapComponent {...this.state.allProps}>
                        </WrapComponent>
                    )
                }
            }

        )
    }
}

export default Connect;

複製代碼

使用方法ui

應用 connect.js

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';

import Connect from './connect'

//父組件向子組件傳遞數據用的組件 
import propTypes from "prop-types";
//引入createStore
import createStore from './createStore'

// reducer  定義store裏面數據用的參數函數 
let appReducer = (state, action) => {
    if (!state) {
        return {
            title: "這個是標題",
        }
    }
    switch (action.type) {
        case "CHANGE_TITLE":
            return {
                ...state,
                title: action.newTitle
            }
        default:
            return state;
    }
}

//設置store
var store = createStore(appReducer)




class Title extends Component {
  render() {
    console.log(this.props)
    const { title, changeTitle } = this.props;
    return (
      <h3 onClick={() => changeTitle('新的標題')}>{title} 點一下換個title</h3>
    )
  }
}

// 這兩個函數都要有返回值, props 目前仍是空對象,這取決於用戶時候在真正的父組件是喲個props的方法傳遞到Connect組件
function mapStateToProps(state, props) {
  console.log(state)
  return {
    title: state.title
  }
}

function mapDispatchToProps(dispatch, props) {
  return {
    changeTitle: (newTitle) => {
      dispatch({
        type: "CHANGE_TITLE",
        newTitle: newTitle
      })
    }
  }
}

///// --->>>> connect的使用方法
export default Connect(mapStateToProps, mapDispatchToProps)(Title)



// app組件
class App extends Component {

    static childContextTypes = {
        store: propTypes.object,
    }
    
    getChildContext() {
        return {
            store: store
        }
    }
    
    constructor() {
        super();
        //訂閱觸發渲染的事件 this.setState
        store.subscribe(() => {
            this.setState(store.getState)
        })
    }
    
    _chengeTitle(newTitle) {
        store.dispatch({
            type: "CHANGE_TITLE",
            newTitle: newTitle
        })
    }
    
    render() {
        const { title, content } = store.getState();

        console.info(store.getState())
        return (
            < div >
                <Title store={store}></Title>
                <button onClick={() => this._chengeTitle('您點擊了按鈕,標題已經被修改')}>點擊</button>
            </div >
        )

    }
}

ReactDOM.render(<App />, document.getElementById('root'));

serviceWorker.unregister();
複製代碼

如今已經將 組件裏面的store傳過來的內容都轉移到了mapStateToProps, mapDispatchToProps函數中, 組件變得很是的乾淨, 下面用Provider組件,對App組件進行處理, 讓App組件也變得乾淨,整潔。

Provider組件的源碼this

class Provider extends Component {
    //用於檢測本身子組件和props
    static propTypes = {
        store: propTypes.object,
        children: propTypes.any
    }
    
    //發送store給子組件們
    static childContextTypes = {
        store: propTypes.object,
    }
    
    //
    getChildContext() {
        return {
            store: store
        }
    }
    
    //渲染本身和子組件
    render() {
        return (
            <div>{this.props.children}</div>
        )
    }
}

複製代碼
class App extends Component {
    render() {
        const { chengeTitle } = this.props;
        console.log(chengeTitle)
        return (
            < div >
                <h1>{title}</h1>
                <button onClick={() => chengeTitle('您點擊了按鈕,標題已經被修改')}>點擊</button>
            </div >
        )

    }
}


function mapStateToProps(state, props) {
    return {
        state: state.title
    }
}


function mapDispatchToProps(dispatch, props) {
    return {
        chengeTitle: (newTitle) => {
            dispatch({
                type: "CHANGE_TITLE",
                newTitle: newTitle
            })
        }
    }
}

App = Connect(mapStateToProps, mapDispatchToProps)(App)

//使用Provider 組件
ReactDOM.render(
    <Provider store={store}> 
        <App />
    </Provider>

    , document.getElementById('root'));

serviceWorker.unregister();
複製代碼
相關文章
相關標籤/搜索