CcbeanBlog CcbeanBlog
首页
  • 前端文章

    • JavaScript
    • HTML+CSS
    • Vue
    • React
  • 系列笔记

    • React使用学习
    • Vue2源码探究
  • Node文章

    • 基础
    • 问题
    • 框架
  • 系列笔记

    • 数据结构与算法
  • 构建工具文章

    • webpack
  • 系列笔记

    • Webpack5使用学习
  • MySQL
  • Linux
  • 网络
  • 小技巧
  • 杂记
  • 系列笔记

    • Protobuf Buffers
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Ccbean

靡不有初,鲜克有终
首页
  • 前端文章

    • JavaScript
    • HTML+CSS
    • Vue
    • React
  • 系列笔记

    • React使用学习
    • Vue2源码探究
  • Node文章

    • 基础
    • 问题
    • 框架
  • 系列笔记

    • 数据结构与算法
  • 构建工具文章

    • webpack
  • 系列笔记

    • Webpack5使用学习
  • MySQL
  • Linux
  • 网络
  • 小技巧
  • 杂记
  • 系列笔记

    • Protobuf Buffers
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Hello World
  • JSX语法
  • JSX语法的本质
  • 脚手架
  • 什么是组件化开发
  • 组件化开发
  • setState详解和React性能优化
  • 受控和非受控组件
  • 高阶组件
  • React的样式
  • React的过渡动画
  • ReactHooks的使用
  • Redux的使用
  • React-Redux
    • react结合redux
      • redux融入react代码
      • 自定义connect函数
      • store的context处理
    • react-redux使用
      • 2.1. react-redux的使用
      • react-redux的源码
  • Redux中间件和state管理
  • React-router
  • React打包发布
  • React SSR
  • React核心技术与开发实战
ccbean
2021-07-04
目录

React-Redux

# React学习(十四)—— React-Redux

# react结合redux

# redux融入react代码

目前redux在react中使用是最大的,所以我们需要将之前编写的redux代码,融入到react当中去。

这里我创建了两个组件:

  • Home组件:其中会展示当前的counter值,并且有一个+1和+5的按钮;
  • About组件:其中会展示当前的counter值,并且有一个-1和-5的按钮;

home.js代码实现

import React, { PureComponent } from 'react';
import store from '../store'; 
import { addAction, increAction } from '../store/actionCreators';

export default class Home extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      counter: store.getState().counter
    };
  }

  componentDidMount() {
    this.unsubscribe = store.subscribe(() => {
      this.setState({
        counter: store.getState().counter
      });
    });
  }  

  componentWillUnmount() {
    this.unsubscribe();
  }

  render() {
    return (
      <div>
        <h1>Home</h1>
        <h2>计数:{this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
        <button onClick={e => this.addNumber()}>+5</button>
      </div>
    )
  }

  increment() {
    store.dispatch(increAction())
  }

  addNumber() {
    store.dispatch(addAction(5))
  }
}

about.js代码实现

import React, { PureComponent } from 'react';
import store from '../store'; 
import { subAction, decreAction } from '../store/actionCreators';

export default class About extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      counter: store.getState().counter
    };
  }

  componentDidMount() {
    this.unsubscribe = store.subscribe(() => {
      this.setState({
        counter: store.getState().counter
      });
    });
  }  

  componentWillUnmount() {
    this.unsubscribe();
  }

  render() {
    return (
      <div>
        <h1>About</h1>
        <h2>计数:{this.state.counter}</h2>
        <button onClick={e => this.decrement()}>-1</button>
        <button onClick={e => this.subNumber()}>-5</button>
      </div>
    )
  }

  decrement() {
    store.dispatch(decreAction())
  }

  subNumber() {
    store.dispatch(subAction(5))
  }
}

上面的代码其实非常简单,核心代码主要是两个:

  • 在 componentDidMount 中定义数据的变化,当数据发生变化时重新设置 counter;
  • 在发生点击事件时,调用store的dispatch来派发对应的action;

