前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >手写一个React-Redux,玩转React的Context API

手写一个React-Redux,玩转React的Context API

作者头像
蒋鹏飞
发布于 2020-10-15 02:05:34
发布于 2020-10-15 02:05:34
3.7K00
代码可运行
举报
文章被收录于专栏:进击的大前端进击的大前端
运行总次数:0
代码可运行

上一篇文章我们手写了一个Redux,但是单纯的Redux只是一个状态机,是没有UI呈现的,所以一般我们使用的时候都会配合一个UI库,比如在React中使用Redux就会用到React-Redux这个库。这个库的作用是将Redux的状态机和React的UI呈现绑定在一起,当你dispatch action改变state的时候,会自动更新页面。本文还是从它的基本使用入手来自己写一个React-Redux,然后替换官方的NPM库,并保持功能一致。

本文全部代码已经上传GitHub,大家可以拿下来玩玩:github.com/dennis-jian…

基本用法

下面这个简单的例子是一个计数器,跑起来效果如下:

要实现这个功能,首先我们要在项目里面添加react-redux库,然后用它提供的Provider包裹整个ReactApp的根组件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import store from './store'
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    Provider>
  React.StrictMode>,
  document.getElementById('root')
);
复制代码

上面代码可以看到我们还给Provider提供了一个参数store,这个参数就是Redux的createStore生成的store,我们需要调一下这个方法,然后将返回的store传进去:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { createStore } from 'redux';
import reducer from './reducer';

let store = createStore(reducer);

export default store;

上面代码中createStore的参数是一个reducer,所以我们还要写个reducer:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const initState = {
  count: 0
};

function reducer(state = initState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return {...state, count: state.count + 1};
    case 'DECREMENT':
      return {...state, count: state.count - 1};
    case 'RESET':
      return {...state, count: 0};
    default:
      return state;
  }
}

export default reducer;
复制代码

这里的reduce会有一个初始state,里面的count0,同时他还能处理三个action,这三个action对应的是UI上的三个按钮,可以对state里面的计数进行加减和重置。到这里其实我们React-Redux的接入和Redux数据的组织其实已经完成了,后面如果要用Redux里面的数据的话,只需要用connectAPI将对应的state和方法连接到组件里面就行了,比如我们的计数器组件需要count这个状态和加一,减一,重置这三个action,我们用connect将它连接进去就是这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement, reset } from './actions';

function Counter(props) {
  const { 
    count,
    incrementHandler,
    decrementHandler,
    resetHandler
   } = props;

  return (
    <>
      <h3>Count: {count}h3>
      <button onClick={incrementHandler}>计数+1button>
      <button onClick={decrementHandler}>计数-1button>
      <button onClick={resetHandler}>重置button>
    
  );
}

const mapStateToProps = (state) => {
  return {
    count: state.count
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    incrementHandler: () => dispatch(increment()),
    decrementHandler: () => dispatch(decrement()),
    resetHandler: () => dispatch(reset()),
  }
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)
复制代码

上面代码可以看到connect是一个高阶函数,他的第一阶会接收mapStateToPropsmapDispatchToProps两个参数,这两个参数都是函数。mapStateToProps可以自定义需要将哪些state连接到当前组件,这些自定义的state可以在组件里面通过props拿到。mapDispatchToProps方法会传入dispatch函数,我们可以自定义一些方法,这些方法可以调用dispatchdispatch action,从而触发state的更新,这些自定义的方法也可以通过组件的props拿到,connect的第二阶接收的参数是一个组件,我们可以猜测这个函数的作用就是将前面自定义的state和方法注入到这个组件里面,同时要返回一个新的组件给外部调用,所以connect其实也是一个高阶组件。

到这里我们汇总来看下我们都用到了哪些API,这些API就是我们后面要手写的目标:

