React-Redux的简单实现

React-Redux简单实现

React 试图在视图层禁止异步和直接操作 DOM 来解决这个问题。美中不足的是,React 依旧把处理 state 中数据的问题留给了我们自己。而 redux 就可以来帮我管理这些。

React-Redux 将所有组件分成两大类:UI 组件(presentational component)和 容器组件(container component)

  • UI组件
    • 只负责 UI 的呈现,不带有任何业务逻辑的组件;
    • 没有状态(不使用 this.state这个变量,即不会进行状态改变的组件);
    • 所有数据都由参数(this.props)提供(单纯的展示组件);
    • 不使用任何 Redux 的 API。
  • 容器组件
    • 负责管理数据和业务逻辑,不负责 UI 的呈现;

    • 带有内部状态(内部有this.state,即可以改变状态);

    • 使用 Redux 的 API;

    • UI 组件负责 UI 的呈现,容器组件负责管理数据逻辑

      很多时候组件不是单单独立存在的,数据和其他公有的"ID"一些异步请求等等的一些属性状态,
      既有 UI 又有业务逻辑,那怎么办?回答是,将它拆分成下面的结构:外面是一个容器组件,里面 包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。

React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。

首先对Redux原先的实现方式进行分析

react-redux是一个轻量级的封装库,核心方法只有两个:

  1. Provider
  2. connect
    这是我自己写的demo的文件目录,可以参照去写,有助于对React-Redux的深度理解和跨级传参的应用常见更加得心应手。
    React-Redux的简单实现_第1张图片
    下面我们来逐个分析其作用
  1. Provider

Provider模块的功能并不复杂,主要分为以下两点:

  • 原应用组件上包裹一层,使原来整个应用成为Provider的子组件
  • 接收Redux的store作为props,通过context对象传递给子孙组件上的connect
// 从React-Redux的调用方法和触发机制着手 
// React-Redux原本的写法如下
//1.通过Provider组件将store导入到App组件及内部所有子组件上.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import {
      BrowserRouter } from 'react-router-dom'
import './components/App.css'
//1.通过Provider组件将store导入到App组件及内部所有子组件上.
import {
      Provider } from 'react-redux';
//此时的store就相当于我们所用到的状态集合
import {
     store} from './store/store'

ReactDOM.render(<Provider store={
     store}>
                    <BrowserRouter>  //这里是H5路由(history API)这里不做过多解释了
                        <App /> //被包裹的组件
                    </BrowserRouter>
                </Provider> , document.getElementById('root'));

调用如下:

//导入connect方法,用于从redux的store中导入数据
import {
     connect} from 'react-redux'
class Home extends Component {
      
	render(){
     
		//此处省略触发action和state数据展示部分
	}
}
export default connect(
    (state) => {
     //负责注入数据
        return {
     
            list: state //list会注入到Home组件的this.props上
        }
    },
    (dispatch) => {
     //负责注入方法,collect,cancel_collect会出现在this.props上
        return {
     
            collect(item) {
     //负责 触发 收藏action
                dispatch( collect_action(item) )
            },
            cancel_collect(id) {
      //负责 触发 取消收藏action
                dispatch( cancel_collect_action(id) )
            }
        }
    }
)(Home)

大家有没有看到这里的Provider很像上一节提到的组件跨级传参里面的Context.Proveder呢!
对的你没有看错,其实React-Redux也是利用了React官方提供的组件跨级传参加上柯里化高阶组件实现的,

简易分析Redux,并结合Context跨组件传值进行简单的实现

具体怎么实现的,我们来具体分析一下:

  1. 首先我们来分析Provider
    看了上一节的盆友,肯定知道组件跨级传参分为"数据入口Provider"和"展示出口Consumer",
    那么React-Redux中的Provider我们就可以当作是我们的"数据入口Provider"

我们进行一个Provier的组件封装并按照Redux原先的方式进行调用

下面是具体代码实现:
修改之前的Provider之后会有修改,请耐心看完做后的修改过程的注释,希望你能深刻的理解

import React, {
      Component } from 'react'
import Context from "../utils/context"
export default class Provider extends Component {
     
    constructor(props){
     
        super(props);
        this.state = {
     
            ...this.props.store
        }
    }
    render() {
     
        let {
     children} = this.props
        return (
            <div>
                <Context.Provider value={
     {
     state:{
     ...this.state}}}>
                //这里的value不用我多说,上一节的组件跨级传值说过了
                //这里的children也就是传统的组件内部传值用this.props.children接收传过来的东西
                //(可以包括组件)相当于 this.props.children就是这里的App组件展示
                    {
     children}
                </Context.Provider>
            </div>
        )
    }
}

在src文件夹下的index中调用(主入口),,也就是把Provider组件当作最终的根级组件,把之前的跟组件给包裹起来。

