React Redux 进阶: Hooks 版本用法 Custom Context 局部 Store 实践

news/2024/7/15 18:56:50 标签: javascript, redux, react hooks, react.js

React Redux 进阶: Hooks 版本用法 & Custom Context 局部 Store 实践

文章目录

  • React Redux 进阶: Hooks 版本用法 & Custom Context 局部 Store 实践
  • 前言
  • 正文
    • 1. 在函数组件内消费 Redux 数据
      • 1.1 Redux 全局状态定义
      • 1.2 使用 Hooks 消费 Redux 数据
    • 2. Custom Context 实现局部 store
      • 2.1 HeaderStore 局部状态定义
      • 2.2 消费局部 store
  • 结语
  • 其他资源
    • 参考连接
    • 完整代码示例

前言

我们在 React 框架下使用 Redux 的时候通常就是简单的使用 react-redux 提供的全局 api,并且在整个项目的最高层组件外包裹一层 Provider 来维护一个全局 Store。

本篇将要带大家来学习如何在函数组件内使用 redux 数据,第二部分则是带大家认识其实 redux 是提供了我们局部 Store 的结局方案的!

正文

1. 在函数组件内消费 Redux 数据

第一部分我们照旧先使用全局的 store 对象,不过我们不再使用 connect 函数来将 Redux 数据映射到 props 了,而是用更适合于函数组件的 hooks api

1.1 Redux 全局状态定义

  • /src/store.ts

当然第一步我们先定义好 store 相关的东西

首先是全局 state

// State
export interface IAppState {
  title: string;
}

const initAppState: IAppState = {
  title: '',
};

然后定义以下 actions

// Actions
enum EAppActionType {
  UpdateTitle,
}

type AppAction = { type: EAppActionType.UpdateTitle; payload: string };

export const updateTitleCreator: ActionCreator<AppAction> = (
  title: string
) => ({
  type: EAppActionType.UpdateTitle,
  payload: title,
});

接下来给出 reducer 来处理更新

// Reducer
const globalReducer = (
  prevState: IAppState = initAppState,
  action: AppAction
) => {
  switch (action.type) {
    case EAppActionType.UpdateTitle:
      return { ...prevState, title: action.payload };
    default:
      return prevState;
  }
};

最后使用 globalReducer 创建 store 对象

// Store
export const store = createStore(globalReducer);

另外我们还封装一个钩子使 ActionCreator 更方便使用

// hooks
export const useActions = (actions: ActionCreator<AppAction>) => {
  const dispatch = useDispatch();

  const boundActions = bindActionCreators(actions, dispatch);

  return boundActions;
};

1.2 使用 Hooks 消费 Redux 数据

定义好 Redux 数据之后,我们照旧在全局提供一个 Provider

  • /src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

import { Provider } from 'react-redux';
import { store } from './store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.querySelector('#app')
);

接下来我们就可以在 App 组件里面使用 useStore、useSelector、useDispatch 等方法消费 Redux 数据了

  • /src/App.tsx
import styles from './index.module.scss';

import React, { useEffect } from 'react';
import classNames from 'classnames';

import useDocumentTitle from '@hooks/useDocumentTitle';
import Header from '@layouts/Header';
import { useSelector } from 'react-redux';
import { IAppState, updateTitleCreator, useActions } from './store';

const useTitle = () => {
  const title = useSelector((state: IAppState) => state.title);

  const updateTitle = useActions(updateTitleCreator);

  useEffect(() => {
    updateTitle('React Redux - Hooks');
  }, []);

  useDocumentTitle(title);
};

const App: React.FC<{}> = () => {
  useTitle();

  return (
    <div className={classNames(styles.app)}>
      <Header />
    </div>
  );
};

export default App;

useSelector 就是 useStore().getState() 的组合技,而 useDispatch 则是 hooks 版用于获取 store.dispatch。

如下我们就能看到 document.title 从默认的空串变成我们自定义的字符串了

2. Custom Context 实现局部 store

第二步实际上是使用 react-redux 非常重要的一个概念,我们不能总是将状态放到全局对象上,相反的我们应该总是将所谓的 store 压制在最小许可范围内,也才能更好的控制 store 中数据的生命周期

2.1 HeaderStore 局部状态定义

一样第一步我们要先定义 redux 中 store 相关配置

  • /src/layouts/Header/store.ts

首先有 state 状态

// State 类型
interface IUserInfo {
  name: string;
}

export interface IHeaderState {
  title: string;
  userInfo: IUserInfo;
}

const initHeaderState: IHeaderState = {
  title: '',
  userInfo: {
    name: '',
  },
};

Acitons 类型

// Action 类型
enum EHeaderActionType {
  ResetAll,
  UpdateTitle,
  UpdateUserInfo,
}