Provider: 用来包裹根组件的组件,作用是注入ReduxstorecreateStore: Redux用来创建store的核心方法,我们另一篇文章已经手写过了connect:用来将statedispatch注入给需要的组件,返回一个新组件,他其实是个高阶组件。

所以React-Redux核心其实就两个API,而且两个都是组件,作用还很类似,都是往组件里面注入参数,Provider是往根组件注入storeconnect是往需要的组件注入statedispatch

在手写之前我们先来思考下,为什么React-Redux要设计这两个API,假如没有这两个API,只用Redux可以吗?当然是可以的!其实我们用Redux的目的不就是希望用它将整个应用的状态都保存下来,每次操作只用dispatch action去更新状态,然后UI就自动更新了吗?那我从根组件开始,每一级都把store传下去不就行了吗?每个子组件需要读取状态的时候,直接用store.getState()就行了,更新状态的时候就store.dispatch,这样其实也能达到目的。但是,如果这样写,子组件如果嵌套层数很多,每一级都需要手动传入store,比较丑陋,开发也比较繁琐,而且如果某个新同学忘了传store,那后面就是一连串的错误了。所以最好有个东西能够将store全局的注入组件树,而不需要一层层作为props传递,这个东西就是Provider!而且如果每个组件都独立依赖Redux会破坏React数据流向,这个我们后面会讲到。

React的Context API

React其实提供了一个全局注入变量的API,这就是context api。假如我现在有一个需求是要给我们所有组件传一个文字颜色的配置,我们的颜色配置在最顶级的组件上,当这个颜色改变的时候,下面所有组件都要自动应用这个颜色。那我们可以使用context api注入这个配置:

先使用React.createContext创建一个context

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 我们使用一个单独的文件来调用createContext
// 因为这个返回值会被Provider和Consumer在不同的地方引用
import React from 'react';

const TestContext = React.createContext();

export default TestContext;

使用Context.Provider包裹根组件

创建好了context,如果我们要传递变量给某些组件,我们需要在他们的根组件上加上TestContext.Provider,然后将变量作为value参数传给TestContext.Provider:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import TestContext from './TestContext';

const setting = {
  color: '#d89151'
}

ReactDOM.render(
  <TestContext.Provider value={setting}>
  	<App />
  TestContext.Provider>,
  document.getElementById('root')
);
复制代码

使用Context.Consumer接收参数

上面我们使用Context.Provider将参数传递进去了,这样被Context.Provider包裹的所有子组件都可以拿到这个变量,只是拿这个变量的时候需要使用Context.Consumer包裹,比如我们前面的Counter组件就可以拿到这个颜色了,只需要将它返回的JSXContext.Consumer包裹一下就行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 注意要引入同一个Context
import TestContext from './TestContext';

// ... 中间省略n行代码 ...
// 返回的JSX用Context.Consumer包裹起来
// 注意Context.Consumer里面是一个方法,这个方法就可以访问到context参数
// 这里的context也就是前面Provider传进来的setting,我们可以拿到上面的color变量
return (
    <TestContext.Consumer>
      {context => 
        <>
          <h3 style={{color:context.color}}>Count: {count}h3>
          <button onClick={incrementHandler}>计数+1button>  
          <button onClick={decrementHandler}>计数-1button>  
          <button onClick={resetHandler}>重置button>
        
      }
    TestContext.Consumer>
  );
复制代码

上面代码我们通过context传递了一个全局配置,可以看到我们文字颜色已经变了:

使用useContext接收参数

除了上面的Context.Consumer可以用来接收context参数,新版React还有useContext这个hook可以接收context参数,使用起来更简单,比如上面代码可以这样写:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const context = useContext(TestContext);

return (
    <>
      <h3 style={{color:context.color}}>Count: {count}h3>
      <button onClick={incrementHandler}>计数+1button>  
      <button onClick={decrementHandler}>计数-1button>  
      <button onClick={resetHandler}>重置button>
    
);
复制代码

