我应该使用 useselector/useDispatch 而不是 mapStateToProps

IT技术 javascript reactjs react-redux react-hooks
2021-05-16 11:40:00

在创建 React 应用程序时,如果我使用 hook useSelector,我需要遵守 hooks 调用规则(只能从功能组件的顶层调用它)。如果我使用mapStateToProps,我会在props中获得状态,我可以在任何地方使用它而没有任何问题......同样的问题useDispatch

与使用钩子相比,除了节省代码行之外,使用钩子还有什么好处mapStateToProps

4个回答

可以从组件中的任何位置读取和更改 Redux 存储状态,包括回调。每当存储状态发生更改时,组件都会重新呈现。当组件重新渲染时, useSelector 再次运行,并为您提供更新的数据,以后可以在您想要的任何地方使用。这是一个示例以及useDispatch回调内部的用法(在根级别分配之后):

function Modal({ children }) {
  const isOpen = useSelector(state => state.isOpen);
  const dispatch = useDispatch();
  function handleModalToggeled() {
    // using updated data from store state in a callback
    if(isOpen) {
      // writing to state, leading to a rerender
      dispatch({type: "CLOSE_MODAL"});
      return;
    }
    // writing to state, leading to a rerender
    dispatch({type: "OPEN_MODAL"});
  }
  // using updated data from store state in render
  return (isOpen ? (
      <div>
        {children}
        <button onClick={handleModalToggeled}>close modal</button>
      </div>
    ) : (
      <button onClick={handleModalToggeled}>open modal</button>
    );
  );
}

使用 mapStateToProps/mapDispatchToProps 没有什么是使用 useSelector 和 useDispatch 钩子做不到的。

话虽如此,这两种方法之间存在一些值得考虑的差异:

  1. 解耦:与mapStateToProps,容器逻辑(存储数据注入组件的方式)与视图逻辑(组件渲染)分离。 useSelector代表了一种新的、不同的思考连接组件的方式,认为组件之间的解耦更重要,组件是自包含的。哪个更好?结论:没有明确的赢家。来源
  2. DX(开发者体验):使用该connect函数通常意味着每个连接的组件都应该有另一个额外的容器组件,其中使用useSelector和 useDispatch 钩子非常简单。结论:钩子有更好的 DX。
  3. “Stale props”和“Zombie child”:useSelector 有一些奇怪的边缘情况,如果它依赖于 props,那么在useSelector最新更新的 props 进来之前可以在哪里运行。这些大多是罕见且可以避免的边缘情况,但它们已经存在在旧connect版本中解决了。结论:connect 比 hooks 稍微稳定一些。来源
  4. 性能优化:两者都以不同的方式支持性能优化:connect有一些先进的技术,使用合并props和其他隐藏在连接函数中的选项。useSelector接受第二个参数 - 用于确定状态是否已更改的相等函数。结论:两者都非常适合在高级情况下的表现。
  5. 类型:使用 typescript withconnect是一场噩梦。我记得自己为每个连接的组件(OwnProps、StateProps、DispatchProps)狂热地编写了三个 props 接口。Redux hooks 以一种相当直接的方式支持类型。结论:使用钩子更容易处理类型。
  6. React 的未来:Hooks 是 React 的未来。这可能听起来像是一个奇怪的论点,但随着“并发模式”和“服务器组件”的出现,生态系统的改变就在眼前。虽然未来的 React 版本仍将支持类组件,但新功能可能仅依赖于钩子。这种变化当然也会影响生态系统中的第三方库,比如 React-Redux。结论:钩子更能证明未来。

TL;DR - 最终结论:每种方法都有其优点。connect更成熟,出现奇怪错误和边缘情况的可能性较小,并且具有更好的关注点分离。Hooks 更易于阅读和编写,因为它们被放置在它们使用的地方附近(全部在一个独立的组件中)。此外,它们更易于与 TypeScript 一起使用。最后,它们将很容易升级到未来的 React 版本。

我认为您误解了“顶级”是什么。它仅仅意味着,在函数组件内部,useSelector()不能放置在循环、条件和嵌套函数中。它与根组件或组件结构没有任何关系

// bad
const MyComponent = () => {
  if (condition) {
    // can't do this
    const data = useSelector(mySelector);
    console.log(data);
  }

  return null;
}

---

// good
const MyComponent = () => {
  const data = useSelector(mySelector);

  if (condition) {
    console.log(data); // using data in condition
  }

  return null;
}

如果有的话,mapStateToPtops位于比钩子调用更高的级别

钩子的规则使得使用那个特定的钩子变得非常困难。您仍然需要以某种方式从回调中的状态访问变化的值

公平地说,您几乎不需要在回调中访问变化的值。我不记得上次我需要那个了。通常,如果您的回调需要最新状态,您最好只分派一个动作,然后该动作的处理程序(redux-thunk、redux-saga、redux-observable 等)本身将访问最新状态

