useSelector

useSelector用于从完整的Store中选取出一部分State,并在选出的这一部分State发生变化的时候通知React重新渲染组件。使用useSelector的组件必须被包含在Provider中,否则useSelector将找不到能够提供Store的上下文。

根据之前的示例,如果在应用中的某一个组件需要使用定义的counterSlice这个State,那么就可以如同以下实例中一样实现。

import { useSelector } from 'react-redux';

export const CounterComponent = () => {
  const counter = useSelector(state => state.counter);
  return <div>{counter}</div>;
};

在默认情况下,使用useSelector创建的选择器是不会保存任何内部状态的,每次调用useSelector都会在组件的实例中创建一个全新的选择器。但是如果需要在多个组件实例之间共享状态,也就是让选择器拥有内部状态,可以使用Redux Toolkit从reselect库中重新到导出的createSelector函数来在组件外部创建State选择方法。

以下是createSelector函数的签名。

createSelector(...inputSelectors | [inputSelectors], resultFunc, selectorOptions?)

这里借用官网的一个示例来展示createSelector的使用方法。

import { createSelector } from '@reduxjs/toolkit';
import { useSelector } from 'react-redux';

// createSelector这里接受了三个参数,其中前两个是用来从State中选出所需要的State的选择器,分别用于从State中选出不同的部分。
// 其中第二个选择器参数比较特殊,它忽略了传入的State,并且支持了一个额外的参数,这个参数将直接对应到createSelector函数的第二个参数。
// 所有的选择器选出的State部分,都将映射到最后一个函数参数的参数列表中,并且最后一个函数参数的运算经过将成为整个选择器的返回值。
// createSelector返回的是一个函数,这个函数可以在之后的useSelector中调用。
const selectCompletedTodosCount = createSelector(
  (state) => state.todos,
  (_, completed) => completed,
  (todos, completed) => todos.filter((todo) => todo.completed === completed).length
);

// 通过调用createSelector生成的选择器函数,useSelector获取到的内容就不止是State的一部分,而可以是一个经过计算处理的State部分。
export const CompletedTodosCount = ({ completed }) => {
  // 此处获取到的不是State的一部分,而是一个基于State计算出的一个数字。
  const matchingCount = useSelector((state) => selectCompletedTodosCount(state, completed));

  return <div>{matchingCount}</div>;
});

这种用法可以保证每个组件实例都有一个基于props的独立选择器实例。但是如果需要在多个组件实例之间保证每个组件实例都有自己独立的选择器实例的话,就需要使用一些额外的操作。

以下同样借用官网上增强后的示例来说明。

import { createSelector } from '@reduxjs/toolkit';
import { useMemo } from 'react';
import { useSelector } from 'react-redux';

// 这个进行复杂计算的State选择器的定义没有任何变化。
const selectCompletedTodosCount = createSelector(
  state => state.todos,
  (_, completed) => completed,
  (todos, completed) => todos.filter(todo => todo.completed === completed).length
);

export const CompletedTodosCount = ({ completed }) => {
  // 这里利用useMemo对每一个组件实例创建一个独立的selector函数实例,这样就可以打断同一个组件在不同实例之间共享selector状态的情况了。
  const selectTodosCount = useMemo(selectCompletedTodosCount, []);

  const matchingCount = useSelector(state => selectTodosCount(state, completed));

  return <div>{matchingCount}</div>;
};