所以我们完全可以用context api来传递redux store,现在我们也可以猜测React-ReduxProvider其实就是包装了Context.Provider,而传递的参数就是redux store,而React-ReduxconnectHOC其实就是包装的Context.Consumer或者useContext。我们可以按照这个思路来自己实现下React-Redux了。

手写Provider

上面说了Provider用了context api,所以我们要先建一个context文件,导出需要用的context

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Context.js
import React from 'react';

const ReactReduxContext = React.createContext();

export default ReactReduxContext;

这个文件很简单,新建一个context再导出就行了,对应的源码看这里

然后将这个context应用到我们的Provider组件里面:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from 'react';
import ReactReduxContext from './Context';

function Provider(props) {
  const {store, children} = props;

  // 这是要传递的context
  const contextValue = { store };

  // 返回ReactReduxContext包裹的组件,传入contextValue
  // 里面的内容就直接是children,我们不动他
  return (
    <ReactReduxContext.Provider value={contextValue}>
      {children}
    ReactReduxContext.Provider>
  )
}
复制代码

Provider的组件代码也不难,直接将传进来的store放到context上,然后直接渲染children就行,对应的源码看这里

手写connect

基本功能

其实connect才是React-Redux中最难的部分,里面功能复杂,考虑的因素很多,想要把它搞明白我们需要一层一层的来看,首先我们实现一个只具有基本功能的connect

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React, { useContext } from 'react';
import ReactReduxContext from './Context';

// 第一层函数接收mapStateToProps和mapDispatchToProps
function connect(mapStateToProps, mapDispatchToProps) {
  // 第二层函数是个高阶组件,里面获取context
  // 然后执行mapStateToProps和mapDispatchToProps
  // 再将这个结果组合用户的参数作为最终参数渲染WrappedComponent
  // WrappedComponent就是我们使用connext包裹的自己的组件
  return function connectHOC(WrappedComponent) {

    function ConnectFunction(props) {
      // 复制一份props到wrapperProps
      const { ...wrapperProps } = props;

      // 获取context的值
      const context = useContext(ReactReduxContext);

      const { store } = context;  // 解构出store
      const state = store.getState();   // 拿到state

      // 执行mapStateToProps和mapDispatchToProps
      const stateProps = mapStateToProps(state);
      const dispatchProps = mapDispatchToProps(store.dispatch);

      // 组装最终的props
      const actualChildProps = Object.assign({}, stateProps, dispatchProps, wrapperProps);

      // 渲染WrappedComponent
      return <WrappedComponent {...actualChildProps}>WrappedComponent>
    }

    return ConnectFunction;
  }
}

export default connect;
复制代码

触发更新

用上面的Providerconnect替换官方的react-redux其实已经可以渲染出页面了,但是点击按钮还不会有反应,因为我们虽然通过dispatch改变了store中的state,但是这种改变并没有触发我们组件的更新。之前Redux那篇文章讲过,可以用store.subscribe来监听state的变化并执行回调,我们这里需要注册的回调是检查我们最终给WrappedComponentprops有没有变化,如果有变化就重新渲染ConnectFunction,所以这里我们需要解决两个问题:

  1. 当我们state变化的时候检查最终给到ConnectFunction的参数有没有变化
  2. 如果这个参数有变化,我们需要重新渲染ConnectFunction
检查参数变化

要检查参数的变化,我们需要知道上次渲染的参数和本地渲染的参数,然后拿过来比一下就知道了。为了知道上次渲染的参数,我们可以直接在ConnectFunction里面使用useRef将上次渲染的参数记录下来:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 记录上次渲染参数
const lastChildProps = useRef();
useLayoutEffect(() => {
  lastChildProps.current = actualChildProps;
}, []);
复制代码

注意lastChildProps.current是在第一次渲染结束后赋值,而且需要使用useLayoutEffect来保证渲染后立即同步执行。