# 自定义connect函数

上面的代码是否可以实现react组件和redux结合起来呢?

  • 当然是可以的,但是我们会发现每个使用的地方其实会有一些重复的代码:
  • 比如监听store数据改变的代码,都需要在 componentDidMount中完成;组件卸载要移除监听
  • 比如派发事件,我们都需要去先拿到 store, 在调用其 dispatch 等;

我们来定义一个connect函数:

  • 这个connect函数本身接受两个参数:

    • 参数一:里面存放 component 希望使用到的 State 属性;
    • 参数二:里面存放 component 希望使用到的 dispatch动作;
  • 这个connect函数有一个返回值,是一个高阶组件:

    • 在constructor中的state中保存一下我们需要获取的状态;
    • 在componentDidMount中订阅store中数据的变化,并且执行 setState操作;
    • 在componentWillUnmount中需要取消订阅;
    • 在render函数中返回传入的WrappedComponent,并且将所有的状态映射到其props中;
    • 这个高阶组件接受一个组件作为参数,返回一个class组件;
    • 在这个class组件中,我们进行如下操作
  • import React from 'react';
    import store from '../store';
    
    export default function connect(mapStateToProp, mapDispatchToProp) {
      return function enhanceHOC(WrapComponent) {
        return class extends React.PureComponent {
          constructor(props) {
            super(props);
    
            this.state = {
              storeState: mapStateToProp(store.getState())
            };
          }
    
          componentDidMount() {
            this.unsubscribe = store.subscribe(() => {
              this.setState({
                storeState: mapStateToProp(store.getState())
              });
            })
          }
    
          componentWillUnmount() {
            this.unsubscribe();
          }
    
          render() {
            return <WrapComponent
              {...this.props}
              {...mapStateToProp(store.getState())}
              {...mapDispatchToProp(store.dispatch)}
            />
          }
        }
      }
    }
    
  • 在home和props文件中,我们按照自己需要的state、dispatch来进行映射:

  • 比如home.js中进行如下修改:

    • mapStateToProps:用于将state映射到一个对象中,对象中包含我们需要的属性;

    • mapDispatchToProps:用于将dispatch映射到对象中,对象中包含在组件中可能操作的函数;

      • 当调用该函数时,本质上其实是调用dispatch(对应的Action);

修改后的home2.js

import React, { PureComponent } from 'react';
import connect from '../utils/connect';
import { addAction, increAction } from '../store/actionCreators';

class Home extends PureComponent {
  render() {
    return (
      <div>
        <h1>Home2</h1>
        <h2>计数:{this.props.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
        <button onClick={e => this.addNumber()}>+5</button>
      </div>
    )
  }

  increment() {
    this.props.increAction();
  }

  addNumber() {
    this.props.addAction(5);
  }
}

const mapStateToProp = state => {
  return {
    counter: state.counter
  };
}

const mapDispatchToProp = dispatch => {
  return {
    increAction() {
      dispatch(increAction());
    },
    addAction(num) {
      dispatch(addAction(num))
    }
  };
}


export default connect(mapStateToProp, mapDispatchToProp)(Home);

About2.js

import React, { PureComponent } from 'react';
import { subAction, decreAction } from '../store/actionCreators';
import connect from '../utils/connect';

class About extends PureComponent {
  render() {
    return (
      <div>
        <h1>About2</h1>
        <h2>计数:{this.props.counter}</h2>
        <button onClick={e => this.decrement()}>-1</button>
        <button onClick={e => this.subNumber()}>-5</button>
      </div>
    )
  }

  decrement() {
    this.props.decreAction();
  }

  subNumber() {
    this.props.subAction(5);
  }
}

export default connect(
  state => ({
    counter: state.counter
  }),
  dispatch => ({
    decreAction() {
      dispatch(decreAction());
    },
    subAction(num) {
      dispatch(subAction(num));
    }
  })
)(About);

有了connect函数,我们之后只需要关心从state和dispatch中映射自己需要的状态和行为即可。

# store的context处理

但是上面的connect函数有一个很大的缺陷:依赖导入的store

