【React.js】ReactRedux - 使用 & 基本实现

为什么要有 ReactRedux ?

  • Redux 是一个数据状态的管理仓库,它可以使数据按照预期进行可预测的变化。
  • Redux 只是管理数据状态 State,它不管理视图 View !
  • 在 React 中直接使用 Redux 是不能引起视图的 响应式更新 (这里的响应式指代数据驱动视图更新,不包括数据的劫持和代理),这是因为 Redux 的数据变化无法直接驱动 React 视图的变化。如果直接使用,需要手动订阅这样的更新:
jsx 复制代码
import { useState, useEffect } from 'react';
import store from '@/store';

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const { counter } = store.getState();
    setCount(counter.count);

    const unsubscribe = store.subscribe(() => {
      const { counter } = store.getState();
      setCount(counter.count);
    });

    return () => {
      unsubscribe();
    };
  }, []);

  function handleCountChange() {
    store.dispatch('ADD_COUNT');
  }

  return (
    <div className="counter">
      <h1>{count}</h1>

      <button onClick={ handleCountChange }></button>
    </div>
  );
}
  • 每次更新 store 的某一个值,都需要在 useEffect 里面设置这样的订阅更新,十分的麻烦。

ReactRedux 解决了什么问题?

  • ReactRedux 在 Redux 的基础上面提供了一套和 React 核心相关的、标准的数据状态 State 驱动视图 View 及时更新的解决方案,让用户直接使用对应的 API 方法(e.g connectuseSelectoruseDispatch),无须手动订阅更新。
  • 使用 ReactRedux 把整个 store 实例提供给了 React 的 Context 全局上下文,更方便调试。
  • 使用 ReactRedux 更加符合 React 的编程范式 (函数式编程、hook 的调用),可读性和可维护性更强。

ReactRedux 的基本用法:

在使用 ReactRedux 之前,都需要把 store 全局提供给 App:

jsx 复制代码
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';

import App from './App';
import store from './store';

const WrapApp = () => (
  <Provider store={store}>
    <App />
  </Provider>
);

createRoot(document.getElementById)
  .render(
    // <App />
    <WrapApp />
  );

根据编程范式的不同,ReactRedux 有以下两种不同的用法:

  1. connect 高阶组件包裹
  2. ReactRedux 提供的 hook

1. connect 高阶组件包裹

connect 函数是一个柯里化式调用的高阶组件

  • connect在第一次调用的时候接收两个参数:mapStateToProps 函数和 mapDispatchToProps函数
  • connect在第二次调用的时候接收需要被提供 Redux state 和 Redux dispatch 的函数
jsx 复制代码
import { connect } from 'react-redux';

function Counter({ count, addCount, initCount }) {
  return (
    <div>
      <h1>{count}</h1>

      <button onClick={() => addCount()}>ADD COUNT</button>
      <button onClick={() => initCount()}>INIT COUNT</button>
    </div>
  );
}

export default connect(
  function mapStateToProps({ counter }) {
    return {
      count: counter.count
    };
  },
  function mapDispatchToProps(dispatch) {
    return {
      addCount() {
        dispatch({
          type: 'ADD_COUNT'
        });
      },
      initCount() {
        dispatch({
          type: 'INIT_COUNT'
        });
      }
    };
  }
)(Counter);

当然,mapDispatchToProps 也可以直接配置一个 action 集合的对象

jsx 复制代码
import { connect } from 'react-redux';

function Counter({ count, addCount, initCount }) {
  return (
    <div>
      <h1>{count}</h1>

      <button onClick={() => addCount()}>ADD COUNT</button>
      <button onClick={() => initCount()}>INIT COUNT</button>
    </div>
  );
}

export default connect(
  function mapStateToProps({ counter }) {
    return {
      count: counter.count
    };
  },
  {
    addCount: {
      type: 'ADD_COUNT'
    },
    initCount: {
      type: 'INIT_COUNT'
    },
  }
)(Counter);

2. ReactRedux 提供的 hook

useSelector()

概述:

useSelector()是新版 ReactRedux 提出的获取 Redux State 的 hook 。

它接收一个携带 state 的回调函数,返回回调函数执行结果的 React 状态值。

基本用法:

使用 useSelector()生成一个 Redux State 的返回值

js 复制代码
const reactState = useSelector((state) => state.xxx);

useDispatch()

概述:

useDispatch()是一个新版 ReactRedux 提出的获取 Redux Dispatch 的 hook 。

调用 useDispatch() 可以生成一个操作 Redux State 的 dispatch 方法,并且此 dispatch 方法可以及时更新视图。

基本用法:

使用 useDispatch() 生成一个操作 Redux State 和 视图更新的方法

js 复制代码
const dispatch = useDispatch();

使用 useSelector()useDispatch()实现上面的功能:

jsx 复制代码
import { useSelector, useDispatch } from 'react-redux';

function Counter() {
	// { count, addCount, initCount }
  const count = useSelector(state => state.counter.count);
  const dispatch = useDispatch();

  const addCount = () => {
    dispatch({
      type: 'ADD_COUNT'
    });
  }

  const initCount = () => {
    dispatch({
      type: 'INIT_COUNT'
    });
  }
  
  return (
    <div>
      <h1>{count}</h1>

      <button onClick={() => addCount()}>ADD COUNT</button>
      <button onClick={() => initCount()}>INIT COUNT</button>
    </div>
  );
}