因为我们检测参数变化是需要重新计算actualChildProps,计算的逻辑其实都是一样的,我们将这块计算逻辑抽出来,成为一个单独的方法childPropsSelector:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function childPropsSelector(store, wrapperProps) {
  const state = store.getState();   // 拿到state

  // 执行mapStateToProps和mapDispatchToProps
  const stateProps = mapStateToProps(state);
  const dispatchProps = mapDispatchToProps(store.dispatch);

  return Object.assign({}, stateProps, dispatchProps, wrapperProps);
}
复制代码

然后就是注册store的回调,在里面来检测参数是否变了,如果变了就强制更新当前组件,对比两个对象是否相等,React-Redux里面是采用的shallowEqual,也就是浅比较,也就是只对比一层,如果你mapStateToProps返回了好几层结构,比如这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
  stateA: {
    value: 1
  }
}

你去改了stateA.value是不会触发重新渲染的,React-Redux这样设计我想是出于性能考虑,如果是深比较,比如递归去比较,比较浪费性能,而且如果有循环引用还可能造成死循环。采用浅比较就需要用户遵循这种范式,不要传入多层结构,这点在官方文档中也有说明。我们这里直接抄一个它的浅比较:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// shallowEqual.js 
function is(x, y) {
  if (x === y) {
    return x !== 0 || y !== 0 || 1 / x === 1 / y
  } else {
    return x !== x && y !== y
  }
}

export default function shallowEqual(objA, objB) {
  if (is(objA, objB)) return true

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false
  }

  const keysA = Object.keys(objA)
  const keysB = Object.keys(objB)

  if (keysA.length !== keysB.length) return false

  for (let i = 0; i < keysA.length; i++) {
    if (
      !Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
      !is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false
    }
  }

  return true
}
复制代码

在回调里面检测参数变化:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 注册回调
store.subscribe(() => {
  const newChildProps = childPropsSelector(store, wrapperProps);
  // 如果参数变了,记录新的值到lastChildProps上
  // 并且强制更新当前组件
  if(!shallowEqual(newChildProps, lastChildProps.current)) {
    lastChildProps.current = newChildProps;

    // 需要一个API来强制更新当前组件
  }
});
复制代码
强制更新

要强制更新当前组件的方法不止一个,如果你是用的Class组件,你可以直接this.setState({}),老版的React-Redux就是这么干的。但是新版React-Redux用hook重写了,那我们可以用React提供的useReducer或者useStatehook,React-Redux源码用了useReducer,为了跟他保持一致,我也使用useReducer:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function storeStateUpdatesReducer(count) {
  return count + 1;
}

// ConnectFunction里面
function ConnectFunction(props) {
  // ... 前面省略n行代码 ... 
  
  // 使用useReducer触发强制更新
  const [
    ,
    forceComponentUpdateDispatch
  ] = useReducer(storeStateUpdatesReducer, 0);
  // 注册回调
  store.subscribe(() => {
    const newChildProps = childPropsSelector(store, wrapperProps);
    if(!shallowEqual(newChildProps, lastChildProps.current)) {
      lastChildProps.current = newChildProps;
      forceComponentUpdateDispatch();
    }
  });
  
  // ... 后面省略n行代码 ...
}
复制代码

connect这块代码主要对应的是源码中connectAdvanced这个类,基本原理和结构跟我们这个都是一样的,只是他写的更灵活,支持用户传入自定义的childPropsSelector和合并stateProps, dispatchProps, wrapperProps的方法。有兴趣的朋友可以去看看他的源码:github.com/reduxjs/rea…

到这里其实已经可以用我们自己的React-Redux替换官方的了,计数器的功能也是支持了。但是下面还想讲一下React-Redux是怎么保证组件的更新顺序的,因为源码中很多代码都是在处理这个。

保证组件更新顺序

