React-redux+Typescript项目——Hooks中useStore、useDispatch和useSelector的基础介绍和使用,以及两者替代connect

本文介绍了在React项目中,如何使用React Redux的Hooks:useDispatch、useSelector和useStore。文章指出,使用Hooks可以提高代码可读性和组件层级的简化。useDispatch提供dispatch方法,useSelector用于根据state创建派生数据并监听store变化,useStore则用于获取整个store的引用。此外,文章讨论了useDispatch和useSelector在不同场景下的适用性,并提到了它们如何替代传统的connect方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在react中,有容器组件和UI组件之分,在React Hooks出现之前,UI组件我们可以使用函数,无状态组件来展示UI,而对于容器组件,函数组件就无能为力,我们依赖类组件来获取数据处理数据,并向下传递参数给UI组件进行渲染。使用React Hooks相比从前的类组件有一下几个好处:

1.代码可读性更强,原本同一块功能的代码逻辑被拆分到不同的生命周期函数中,容易使开发者不利于维护和迭代,通过React Hooks可以讲功能代码聚合,方便阅读维护。

2.组件树层级变浅(子组件即UI组件可自己编写,但父组件即容器组件必须使用connect()()创建并暴露),在原本的代码中,我们经常用HOC/render props等方式来服用组件等状态,增加功能等,无疑增加了组件树层数及渲染,而在React Hooks中,这些功能都可以通过强大的Hooks实现(不用connect()()再创建容器组件)

 

让我们通过下面的项目练习,了解useStore、useDispatch和useSelector的使用。最后有总结

需求:

 项目结构:用hooks就不用建这个文件

 count.tsx文件:

import { Select } from "antd";
import React, { useEffect } from "react";
import { useState } from "react";
import { FC } from "react";
import {
  decrementAction,
  incrementAction,
  incrementAsyncAction,
} from "../redux/action";
import store from "../redux/store";
import { useStore, useDispatch, useSelector } from "react-redux";
import { ActionType } from "../types/actionTypes";

interface Props {
  count: number;
}
// 不用useSelector和useDispatch需用Props
// const CountUI: FC<Props> = (props) => {
const CountUI: FC = () => {
  const [selectNumber, setSelectNumber] = useState<number>(1);
  /*一个store可以有多个countReducer。
   store中的getState()方法调用可以得到store里的状态,
   与useSelector不同的是需要监听状态变化手动更新视图,useSelector则是自动更新视图,
   useSelector传入的函数可以加工数据,预处理数据,所有逻辑就在reducer里面处理,降低耦合度
   const count = useStore<any>().getState().countReducer
   */
  const count = useSelector((state: any) => state.countReducer);
  const myDispatch = useDispatch<any>();

  const increment = () => {
    myDispatch({ type: ActionType.Increment, data: selectNumber });
  };
  const decrement = () => {
    myDispatch({ type: ActionType.Decrement, data: selectNumber });
  };
  const incrementOdd = () => {
    if (count % 2 !== 0) {
      myDispatch(incrementAction(selectNumber));
    }
  };
  const incrementAsync = () => {
    myDispatch(incrementAsyncAction(selectNumber, 2000));
  };

  return (
    <div>
      <h1>当前求和为:{count}</h1>
      <Select
        defaultValue={selectNumber}
        style={{ width: 120 }}
        onChange={(v: number) => setSelectNumber(v * 1)}
      >
        <Select.Option value="1">1</Select.Option>
        <Select.Option value="2">2</Select.Option>
        <Select.Option value="3">3</Select.Option>
      </Select>
      <button onClick={increment}>➕</button>&nbsp;
      <button onClick={decrement}>➖</button>&nbsp;
      <button onClick={incrementOdd}>当前求和为奇数再加</button>&nbsp;
      <button onClick={incrementAsync}>异步加</button>
    </div>
  );
};

export default CountUI;

action.tsx文件:

import { ActionType } from "../types/actionTypes";

export const incrementAction = (v: number) => ({ type: ActionType.Increment, data: v });

export const decrementAction = (v: number) => ({ type: ActionType.Decrement, data: v });

export const incrementAsyncAction = (v: number, t: number) => {
  return (dispatch:any) => {
    setTimeout(() => {
      dispatch(incrementAction(v));
    },t);
  };
};

reducer.tsx文件:

import { ActionType } from "../types/actionTypes";

function countReducer(preState: number, action: any) {
  if (preState == undefined) preState = 0;
  const { type, data } = action;
  switch (type) {
    case ActionType.Increment:
      return preState + data;
    case ActionType.Decrement:
      return preState - data;
    default:
      return preState;
  }
}

export default countReducer;

store.tsx文件:

/**
 * 该文件专门用于暴露一个Store对象,整个应用只有一个store对象
 */

//引用createStore,专门用于创建redux中最为核心的store对象
import { legacy_createStore as createStore, combineReducers ,applyMiddleware} from "redux";
import countReducer from "./reducer";
//引用redux-thunk用于支持异步action
import thunk from 'redux-thunk'

const reducer = combineReducers({ countReducer });
export default createStore(reducer,applyMiddleware(thunk));

actionTypes.tsx文件:

export enum ActionType {
  Increment,
  Decrement,
}

App.tsx文件:

import React from "react";
import { FC } from "react";
// import Count from "./containers/countContainer";
import store from "./redux/store";
import { Provider } from "react-redux";
import CountUI from "./components/count";

const App: FC = () => {
  return (
    // 必须要用Provider包裹,并给容器组件传store
    <Provider store={store}>
      {/* 没有hooks的写法(类组件同样)
      <Count /> */}
      {/* 有hooks的写法 */}
      <CountUI/>
    </Provider>
  );
};