实现一下 ReactRedux 的基本功能:

注意

实现 ReactRedux 需要先有 reactredux 依赖 !

概述:

本文和源码不同,直接使用函数组件的思路来实现:

目录结构:

perl 复制代码
my-react-redux
	|- adaptors.js              # 适配器函数
	|- connect.jsx              # connect 高阶组件
	|- context.js               # my-react-redux 需要使用到的 context
	|- hooks.jsx                # 新版 my-react-redux 需要实现的 hook
	|- index.js                 # my-react-redux 导出的 API
	|- Provider.jsx             # redux store 提供器

实现步骤:

按照程序从外向里设计,具体的实现步骤如下:

  1. 从 index.js 中导出需要给到外界使用的 API:
js 复制代码
export { default as Provider } from './Provider';
export { default as connect } from './connect';
export * from './hooks';
  1. 在 context.js 创建一个 StoreContext, 表示提供 Redux Store 的 React 上下文对象:
js 复制代码
import { createContext } from 'react';

/** @type {React.Context<any>} redux store context */
export const StoreContext = createContext({});
  1. Provider.jsx 中实现一下 Provider 组件。这个组件比较简单,相当于把 Redux Store 提供给了全局:
jsx 复制代码
import { StoreContext } from './context';

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

  return (
    <StoreContext.Provider value={store}>
      {children}
    </StoreContext.Provider>
  );
}

export default Provider;
  1. 实现 connect 高阶组件,这个高阶组件比较麻烦:

    1. 接收 mapStateToPropsmapDispatchToProps,生成一个 createWrapComponent高阶组件
    2. mapDispatchToProps 可以是一个函数,也可以是一个 actions 的对象
    3. createWrapComponent返回的是一个新的组件,这个组件里面需要收集所有的 props 传递给需要被提供 Redux 状态和方法的被包裹组件渲染

    于是,我们采取这样的操作方案:

js 复制代码
/* 在 `adaptors.js`中写一个 `getMapDispatchToProps`方法,处理上述的第二点情况: */
export function getMapDispatchToProps({
  mapDispatchToProps,
  dispatch
}) {
  if (typeof mapDispatchToProps === 'function') {
    // ActionCreatorFunction
    return mapDispatchToProps(dispatch) || {};
  }
  if (typeof mapDispatchToProps === 'object' && mapDispatchToProps !== null) {
    // ActionNamespaceObject
    return Object.keys(mapDispatchToProps).reduce((res, actionName) => {
      res[actionName] = function (...args) {
        dispatch(mapDispatchToProps[actionName].apply(null, args));
      }
      return res;
    }, {});
  }
  return {};
}
jsx 复制代码
/* 基于 `adaptors`编写 `connect` 高阶组件: */
import { useState, useContext } from 'react';

import { StoreContext } from './context';
import { getMapDispatchToProps } from './adaptors';

function connect(mapStateToProps, mapDispatchToProps) {
  return (WrapComponent) => {

    return (props) => {
      const store = useContext(StoreContext);
      const [storeState, setStoreState] = useState(store.getState());

      const dispatch = (action) => {
        store.dispatch(action);
        setStoreState(store.getState());
      }

      const combineProps = {
        ...props,
        ...mapStateToProps(storeState),
        ...getMapDispatchToProps({
          mapDispatchToProps,
          dispatch
        })
      };

      return (
        <WrapComponent { ...combineProps } />
      );
    }
  };
}

export default connect;
  1. hooks.js 写一下外界需要使用到的自定义 hook:
js 复制代码
import { useContext, useState, useCallback } from 'react';

import { StoreContext } from './context';

function useStore() {
  return useContext(StoreContext);
}

export function useSelector(selector) {
  const store = useStore();
  
  try {
    return selector(store.getState());
  } catch (e) {
    console.warn(e);
    return null;
  }
}

export function useDispatch() {
  const store = useStore();
  const [storeState, setStoreState] = useState(store.getState());

  const dispatch = useCallback((action) => {
    store.dispatch(action);
    setStoreState(store.getState);
  }, [store]);

  return dispatch;
}

这样,ReactRedux 的基本功能就实现了!

相关推荐
_pengliang3 小时前
react native i18n插值:跨组件trans
javascript·react native·react.js
江湖行骗老中医3 小时前
react native在windows环境搭建并使用脚手架新建工程
windows·react native·react.js
screct_demo6 小时前
詳細講一下RN(React Native)中的列表組件FlatList和SectionList
javascript·react native·react.js
ThomasChan1236 小时前
Typesrcipt泛型约束详细解读
前端·javascript·vue.js·react.js·typescript·vue·jquery
崽崽的谷雨6 小时前
Fullcalendar @fullcalendar/react 样式错乱丢失问题和导致页面卡顿崩溃问题
前端·react.js·前端框架
每一天,每一步7 小时前
react页面定时器调用一组多个接口,如果接口请求返回令牌失效,清除定时器不再触发这一组请求
前端·javascript·react.js
海盐泡泡龟18 小时前
侧边栏布局和响应式布局的对比(Semi Design)
前端·react.js·html
小满zs20 小时前
React第二十五章(受控组件/非受控组件)
前端·react.js
前端加油站1 天前
N 个值得一看的前端Hooks
前端·react.js
每一天,每一步1 天前
前端react后端java实现提交antd form表单成功即导出压缩包
java·前端·react.js