前面我们的Counter组件使用connect连接了redux store,假如他下面还有个子组件也连接到了redux store,我们就要考虑他们的回调的执行顺序的问题了。我们知道React是单向数据流的,参数都是由父组件传给子组件的,现在引入了Redux,即使父组件和子组件都引用了同一个变量count,但是子组件完全可以不从父组件拿这个参数,而是直接从Redux拿,这样就打破了React本来的数据流向。在父->子这种单向数据流中,如果他们的一个公用变量变化了,肯定是父组件先更新,然后参数传给子组件再更新,但是在Redux里,数据变成了Redux -> 父,Redux -> 子完全可以根据Redux的数据进行独立更新,而不能完全保证父级先更新,子级再更新的流程。所以React-Redux花了不少功夫来手动保证这个更新顺序,React-Redux保证这个更新顺序的方案是在redux store外,再单独创建一个监听者类Subscription

  1. Subscription负责处理所有的state变化的回调
  2. 如果当前连接redux的组件是第一个连接redux的组件,也就是说他是连接redux的根组件,他的state回调直接注册到redux store;同时新建一个Subscription实例subscription通过context传递给子级。
  3. 如果当前连接redux的组件不是连接redux的根组件,也就是说他上面有组件已经注册到redux store了,那么他可以拿到上面通过context传下来的subscription,源码里面这个变量叫parentSub,那当前组件的更新回调就注册到parentSub上。同时再新建一个Subscription实例,替代context上的subscription,继续往下传,也就是说他的子组件的回调会注册到当前subscription上。
  4. state变化了,根组件注册到redux store上的回调会执行更新根组件,同时根组件需要手动执行子组件的回调,子组件回调执行会触发子组件更新,然后子组件再执行自己subscription上注册的回调,触发孙子组件更新,孙子组件再调用注册到自己subscription上的回调。。。这样就实现了从根组件开始,一层一层更新子组件的目的,保证了父->子这样的更新顺序。

Subscription

所以我们先新建一个Subscription类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
export default class Subscription {
  constructor(store, parentSub) {
    this.store = store
    this.parentSub = parentSub
    this.listeners = [];        // 源码listeners是用链表实现的,我这里简单处理,直接数组了

    this.handleChangeWrapper = this.handleChangeWrapper.bind(this)
  }

  // 子组件注册回调到Subscription上
  addNestedSub(listener) {
    this.listeners.push(listener)
  }

  // 执行子组件的回调
  notifyNestedSubs() {
    const length = this.listeners.length;
    for(let i = 0; i < length; i++) {
      const callback = this.listeners[i];
      callback();
    }
  }

  // 回调函数的包装
  handleChangeWrapper() {
    if (this.onStateChange) {
      this.onStateChange()
    }
  }

  // 注册回调的函数
  // 如果parentSub有值,就将回调注册到parentSub上
  // 如果parentSub没值,那当前组件就是根组件,回调注册到redux store上
  trySubscribe() {
      this.parentSub
        ? this.parentSub.addNestedSub(this.handleChangeWrapper)
        : this.store.subscribe(this.handleChangeWrapper)
  }
}
复制代码

Subscription对应的源码看这里

改造Provider

然后在我们前面自己实现的React-Redux里面,我们的根组件始终是Provider,所以Provider需要实例化一个Subscription并放到context上,而且每次state更新的时候需要手动调用子组件回调,代码改造如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React, { useMemo, useEffect } from 'react';
import ReactReduxContext from './Context';
import Subscription from './Subscription';

function Provider(props) {
  const {store, children} = props;

  // 这是要传递的context
  // 里面放入store和subscription实例
  const contextValue = useMemo(() => {
    const subscription = new Subscription(store)
    // 注册回调为通知子组件,这样就可以开始层级通知了
    subscription.onStateChange = subscription.notifyNestedSubs
    return {
      store,
      subscription
    }
  }, [store])

  // 拿到之前的state值
  const previousState = useMemo(() => store.getState(), [store])

  // 每次contextValue或者previousState变化的时候
  // 用notifyNestedSubs通知子组件
  useEffect(() => {
    const { subscription } = contextValue;
    subscription.trySubscribe()

    if (previousState !== store.getState()) {
      subscription.notifyNestedSubs()
    }
  }, [contextValue, previousState])

  // 返回ReactReduxContext包裹的组件,传入contextValue
  // 里面的内容就直接是children,我们不动他
  return (
    <ReactReduxContext.Provider value={contextValue}>
      {children}
    ReactReduxContext.Provider>
  )
}