这里是引用我们自己封装的Provider数据入口

import React from 'react';
import ReactDOM from 'react-dom';
import App from './views/NewHoc/index';
import Provider from "./components/provider"
import store from "./store/data"
import "./index.css"
ReactDOM.render(
  <Provider store = {
     store}>//这里的store就相当与要穿过来的数据数据模拟在下面的代码中,自己也可以尝试模拟更加复杂的是数据
    <App />   //上面谈到了App组件的传值和Provider做为最大的根组件进行包裹
  </Provider>
  ,
  document.getElementById('root')
);

在这里我只是做了一个简单的模拟redux返回会来的数据
store.js

const Reducers = {
     
    sonReducer : {
     
        name:"儿子",
        height:1
    },
    childOneReducer : {
     
        title:"孙子兵法1",
        num:2
    },
    childTwoReducer : {
     
        type:"孙子兵法2",
        left:3
    },
}
export default Reducers

这里我们写完了Provider的入口那么接下来就得有出口,那么出口又该怎么写,怎么理解呢?
出口还是结合我们的Consumer进行接收

封装context自然不必多说,直接撸代码

import React from "react"

const Context = React.createContext();
export default Context

views/NewHoc/index,js

import React, {
      Component } from 'react'
import Son from "./Son"
import Context from "./context"

export default class Index extends Component {
     
    render() {
     
        let {
     
			sonReducer:
			{
     name}
		} = this.props
        return (
            <div>
                <p className="parent">
                    我是父组件
                </p>
                <div>
                    <Context.Consumer>
                        {
     
                            ({
     state})=>{
     
                                return (
                                    <div>{
     num}</div>
                                )
                            }
                        }
                    </Context.Consumer>
                </div>
                <Son />
            </div>
        )
    }
}

views/NewHoc/Son,js

import React, {
      Component } from 'react'
import ChildOne from './ChildOne'
import ChildTwo from './ChildTwo'

export default class Son extends Component {
     
    render() {
     
        return (
            <div>
                <p className="son">
                我是子组件
                </p>
                <ChildOne />
                <ChildTwo/>
            </div>
        )
    }
}

views/NewHoc/ChildOne,js

import React, {
      Component } from 'react'
import Context from "./context"

export default class ChildOne extends Component {
     
    state={
     
        count:999
    }
    render() {
     
        let {
     
			childOneReducer:{
     title,num}
		} = this.props
        return (
            <div>
                <p className="childOne">我是孙组件1</p>
                <div>
                    {
     title}
                </div>
                <Context.Consumer>
                        {
     
                            ({
     state})=>{
     
                                return (
                                   <div>{
     num}</div>
               					   <button onClick={
     ()=>{
     this.props.handleNum(this.state.count)}}>点我</button>
                                )
                            }
                        }
                </Context.Consumer>
                
            </div>
        )
    }
}

ChildTwo参照ChildOne.
大家会看到在NewHoc/index.js和ChildOne.js中的Context.Consumer出口被复用了多次
那我们是不是可以把他封装起来做为统一的出口呢?像是统一的入口Provider一样。
这里就引入了我们的connect组件也可以看作是原Redux中的调用,
我们是不是可以先把Connect的调用方式分析清楚,它其实是利用了一个叫柯里化高阶组件的东西来实现的。
加入我们这样写ChildOne,ChildTwo和Index组件

connect的抽离封装

import React, {
      Component } from 'react'
import connect from "../../utils/connect" //这里是我们封装的connect

class ChildOne extends Component {
     
    state={
     
        count:999
    }
    render() {
     
        let {
     
                title,
                num
        } = this.props
        return (
            <div>
                <p className="childOne">我是孙组件1</p>
                <div>
                    {
     title}
                </div>
                <div>{
     num}</div>
                <button onClick={
     ()=>{
     this.props.handleNum(this.state.count)}}>点我</button>
            </div>
        )
    }
}
export default connect(
    (store)=>{
      //数据注入
        return {
     
            ...store.childOneReducer
        }
    },
    (dispatch)=>{
     
        return {
     
            handleNum(count){
     //点击事件,触发connect中传过来的dispatch也就是changeState修改状态的函数setState
                dispatch({
     
                    type:"childOneReducer/num",  //模仿redux的action的触发方式判断type类型
                    data:count  //要进行修改的新数据
                })
            }
        }
    }
)(ChildOne)  //柯里化高阶组件的调用方式

那么我们看看connect组件做了一个什么操作呢?

// 柯里化高阶组件connect的具体内容
import React, {
     Component} from "react"
import Context from "./context"   //1、我们首先把Context组件引进来用作出口的调用,毋庸置疑