type HeaderAction =
  | { type: EHeaderActionType.ResetAll }
  | { type: EHeaderActionType.UpdateTitle; payload: string }
  | { type: EHeaderActionType.UpdateUserInfo; payload: IUserInfo };

还有与 Actions 匹配的 ActionCreator

// ActionCreators
export const resetCreator: ActionCreator<HeaderAction> = () => ({
  type: EHeaderActionType.ResetAll,
});

export const updateTitleCreator: ActionCreator<HeaderAction> = (
  title: string
) => ({
  type: EHeaderActionType.UpdateTitle,
  payload: title,
});

export const updateUserInfoCreator: ActionCreator<HeaderAction> = (
  userInfo: IUserInfo
) => ({
  type: EHeaderActionType.UpdateUserInfo,
  payload: { ...userInfo },
});

以及 reducer

// Reducer
const headerReducer = (
  prevState: IHeaderState = initHeaderState,
  action: HeaderAction
) => {
  switch (action.type) {
    case EHeaderActionType.ResetAll:
      return initHeaderState;
    case EHeaderActionType.UpdateTitle:
      const title = action.payload;
      return { ...prevState, title };
    case EHeaderActionType.UpdateUserInfo:
      const userInfo = action.payload;
      return { ...prevState, userInfo };
    default:
      return prevState;
  }
};

最后是 store 与 hooks,与前面不同的是这时候我们需要提供一个自己的 Context 而不是默认的全局 Context,也就是说 useSelector、useDispatch 也是需要绑定到我们自己的 Context 上才能实现与全局 Context 区隔

// store
export const store = createStore(headerReducer);

// context
export const headerContext = React.createContext(null);

// hooks
export const useHeaderStore = createStoreHook(headerContext);
export const useHeaderSelector = createSelectorHook(headerContext);
export const useHeaderDispatch = createDispatchHook(headerContext);

export const useHeaderActions = (
  actions: ActionCreator<HeaderAction>,
  deps: DependencyList = []
) => {
  const dispatch = useHeaderDispatch();

  const boundActions = useMemo(() => {
    return bindActionCreators(actions, dispatch);
  }, [dispatch, ...deps]);

  return boundActions;
};

createStoreHook、createSelectorHook、createDispatchHook 都是用来绑定 context 后返回对应的 useStore、useSelector、useDispatch 钩子

2.2 消费局部 store

定义好了之后我们就可以到组件内消费了,首先是局部的根组件

  • /src/layouts/Header/index.tsx
import styles from './index.module.scss';

import React from 'react';
import { Provider } from 'react-redux';

import { headerContext, store } from './store';
import Title from './Title';
import Avator from './Avator';
import MoreActions from './MoreActions';

const Header = () => {
  return (
    <Provider context={headerContext} store={store}>
      <div className={styles.header}>
        <Title />
        <div className={styles.info}>
          <Avator />
          <MoreActions />
        </div>
      </div>
    </Provider>
  );
};

export default Header;

在 Header 上注入自定义 context 的 Provider 之后,就可以在 Title、Avator、MoreActinos 中使用特制的钩子,与全局提供的 useSelector、useDispatch 没什么区别

  • /src/layouts/Header/Title/index.tsx
import styles from './index.module.scss';

import React, { useEffect } from 'react';
import {
  IHeaderState,
  updateTitleCreator,
  useHeaderActions,
  useHeaderSelector,
} from '../store';

const Title = () => {
  const title = useHeaderSelector((state: IHeaderState) => state.title);

  const updateTitle = useHeaderActions(updateTitleCreator);

  useEffect(() => {
    const title = 'React Redux - Hooks';
    let n = 0;
    for (let i = 0; i < title.length; i++) {
      const nextChar = title.charAt(i);
      if (nextChar === ' ') {
        continue;
      }
      setTimeout(() => {
        updateTitle(title.substring(0, i + 1));
      }, n * 500);
      n++;
    }
  }, []);

  return <h1 className={styles.title}>{title}</h1>;
};

export default Title;
  • /src/layouts/Header/Avator/index.tsx
import styles from './index.module.scss';

import React, { useEffect } from 'react';
import {
  IHeaderState,
  updateUserInfoCreator,
  useHeaderActions,
  useHeaderSelector,
} from '../store';

const Avator = () => {
  const userInfo = useHeaderSelector((state: IHeaderState) => state.userInfo);

  const updateUserInfo = useHeaderActions(updateUserInfoCreator);

  useEffect(() => {
    setTimeout(() => {
      updateUserInfo({ name: '超悠閒' });
    }, 2500);
  }, []);

  return (
    <div className={styles.avator}>
      <h3>{userInfo.name}</h3>
    </div>
  );
};

export default Avator;
  • /src/layouts/Header/MoreActions/index.tsx
import styles from './index.module.scss';