export default Provider;
复制代码

改造connect

有了Subscription类,connect就不能直接注册到store了,而是应该注册到父级subscription上,更新的时候除了更新自己还要通知子组件更新。在渲染包裹的组件时,也不能直接渲染了,而是应该再次使用Context.Provider包裹下,传入修改过的contextValue,这个contextValue里面的subscription应该替换为自己的。改造后代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React, { useContext, useRef, useLayoutEffect, useReducer } from 'react';
import ReactReduxContext from './Context';
import shallowEqual from './shallowEqual';
import Subscription from './Subscription';

function storeStateUpdatesReducer(count) {
  return count + 1;
}

function connect(
  mapStateToProps = () => {}, 
  mapDispatchToProps = () => {}
  ) {
  function childPropsSelector(store, wrapperProps) {
    const state = store.getState();   // 拿到state

    // 执行mapStateToProps和mapDispatchToProps
    const stateProps = mapStateToProps(state);
    const dispatchProps = mapDispatchToProps(store.dispatch);

    return Object.assign({}, stateProps, dispatchProps, wrapperProps);
  }

  return function connectHOC(WrappedComponent) {
    function ConnectFunction(props) {
      const { ...wrapperProps } = props;

      const contextValue = useContext(ReactReduxContext);

      const { store, subscription: parentSub } = contextValue;  // 解构出store和parentSub
      
      const actualChildProps = childPropsSelector(store, wrapperProps);

      const lastChildProps = useRef();
      useLayoutEffect(() => {
        lastChildProps.current = actualChildProps;
      }, [actualChildProps]);

      const [
        ,
        forceComponentUpdateDispatch
      ] = useReducer(storeStateUpdatesReducer, 0)

      // 新建一个subscription实例
      const subscription = new Subscription(store, parentSub);

      // state回调抽出来成为一个方法
      const checkForUpdates = () => {
        const newChildProps = childPropsSelector(store, wrapperProps);
        // 如果参数变了,记录新的值到lastChildProps上
        // 并且强制更新当前组件
        if(!shallowEqual(newChildProps, lastChildProps.current)) {
          lastChildProps.current = newChildProps;

          // 需要一个API来强制更新当前组件
          forceComponentUpdateDispatch();

          // 然后通知子级更新
          subscription.notifyNestedSubs();
        }
      };

      // 使用subscription注册回调
      subscription.onStateChange = checkForUpdates;
      subscription.trySubscribe();

      // 修改传给子级的context
      // 将subscription替换为自己的
      const overriddenContextValue = {
        ...contextValue,
        subscription
      }

      // 渲染WrappedComponent
      // 再次使用ReactReduxContext包裹,传入修改过的context
      return (
        
          
        
      )
    }

    return ConnectFunction;
  }
}

export default connect;
复制代码

到这里我们的React-Redux就完成了,跑起来的效果跟官方的效果一样,完整代码已经上传GitHub了:github.com/dennis-jian…

下面我们再来总结下React-Redux的核心原理。