这只是一般钩子的细节(不仅仅是 useSelector),如果你真的想要,有很多方法可以解决它,例如

const MyComponent = () => {
  const data = useSelector(mySelector);
  const latestData = useRef()
  latestData.current = data

  return (
    <button
      onClick={() => {
        setTimeout(() => {
          console.log(latestData.current) // always refers to latest data
        }, 5000)
      }}
    />
  )
}

与 mapStateToProps 相比,除了节省代码行之外,使用钩子还有什么好处?

  1. 您可以在任何需要访问 store 的时候不编写连接函数来节省时间,并在您不再需要访问 store 时将其删除。react devtools 中没有无尽的包装器
  2. 来自 connect 的 props、来自 parent 的 props 和来自 3rd 方库的包装器注入的 props 之间有明显的区别并且没有冲突
  3. 有时您(或与您合作的开发人员)会为 props 选择不明确的名称,mapStateToProps并且您必须一直滚动到mapStateToProps文件中以找出用于此特定 props 的选择器。这不是钩子的情况,其中选择器和变量及其返回的数据耦合在同一行
  4. 通过使用钩子,您可以获得钩子的一般优势,其中最大的优势是能够耦合在一起并在多个组件中重用相关的有状态逻辑
  5. mapStateToProps你通常要处理mapDispatchToProps哪个更麻烦更容易迷路,尤其是阅读别人的代码(对象形式?函数形式?bindActionCreators?)。来自的 PropmapDispatchToProps可以与它的动作创建者具有相同的名称,但具有不同的签名,因为它在mapDispatchToprops. 如果您在多个组件中使用一个动作创建器,然后重命名该动作创建器,这些组件将继续使用来自 props 的旧名称。如果您有依赖循环,并且您必须处理隐藏变量名称,则对象形式很容易中断

.

import { getUsers } from 'actions/user'

class MyComponent extends Component {
  render() {
    // shadowed variable getUsers, now you either rename it
    // or call it like this.props.getUsers
    // or change import to asterisk, and neither option is good
    const { getUsers } = this.props
    // ...
  }
}

const mapDispatchToProps = {
  getUsers,
}

export default connect(null, mapDispatchToProps)(MyComponent)

有关最终答案,请参阅最后的 EDIT 2

由于没有人知道如何回答,似乎最好的答案是,当您需要在组件根级别以外的其他地方获取信息时,您不应该使用 useselector。既然不知道组件以后会不会变,那就干脆别用useselector了。

如果有人有比这更好的答案,我会更改已接受的答案。

编辑:添加了一些答案,但它们只是强调为什么你根本不应该使用 useselector,直到钩子规则发生变化的那一天,你也可以在回调中使用它。话虽如此,如果您不想在回调中使用它,它对您来说可能是一个很好的解决方案。

编辑 2:添加了我想要的所有示例的答案,并展示了如何useSelector以及useDispatch更易于使用。

useSelector钩子返回的 redux 状态可以像对mapStateToProps. 示例:它也可以传递给另一个函数。唯一的限制是在其声明期间必须遵循钩子规则:

  1. 它必须仅在功能组件中声明。

  2. 在声明期间,它不能在任何条件块内。下面的示例代码

        function test(displayText) {
           return (<div>{displayText}</div>);
        }
    
        export function App(props) {
            const displayReady = useSelector(state => {
            return state.readyFlag;
            });
    
            const displayText = useSelector(state => {
            return state.displayText;
            });
    
            if(displayReady) {
                return 
                (<div>
                    Outer
                    {test(displayText)}
    
                </div>);
            }
            else {
            return null;
            }
        }
    

编辑:由于 OP 提出了一个特定的问题——关于在回调中使用它,我想添加一个特定的代码。总而言之,我没有看到任何阻止我们在回调中使用 useSelector 钩子输出的东西。请参阅下面的示例代码,它是我自己的代码片段,用于演示此特定用例。

export default function CustomPaginationActionsTable(props) {
//Read state with useSelector.
const searchCriteria = useSelector(state => {
  return state && state.selectedFacets;
 });

//use the read state in a callback invoked from useEffect hook.
useEffect( ()=>{
  const postParams = constructParticipantListQueryParams(searchCriteria);
  const options = {
    headers: {
        'Content-Type': 'application/json'
    },
    validateStatus: () => true
  };
  var request = axios.post(PORTAL_SEARCH_LIST_ALL_PARTICIPANTS_URI, postParams, options)
      .then(function(response)
        { 
          if(response.status === HTTP_STATUS_CODE_SUCCESS) {
            console.log('Accessing useSelector hook output in axios callback. Printing it '+JSON.stringify(searchCriteria));
            
          }
                    
        })          
      .catch(function(error) {
      });
}, []);
}