import React, { useCallback } from 'react';
import {
  resetCreator,
  updateTitleCreator,
  updateUserInfoCreator,
  useHeaderActions,
} from '../store';

const MoreActions = () => {
  const updateTitle = useHeaderActions(updateTitleCreator);
  const updateUserInfo = useHeaderActions(updateUserInfoCreator);
  const resetHeader = useHeaderActions(resetCreator);

  const retry = useCallback(() => {
    resetHeader();

    const title = 'React Redux - Hooks';
    for (let i = 1; i < title.length; i++) {
      setTimeout(() => {
        updateTitle(title.substring(0, i));
      }, i * 500);
    }

    setTimeout(() => {
      updateUserInfo({ name: '超悠閒' });
    }, 2500);
  }, []);

  return (
    <div className={styles.more}>
      <button onClick={retry}>重试</button>
    </div>
  );
};

export default MoreActions;

最终结果如下,实际上自己把代码拉下来可以看到还做了一个小小的打字机动画

结语

本篇其实就是介绍几个 react-redux 的进阶钩子,本质上用法都不是太难,非常重要的一点就是可以多使用 custom context 的特性区分多个 store 实例,避免将状态放置在全局

其他资源

参考连接

TitleLink
React Redux - Hookshttps://react-redux.js.org/api/hooks
React 项目启动2:使用 webpack 手动创建 React 项目(附加 React Router + Redux)https://blog.csdn.net/weixin_44691608/article/details/116363154
React 高阶指引: Context 上下文 & 组件组合 & Render Propshttps://blog.csdn.net/weixin_44691608/article/details/117458645

完整代码示例

https://github.com/superfreeeee/Blog-code/tree/main/front_end/react/react_redux_hooks


http://www.niftyadmin.cn/n/735221.html

相关文章

mysql 存储过程

直接点&#xff0c;代码为主&#xff1a; 1.创建表 CREATE TABLE t_tree (id int(11) NOT NULL AUTO_INCREMENT,park_id int(11) NOT NULL COMMENT 园区id,parent_id int(11) NOT NULL COMMENT 父tree的id,level int(11) DEFAULT NULL COMMENT 层级,PRIMARY KEY (id) ) ENGINEI…

Redux 源码解析(一): createStore API

Redux 源码解析(一): createStore API 文章目录Redux 源码解析(一): createStore API前言正文0. 背景知识0.1 源代码版本&#xff1a;4.1.1, TS 实现0.2 源码目录结构1. createStore 源码解析1.1 导出类型1.2 状态 & 返回1.3 预处理1.4 获取状态 getState1.5 订阅状态 subs…

华三路由_BGP技术详解(H3C)

1.BGP的基本概念1&#xff09;BGP &#xff08; Border Gateway Protocol&#xff0c;边界网关协议&#xff09;是一种既可以用于不同 AS &#xff08; Autonomous System&#xff0c;自治系统&#xff09;之间&#xff0c;又可以用于同一 AS 内部的动态路由协议。当 BGP 运行于…

Redux 源码解析(二): bindActionCreators combineReducers API

Redux 源码解析(二): bindActionCreators & combineReducers API 文章目录Redux 源码解析(二): bindActionCreators & combineReducers API前言正文1. bindActionCreators 源码解析1.1 类型定义1.2 导出方法签名1.3 源码实现2. combineReducers 源码解析2.1 使用背景2.…

学习笔记:Linux实时同步软件之inotify

Linux 内核从 2.6.13 版本开始提供了 inotify 通知接口&#xff0c;用来监控文件系统的各种变化情况&#xff0c;如文件存取、删除、移动等。利用这一机制&#xff0c;可以非常方便地实现文件异动告警、增量备份&#xff0c;并针对目录或文件的变化及时作出响应。inotify可以通…

Redux 源码解析(三): compose applyMiddleware API

Redux 源码解析(三): compose & applyMiddleware API 文章目录Redux 源码解析(三): compose & applyMiddleware API前言正文1. compose 源码解析1.1 方法签名1.2 源码实现2. applyMiddleware 源码解析2.1 类型定义2.2 方法签名2.3 源码实现结语其他资源参考连接阅读笔记…

超简单将Centos的yum源更换为国内的阿里云源

自己的yum源不知道什么时候给改毁了……搜到了个超简单的方法将yum源更换为阿里的源 完全参考 http://mirrors.aliyun.com/help/centos?spm5176.bbsr150321.0.0.d6ykiD 1、备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2、下载新的Ce…

ztree 模糊搜索数据重复问题处理

var btnName"station"var treeObj$.fn.zTree.getZTreeObj(btnName"Tree"); nodeListtreeObj.getNodesByParamFuzzy("pinyin", $btn.val().trim().toLowerCase(),null);//移除重复站点信息 nodeListremoveNodes(nodeList);/** * 不清楚是不是ztre…