总结

  1. React-Redux是连接ReactRedux的库,同时使用了ReactRedux的API。
  2. React-Redux主要是使用了Reactcontext api来传递Reduxstore
  3. Provider的作用是接收Redux store并将它放到context上传递下去。
  4. connect的作用是从Redux store中选取需要的属性传递给包裹的组件。
  5. connect会自己判断是否需要更新,判断的依据是需要的state是否已经变化了。
  6. connect在判断是否变化的时候使用的是浅比较,也就是只比较一层,所以在mapStateToPropsmapDispatchToProps中不要反回多层嵌套的对象。
  7. 为了解决父组件和子组件各自独立依赖Redux,破坏了React父级->子级的更新流程,React-Redux使用Subscription类自己管理了一套通知流程。
  8. 只有连接到Redux最顶级的组件才会直接注册到Redux store,其他子组件都会注册到最近父组件的subscription实例上。
  9. 通知的时候从根组件开始依次通知自己的子组件,子组件接收到通知的时候,先更新自己再通知自己的子组件。

参考资料

官方文档:react-redux.js.org/

GitHub源码:github.com/reduxjs/rea…

文章的最后,感谢你花费宝贵的时间阅读本文,如果本文给了你一点点帮助或者启发,请不要吝啬你的赞和GitHub小星星,你的支持是作者持续创作的动力。

作者博文GitHub项目地址: github.com/dennis-jian…