  • 如果我们将其封装成一个独立的库,需要依赖用于创建的store,我们应该如何去获取呢?
  • 难道让用户来修改我们的源码吗?不太现实;

正确的做法是我们提供一个Provider,Provider来自于我们创建的Context,让用户将store传入到value中即可;

import React from 'react';

const StoreContext = React.createContext();

export {
  StoreContext
};

修改connect函数中class组件部分的代码:

  • 注意下面我们将class组件的名称明确的定义出来,并且给它的contextType进行了赋值;
  • 在组件内部用到store的地方,统一使用this.context代替(注意:constructor中直接使用第二个参数即可)
import React from 'react';
import { StoreContext } from './context';

export default function connect(mapStateToProp, mapDispatchToProp) {
  return function enhanceHOC(WrapComponent) {
    class EnhanceComponent extends React.PureComponent {
      constructor(props, context) {
        super(props);

        this.state = {
          storeState: mapStateToProp(context.getState())
        };
      }

      componentDidMount() {
        this.unsubscribe = this.context.subscribe(() => {
          this.setState({
            storeState: mapStateToProp(this.context.getState())
          });
        })
      }

      componentWillUnmount() {
        this.unsubscribe();
      }

      render() {
        return <WrapComponent
          {...this.props}
          {...mapStateToProp(this.context.getState())}
          {...mapDispatchToProp(this.context.dispatch)}
        />
      }
    }

    EnhanceComponent.contextType = StoreContext;

    return EnhanceComponent;
  }
}

在入口的index.js中,使用Provider并且提供store即可:

import { StoreContext } from './utils/context';
import store from './store';

ReactDOM.render(
  <StoreContext.Provider value={store}>
    <App />
  </StoreContext.Provider>,
  document.getElementById('root')
);

这样,我们通过Context将Store进行共享,实现connect对所有业务代码没有任何的依赖。可以在任何地方进行使用。

# react-redux使用

# 2.1. react-redux的使用

开始之前需要强调一下,redux和react没有直接的关系,你完全可以在React, Angular, Ember, jQuery, or vanilla JavaScript中使用Redux。

尽管这样说,redux依然是和React或者Deku的库结合的更好,因为他们是通过state函数来描述界面的状态,Redux可以发射状态的更新,让他们作出相应。

虽然我们之前已经实现了connect、Provider这些帮助我们完成连接redux、react的辅助工具,但是实际上redux官方帮助我们提供了 react-redux 的库,可以直接在项目中使用,并且实现的逻辑会更加的严谨和高效。

安装react-redux:

yarn add react-redux

使用connect函数:

  • 将之前使用的connect函数,换成react-redux的connect函数;
import React, { PureComponent } from 'react';
import { connect } from "react-redux";

// import connect from '../utils/connect2';


export default connect(mapStateToProps, mapDispatchToProps)(Home);

使用Provider:

  • 将之前自己创建的Context的Provider,换成react-redux的Provider组件:
  • 注意:这里传入的是store属性,而不是value属性,其实本质上还是value,只是做了一层封装(待会儿可以在源码中查看);
import { Provider } from 'react-redux';

import store from './store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

# react-redux的源码

这里我简单带着大家看一下react-redux的源码。

首先,我们简单看一下Provider的源码:

  • 使用了一个useMemo来返回一个contextValue的对象;