const Connect = (cbk_1, cbk_2) => {
      //cbk_1和cbk_2作为一个数据的注入的函数,和数据改变的函数传入
    return (Com) =>{
     //这里的Com也就是传入的组件  也就是上面代码中的(ChildOne)
        return class extends Component{
       //抛出的进行数据注入和具有课改变数据功能的匿名组件
            render(){
     
                return (
                    <Context.Consumer>
                        {
     
                            ({
     state, changeState})=>{
     
                                let res_1 = cbk_1(state)  //数据注入,数据注入,数据注入
                                let res_2 = cbk_2(changeState)  //dispatch  事件触发,dispatch  事件触发,dispatch  事件触发
                                return (
                                    <Com {
     ...res_1} {
     ...res_2} {
     ...this.props} />  //抛出带有内容的组件
                                )
                            }
                        }
                    </Context.Consumer>
                )
            }
        }
    }
}
export default Connect   //最终抛出Connect

上述代码中我强调了三次dispatch 事件触发
那么我们没有写事件触发啊那到底具体在哪儿写呢?又是怎么触发的呢,
我们在之前说了要调用和触发都是在Provider中进行的
那么我们把之前封装的Provider进行一下修改就可以触发我们在Redux中看到的dispatch 的触发了
修改后的Provider

dispatch事件触发实现

import React, {
      Component } from 'react'
import Context from "../utils/context"
export default class Provider extends Component {
     
    constructor(props){
     
        super(props);
        this.state = {
     
            ...this.props.store
        }
    }
    changeState = ({
     type, data}) => {
       //和之前相比,加了一个更改state的方法
        let [reducer, target] = type.split("/")  //这里是进行了数据的切割childOneReducer/num
        this.setState({
       //调用setState进行状态的修改(React中唯一个可以修改state的那就是setState())
            [reducer]:{
     
                ...this.state[reducer],  //ES6语法不必多说
                [target]: data
            }
        })
    }
    render() {
     
        let {
     children} = this.props
        return (
            <div>
                <Context.Provider value={
     {
     state:{
     ...this.state}, changeState:this.changeState}}>
                //changeState:this.changeState 这里的changeState就是给我们的cbk_2(changeState)传入的参数,
                //再利用了柯里化组件抛出的匿名函数,再原组件中进行的触发
                    {
     children}
                </Context.Provider>
            </div>
        )
    }
}

总结:

首先,我们必须清楚的认识到react本身是一个专注于view层的框架。
其次,我们必须要明白Redux本身和React并没有之间的关联,它是一个通用Javscript App模块,用做App State的管理。要在React的项目中使用Redux,比较好的方式是借助react-redux这个库来做连接,这里的意思是,并不是没有react-redux,这两个库就不弄一起用了,而是说react-redux提供了一些封装,一种更科学的代码组织方式,让我们更舒服地在React的代码中使用Redux。我们可以通过npm将react-redux直接安装到我们的项目配置里面,在项目里直接就可以用了。
  • provider:
+ Provider这个模块是作为整个App的容器,在你原有的App Container的基础上再包上一层,它的工作简单概括就是接受Redux的store作为props,并将其声明为context的属性之一,将store放在context里,是为了给下面的connect用的。

需要注意的一点就是Provider里面可以包括页面渲染的任何内容,不是必须Router(路由)。

  • connect:
    先考虑Redux是怎么运作的:首先store中维护了一个state,我们dispatch一个action
    映射到我们的React应用中,store中维护的state就是我们的app state,一个React组件作为View层,## 标题做两件事:render和响应用户操作。于是connect就是将store中的必要数据作为props传递给React组件来render,并包装action creator用于在响应用户操作时dispatch一个action。

在这里提一下react性能优化方面的问题: react有一个生命周期函数 shouldComponentUpdate,来控制react的更新 假如你有个带有多个对话的消息应用,如果只有一个对话发生改变,如果我们在 ChatThread 组件执行 shouldComponentUpdate,React 可以跳过其他对话的重新渲染步骤
合理的组件拆分,提高代码1、可组合性 2、可重用性 3、可维护性 4、可测试性
还有一个就是16.8以后的出现的Hook组件也是以后开发的一个放向,
新增的React的几生命周期大家可以看看研究研究

  • getDerivedStateFromProps
  • getSnapshotBeforeUpdate
  • ComponentDidCatch

这就是我写的React-Redux的实现原理。也和不足,还请评论指教,您的评论就是对我最大的帮助,在这里我也只是把自己学过的写在了这里,希望能帮助更多的朋友和小伙伴们。。。加油

Encourage yourself: maybe today is not easy, tomorrow will be more difficult, but the day after tomorrow will be very beautiful.

在这里插入图片描述

你可能感兴趣的:(笔记,react)