作者掘金文章汇总:juejin.im/post/684490…

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
「源码解析」一文吃透react-redux源码(useMemo经典源码级案例)
使用过redux的同学都知道,redux作为react公共状态管理工具,配合react-redux可以很好的管理数据,派发更新,更新视图渲染的作用,那么对于 react-redux 是如何做到根据 state 的改变,而更新组件,促使视图渲染的呢,让我们一起来探讨一下,react-redux 源码的奥妙所在。
用户6835371
2021/06/01
2.4K1
「源码解析」一文吃透react-redux源码(useMemo经典源码级案例)
【重学React】动手实现一个react-redux
react-redux 是 redux 官方 React 绑定库。它帮助我们连接UI层和数据层。本文目的不是介绍 react-redux 的使用,而是要动手实现一个简易的 react-redux,希望能够对你有所帮助。
胡哥有话说
2019/10/24
3.2K0
React 进阶 - React Redux
应用初始化时候,只请求一次数据,然后通过状态管理把数据存起来,需要数据的组件只需要从状态管理中‘拿’就可以了。
Cellinlab
2023/05/17
9490
React 进阶 - React Redux
【干货】从零实现 react-redux
在 React 诞生之初,Facebook 宣传这是一个用于前端开发的界面库,仅仅是一个 View 层。前面我们也介绍过 React 的组件通信,在大型应用中,处理好 React 组件通信和状态管理就显得非常重要。为了解决这一问题,Facebook 最先提出了单向数据流的 Flux 架构,弥补了使用 React 开发大型网站的不足。
winty
2020/03/31
1.7K0
【干货】从零实现 react-redux
React-Redux 源码分析(三) -- connect
IMWeb前端团队
2018/01/08
9430
React-Redux 源码分析(三) -- connect
React进阶(6)-react-redux的使用
image.png 前言 您将在本文当中学习到 react-redux是什么,解决什么问题 UI组件以及容器组件 react-redux中两个重要的API,Provider以及connect mapS
itclanCoder
2020/10/25
2.2K0
Redux 入门教程(三):React-Redux 的用法
前两篇教程介绍了 Redux 的基本用法和异步操作,今天是最后一部分,介绍如何在 React 项目中使用 Redux。 为了方便使用,Redux 的作者封装了一个 React 专用的库 React-R
ruanyf
2018/04/12
1.7K0
Redux 入门教程(三):React-Redux 的用法
Redux with Hooks
React在16.8版本为我们正式带来了Hooks API。什么是Hooks?简而言之,就是对函数式组件的一些辅助,让我们不必写class形式的组件也能使用state和其他一些React特性。按照官网的介绍,Hooks带来的好处有很多,其中让我感受最深的主要有这几点:
腾讯IVWEB团队
2020/06/28
3.3K0
Redux/react-redux/redux中间件设计实现剖析
在一切开始之前,我们首先要回答一个问题:为什么我们需要redux,redux为我们解决了什么问题?只有回答了这个问题,我们才能把握redux的设计思路。
Nealyang
2020/04/23
2.3K0
Redux/react-redux/redux中间件设计实现剖析
react-redux
React-Redux是Redux的官方React绑定。 它允许您的React组件从Redux存储中读取数据,并将操作分派给存储以更新数据。 它由二个重要的部分组成,一个是<Provider />组件,另一个是connect()是react-redux提供的一个柯里化的函数, 用于连接redux
_kyle
2020/08/24
9940
React-Redux 对Todolist修改
在单独使用redux的时候 需要手动订阅store里面 感觉特别麻烦 不错的是react有一个组件可以帮我们解决这个问题, 那就是react-redux。
憧憬博客
2020/07/21
6300
React-Redux 对Todolist修改
使用Redux和React-redux在React中进行状态管理
首先,我们需要使用create-react-app命令行工具安装新的react应用。
前端修罗场
2022/07/29
2.9K0
使用Redux和React-redux在React中进行状态管理
React高级篇(一)从Flux到Redux,react-redux
React框架本身只应用于View,如果基于MVC模式开发,还需要Model和Control层,这样催生了Flux的产生,而Redux是基于Flux理念的一种解决方式。
娜姐
2021/01/14
2K1
React高级篇(一)从Flux到Redux,react-redux
mini-react-redux的实现
` react-redux的原理 提供一个Provider组件 负责吧外层的数据 传递给所有的子组件 connect方法(高阶组件) 负责将props和dispatch的方法 传递给子组件 废话不多
念念不忘
2019/03/29
5190
mini-react-redux的实现
react-redux 源码解析一: Provider做了什么,发布订阅模式实现?
使用过react的同学都知道,redux作为react公共状态管理容器,配合react-redux可以很好的派发更新,更新视图渲染的作用,那么对于react-redux是如何做到根据state的改变,而更新组件,促使视图渲染的呢,让我们一起来探讨一下,react-redux源码的奥妙所在。在正式分析之前我们不妨来想几个问题: 1 为什么要在root跟组件上使用react-redux的provider组件包裹 2 redux是使用store.subscribe()来发布订阅 ,那么react-redux组件更新是否也是用这个模式呢 3 provide 用什么方式存放当前的redux的 store, 又是怎么传递给每一个需要管理state的组件的 带着这些疑问我们不妨先看一下Provider究竟做了什么
用户6835371
2021/06/01
1.6K0
React-Redux-实现原理
React-Redux 是一个用于管理 React 应用状态的库,它背后有着强大的实现原理。本文简要介绍 React-Redux 的实现原理。
杨不易呀
2023/10/01
2710
React 入门学习(十五)-- React-Redux 基本使用
在前面我们学习了 Redux ,我们在写案例的时候,也发现了它存在着一些问题,例如组件无法状态无法公用,每一个状态组件都需要通过订阅来监视,状态更新会影响到全部组件更新,面对着这些问题,React 官方在 redux 基础上提出了 React-Redux 库
小丞同学
2021/10/08
9140
react-redux入门教程
最近这段时间在重新回顾上个暑假学的内容,很多内容因为用的比较少就给忘掉了,想着就谢谢博客帮助自己复习一下。
henu_Newxc03
2021/12/28
1.2K0
react-redux源码解读
react-redux作为胶水一样的东西,似乎没有深入了解的必要,但实际上,作为数据层(redux)与UI层(react)的连接处,其实现细节对整体性能有着决定性的影响。组件树胡乱update的成本,要比多跑几遍reducer树的成本高得多,所以有必要了解其实现细节
ayqy贾杰
2019/06/12
9850
redux架构基础
本文书接 从flux到redux , 是《深入浅出react和redux》为主的比较阅读笔记。
一粒小麦
2020/01/02
1.2K0
相关推荐
「源码解析」一文吃透react-redux源码(useMemo经典源码级案例)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文