    • 这里使用useMemo的原因是为了进行性能的优化;
    • 在依赖的store不改变的情况下,不会进行重新计算,返回一个新的对象;
  • 在下面的Context的Provider中就会将其赋值给value属性;

react-redux\src\components\Provider.js

function Provider({ store, context, children }) {
  const contextValue = useMemo(() => {
    const subscription = new Subscription(store)
    subscription.onStateChange = subscription.notifyNestedSubs
    return {
      store,
      subscription,
    }
  }, [store])

  const previousState = useMemo(() => store.getState(), [store])

  useIsomorphicLayoutEffect(() => {
    const { subscription } = contextValue
    subscription.trySubscribe()

    if (previousState !== store.getState()) {
      subscription.notifyNestedSubs()
    }
    return () => {
      subscription.tryUnsubscribe()
      subscription.onStateChange = null
    }
  }, [contextValue, previousState])
	
  // ReactReduxContext从另外一个文件导入
  const Context = context || ReactReduxContext

  return <Context.Provider value={contextValue}>{children}</Context.Provider>
}

react-redux\src\components\Context.js

import React from 'react'

export const ReactReduxContext = /*#__PURE__*/ React.createContext(null)

if (process.env.NODE_ENV !== 'production') {
  ReactReduxContext.displayName = 'ReactRedux'
}

export default ReactReduxContext

connect函数的依赖比较复杂:

调用createConnect来返回一个connect函数:

react-redux\src\connect\connect.js

export function createConnect({
  connectHOC = connectAdvanced,
  mapStateToPropsFactories = defaultMapStateToPropsFactories,
  mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
  mergePropsFactories = defaultMergePropsFactories,
  selectorFactory = defaultSelectorFactory,
} = {}) {
  return function connect(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    {
      pure = true,
      areStatesEqual = strictEqual,
      areOwnPropsEqual = shallowEqual,
      areStatePropsEqual = shallowEqual,
      areMergedPropsEqual = shallowEqual,
      ...extraOptions
    } = {}
  ) {
    const initMapStateToProps = match(
      mapStateToProps,
      mapStateToPropsFactories,
      'mapStateToProps'
    )
    const initMapDispatchToProps = match(
      mapDispatchToProps,
      mapDispatchToPropsFactories,
      'mapDispatchToProps'
    )
    const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
		
    // 最终调用connectHOC
    return connectHOC(selectorFactory, {
      // used in error messages
      methodName: 'connect',

      // used to compute Connect's displayName from the wrapped component's displayName.
      getDisplayName: (name) => `Connect(${name})`,

      // if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes
      shouldHandleStateChanges: Boolean(mapStateToProps),

      // passed through to selectorFactory
      initMapStateToProps,
      initMapDispatchToProps,
      initMergeProps,
      pure,
      areStatesEqual,
      areOwnPropsEqual,
      areStatePropsEqual,
      areMergedPropsEqual,

      // any extra options args can override defaults of connect or connectAdvanced
      ...extraOptions,
    })
  }
}

// connect是createConnect()的返回
export default /*#__PURE__*/ createConnect()

connect函数最终调用的是connectHOC:

  • connectHOC其实是connectAdvanced的函数;
  • connectAdvanced函数最终返回的是wrapWithConnect函数;

react-redux\src\components\connectAdvanced.js

export default function connectAdvanced(..., ...) {
  ...
  const Context = context

  return function wrapWithConnect(WrappedComponent) {
  	...
    // If we're in "pure" mode, ensure our wrapper component only re-renders when incoming props have changed.
    const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction

    Connect.WrappedComponent = WrappedComponent
    Connect.displayName = ConnectFunction.displayName = displayName

    if (forwardRef) {
      const forwarded = React.forwardRef(function forwardConnectRef(
        props,
        ref
      ) {
        return <Connect {...props} reactReduxForwardedRef={ref} />
      })

      forwarded.displayName = displayName
      forwarded.WrappedComponent = WrappedComponent
      return hoistStatics(forwarded, WrappedComponent)
    }

    return hoistStatics(Connect, WrappedComponent)
  }
}
编辑 (opens new window)
上次更新: 2021/11/10, 12:11:50
Redux的使用
Redux中间件和state管理

← Redux的使用 Redux中间件和state管理→

最近更新
01
阅读精通正则表达式总结
09-29
02
项目搭建规范的配置
07-15
03
Vite的使用
07-03
更多文章>
Theme by Vdoing | Copyright © 2018-2023 Ccbeango
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式