export default App;

index.tsx文件:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

总结

1.useDispatch

这个hook能得到redux store 的dispatch方法引用,通常手动dispatch action

例如:

const myDispatch = useDispatch<any>();

  const increment = () => {
    myDispatch({ type: ActionType.Increment, data: selectNumber });
  };

之前在使用 connect 的时候,我们通常使用mapDispatchToProps和actionCreator封装一下dispatch action的过程,然而使用 useDispatch()的时候却需要“手动”地调用 dispatch()方法。

然而,实际上 Redux Hooks 曾经提供一个叫 useActions()的 API 起到类似于mapDispatchToProps 和 bindActionCreators 的作用,但后来被 Dan Abramov (React和Redux的核心成员)毙掉了,主要有两个原因:

避免将 actionCreator 的 dependency 也不得不加进 useActions() 中,导致代码冗长(dependency array)
在 Hooks 的世界中,直接使用 dispatch() 和 actionCreator 让代码更直接。相反,使用类似mapDispatchToProps 和 bindActionCreators 虽然看似缩短了代码量,却让开发者一定程度上丢失了对 redux 整体数据流动的视野和理解

2.useSelector

组件可以通过uesSelector访问store中释放的state数据

const count 222= useSelector((state: any) => state.countReducer//处理数据操作);

selector的作用时根据redux的state查找、筛选、处理后获得一个或多个派生的数据。

useSelector()这个hook的参数就是selector并返回selector的计算结果。重要的是这个hook会订阅redux store(牢记),所以每次redux store有更新,useSelector()里的selector就会重新计算一次并返回新的结果,并重新渲染当前组件。

3.useStore

useStore()这个Hook可以获取到Redux store的引用,所以可以使用到更‘底层’的API,如

  • getState()
  • dispatch(action)
  • subscribe(listener)
  • replaceReducer(nextReducer)

其中useStore().dispatch等同于useDispatch()

const dispatch = useDispatch();
const store = useStore();
console.log("they are equal: " + (dispatch === store.dispatch)); // true

store中的getState()方法可以得到store中的所有redux,但它并不等于useSelector()。最大的区别在于:getState()只会获取到当前时刻的state,之后的state更新并不会导致这个歌方法被再次调用,也不会导致重新渲染。并且useSelector()传入的函数可以预处理数据。

const count111 = useStore<any>().getState().countReducer

const count 222= useSelector((state: any) => state.countReducer//处理数据操作);

因此,根据业务需求:

  • 假如当前组件需要监听 redux state 的变化,并根据 redux state的更新而渲染不同的视图或者有不同行为—— 那么就应该使用 useSelector Hook
  • 假如当前组件只是为了在 redux state中一次性查询某个数据/状态,并不关心(或刻意忽略)之后的更新—— 那么就应该使用 useStore().getState()

4.两者代替connect

平时我们使用redux的时候可能使用的HOC的形式,mapStateToPropsmapDispatchToProps加强组件,例如:

//引入connect用于链接UI组件和redux
import { connect } from "react-redux";
import { Dispatch } from "redux";
//引入countUI组件
import CountUI from "../components/count";
import { ActionType } from "../types/actionTypes";

//函数的返回值作为状态(key:value组合)返回给了UI组件----状态
function mapStateToProps(state: any) {
  //这个状态是容器组件的父组件自动传过来的
  return { count: state.countReducer };
}
function mapDispatchToProps(dispatch: Dispatch) {
  //这个通知redux执行的方法是容器组件的父组件自动传过来的
  return {
    jia: (number: number) => dispatch({ type: ActionType.Increment, data: number });
    jian: (number: number) => dispatch({ type: ActionType.Decrement, data: number });
  };
}

//使用connect()()创建并暴露一个Count的容器组件
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);

我们可以用useDispatch和useSelector来实现类似的需求。如

import { Select } from "antd";
import React, { useEffect } from "react";
import { useState } from "react";
import { FC } from "react";
import {
  incrementAction,
  incrementAsyncAction,
} from "../redux/action";
import { useDispatch, useSelector } from "react-redux";
import { ActionType } from "../types/actionTypes";

const CountUI: FC = () => {
  const [selectNumber, setSelectNumber] = useState<number>(1);
  const count = useSelector((state: any) => state.countReducer);
  const myDispatch = useDispatch<any>();

  const increment = () => {
    myDispatch({ type: ActionType.Increment, data: selectNumber });
  };
  const decrement = () => {
    myDispatch({ type: ActionType.Decrement, data: selectNumber });
  };
  const incrementOdd = () => {
    if (count % 2 !== 0) {
      myDispatch(incrementAction(selectNumber));
    }
  };
  const incrementAsync = () => {
    myDispatch(incrementAsyncAction(selectNumber, 2000));
  };

  return (
    <div>
      <h1>当前求和为:{count}</h1>
      <Select
        defaultValue={selectNumber}
        style={{ width: 120 }}
        onChange={(v: number) => setSelectNumber(v * 1)}
      >
        <Select.Option value="1">1</Select.Option>
        <Select.Option value="2">2</Select.Option>
        <Select.Option value="3">3</Select.Option>
      </Select>
      <button onClick={increment}>➕</button>&nbsp;
      <button onClick={decrement}>➖</button>&nbsp;
      <button onClick={incrementOdd}>当前求和为奇数再加</button>&nbsp;
      <button onClick={incrementAsync}>异步加</button>
    </div>
  );
};

export default CountUI;

小白笔记,如有不对请指教

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值