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

Posted

技术标签:

【中文标题】我应该使用 useselector/useDispatch 而不是 mapStateToProps【英文标题】:Should I use useselector/useDispatch instead of mapStateToProps 【发布时间】:2020-05-02 00:42:06 【问题描述】:

在创建 React 应用时,如果我使用钩子 useSelector,我需要遵守钩子调用规则(只能从功能组件的顶层调用它)。如果我使用mapStateToProps,我会在道具中获得状态,我可以在任何地方使用它而不会出现任何问题...useDispatch 的同样问题

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

【问题讨论】:

你读过这个吗:react-redux.js.org/next/api/hooks#useselector 是的,它没有回答福利问题 一般钩子动机(reactjs.org/docs/hooks-intro.html#motivation)呢?多个useSelector,更细化?更具可读性的“消费” @xadm 不,因为我仍然不能像使用 mapstatetoprops 那样使用钩子在任何地方使用 redux 状态 @Yonatan Nir ​​- 这是不正确的。你可以在任何你喜欢的地方使用状态。每当更改存储状态时,组件都会重新呈现。当组件重新渲染时,useSelector 再次运行,并为您提供更新的数据,以后可以在任何您想要的地方使用。 【参考方案1】:

可以从组件中的任何位置读取和更改 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 钩子也不能做。

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

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

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

【讨论】:

【参考方案2】:

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

首先,如果你需要在功能组件内部使用状态,或者组件生命周期或者redux状态,你不需要将这个功能组件转换为类组件。

你可以阅读更多关于为什么使用 Hooks 尤其是 redux hooks **useDispatch**, **useSelector** 来自以下文章: https://medium.com/swlh/react-redux-hooks-5e5dbb52d057

第二个原因,逃离包装地狱: 组件中有两种流行的共享逻辑类型,即高阶组件和渲染道具。当然,这些都是很好的模式,不幸的是,有时它们被过度使用并且需要重组组件。这使得组件太大。因此,React 团队在 React 应用程序中创建的这个逻辑存在三个主要问题。这些问题包括:包装器地狱、Gaint 组件(当您的应用程序遇到复杂逻辑时,单个组件可能多达一千行代码)和令人困惑的类(您的 React 应用程序崩溃并且您意识到您忘记绑定它) . 当您的应用程序有大量嵌套组件时,它们将导致“包装器地狱”。

第三,没有重复的逻辑。

【讨论】:

【参考方案3】:

最后的答案见 EDIT 2

由于没有人知道如何回答,似乎最好的答案是,当您在组件的根级别以外的其他地方需要信息时,您不应该使用 useselector。由于不知道以后组件是否会发生变化,所以根本不要使用 useselector。

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

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

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

【讨论】:

我不明白你。您可以在任何地方使用 useSelector。如果您要在父组件中使用 useSelector,并将状态作为道具传递给子组件。那你就用错了。您可以连接 useSelector 任何您使用的任何组件。 钩子规则说你必须在组件的根目录使用它,这意味着你不能在任何地方使用它。最基本的事情之一是使用基于存储值的逻辑回调。对于要从​​回调将值保存到商店的调度,同样如此。如果你不能在回调中使用它们,它们一文不值 也许我误解了你。您是说钩子只能在根级别使用,这意味着钩子不应该是有条件的。我以为你的意思是钩子只能在父组件中使用【参考方案4】:

对于回调函数,您可以使用从 useSelector 返回的值,就像使用来自 useState 的值一样。

const ExampleComponent = () => 
    // use hook to get data from redux state.
    const stateData = useSelector(state => state.data);

    // use hook to get dispatch for redux store.
    // this allows actions to be dispatched.
    const dispatch = useDispatch();

    // Create a non-memoized callback function using stateData.
    // This function is recreated every rerender, a change in
    // state.data in the redux store will cause a rerender.
    const callbackWithoutMemo = (event) => 
        // use state values.
        if (stateData.condition) 
            doSomething();
        
        else 
            doSomethingElse();
        

        // dispatch some action to the store
        // can pass data if needed.
        dispatch(someActionCreator());
    ;

    // Create a memoized callback function using stateData.
    // This function is recreated whenever a value in the
    // dependency array changes (reference comparison).
    const callbackWithMemo = useCallback((event) => 
        // use state values.
        if (stateData.condition) 
            doSomething();
        
        else 
            doSomethingElse();
        

        // dispatch some action to the store
        // can pass data if needed.
        dispatch(someActionCreator());
    , [stateData, doSomething, doSomethingElse]);

    // Use the callbacks.
    return (
        <>
            <div onClick=callbackWithoutMemo>
                Click me
            </div>
            <div onClick=callbackWithMemo>
                Click me
            </div>
        </>
    )
;

钩子规则说你必须在组件的根目录使用它,这意味着你不能在任何地方使用它。

正如 Max 在他的回答中所说,这意味着钩子语句本身不能是动态/有条件的。这是因为后备框架使用基本挂钩的顺序(react 的内部挂钩:useState 等)在每次渲染时填充存储的数据。

钩子中的值可以在任何你喜欢的地方使用。

虽然我怀疑这将接近回答您的完整问题,但回电不断出现,并且没有发布任何示例。

【讨论】:

【参考方案5】:

useSelector 钩子返回的 redux 状态可以传递到其他任何地方,就像它为 mapStateToProps 所做的那样。示例:它也可以传递给另一个函数。唯一的限制是在声明期间必须遵循钩子规则:

    它只能在功能组件中声明。

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

        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 钩子输出的东西.请看下面的示例代码,它是我自己的代码中的一个 sn-p,它演示了这个特定的用例。

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) 
      );
, []);

【讨论】:

当你不能在你真正需要它的最基本的地方使用它时——在回调中,它在现实生活中是不可用的 @YonatanNir - 希望这会有所帮助。已针对您感兴趣的特定用例更新了我的答案。基本上,使用 useSelector 钩子的输出也适用于回调函数。 这不是(完全)我的意思。您正在描述一种情况,您在根级别读取状态,就像钩子的规则说我们必须那样,然后在回调中使用该值。我说的是有时需要在回调中直接从 redux 读取数据。在 redux 的回调中写入信息也是一个更大的问题【参考方案6】:

我认为您误解了“***”是什么。它仅仅意味着,在一个函数组件中,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 相比,使用 hook 除了节省代码行之外还有什么好处?

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

.

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)

【讨论】:

我不知道什么时候不需要访问回调中的数据。一个按钮被按下,你需要更改 redux 中的数据一直发生

以上是关于我应该使用 useselector/useDispatch 而不是 mapStateToProps的主要内容,如果未能解决你的问题,请参考以下文章

我应该参考哪个单位的宽度以及我应该使用啥单位进行响应式设计? (非自适应设计)

C++ 标准库 - 我应该啥时候使用它,啥时候不应该使用它?

我应该使用相同的 VBO 来传递不同的顶点属性吗?还是我应该使用 2?

当我应该/不应该使用 git pull --rebase

我应该使用哪个版本的 MSXML?

我应该使用啥 IronPython IDE?