react redux 的 Connect 和 Provider的原理

这里以 App组件向Title组件通过传递为例

connect的作用是对子组件进行一个包装, 减少每个子组件都要写 contextTypes, 减少代码重复。

prop-types 这个插件是用来从父组件向子组件传递数据的 包含一个方法, 两个属性

父组件使用:

    static childContextTypes={
        数据名: propTypes.数据类型
    }
    
    getChildContext() {
        return {
            数据名: 数据值
        }
    }
复制代码

子组件使用:

static contextTypes = {
    数据名: propTypes.数据类型
}
复制代码

下面是app.js

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.js

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;

复制代码

使用方法

应用 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组件的源码

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();


作者:liyuanzhe-cn
链接:https://juejin.im/post/5cd50d735188256add69168e
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值