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

mv1qrgav  于 2022-11-04  发布在  React
关注(0)|答案(6)|浏览(174)

在创建React应用时,如果我使用钩子useSelector,我需要遵守钩子调用规则(仅从功能组件的顶层调用它)。如果我使用mapStateToProps,我将在props中获得状态,并且我可以在任何地方使用它,而不会出现任何问题... useDispatch也存在同样的问题
mapStateToProps相比,除了节省代码行之外,使用钩子还有什么好处?

thigvfpy

thigvfpy1#

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代表了一种新的、不同的思考连接组件的方式,认为组件之间的解耦更重要,组件是自包含的。哪一种更好?结论:没有明显的赢家。

  1. DX(开发人员体验):使用connect函数通常意味着每个连接的组件都应该有另一个容器组件,而使用useSelector和useDispatch钩子则非常简单。钩子有更好的DX。
    1.“陈旧的道具”和“僵尸小孩”:如果useSelector依赖于props,它会出现一些奇怪的边缘情况,其中useSelector可能会在最新更新的props进入之前运行。这些大多是罕见的、可以避免的边缘情况,但它们已经在较早的connect版本中解决。结论:connect比hooks稍微稳定一些。
    1.性能优化:两者都以不同的方式支持性能优化:connect有一些先进的技术,使用合并属性和其他选项隐藏在连接函数中。useSelector接受第二个参数-一个等式函数,以确定状态是否已更改。这两种方法都非常适合在高级情况下提高性能。
    1.类型:在connect中使用typescript是一场噩梦。我记得自己狂热地为每个连接的组件编写了三个props接口(OwnProps、StateProps、DispatchProps)。Redux钩子以一种相当直接的方式支持类型。结论:使用挂接处理类型要容易得多。
  2. React的未来:挂钩是react的未来。这可能看起来像一个奇怪的论点,但生态系统的变化是正确的角落与“并发模式”和“服务器组件”。虽然类组件仍将在未来的React版本中支持,新的功能可能只依赖挂钩。这一变化当然也会影响生态系统中的第三方库,如React-Redux。结论:钩子更能适应未来。
    TL;DR -最终裁决:每种方法都有它的优点。connect更成熟,更少出现奇怪的bug和边缘情况,并且有更好的关注点分离。钩子更容易读写,因为它们被放在使用它们的地方附近(所有都在一个自包含的组件中)。而且,它们更容易与TypeScript一起使用。最后,它们将很容易升级到未来的react版本。
hzbexzde

hzbexzde2#

我想你误解了什么是“顶级”。它仅仅意味着,在一个函数组件中,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位于比钩子调用更高的级别
钩子的规则使得使用特定的钩子变得非常困难。2你仍然需要以某种方式从回调内部的状态访问一个变化的值
公平地说,你几乎从来不需要访问回调中的变化值。我不记得上次我需要它是什么时候了。通常,如果你的回调需要最新的状态,你最好只是调度一个操作,然后这个操作的处理程序(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时不需要编写connect函数,而在不再需要访问store时删除它,这样可以保存时间。
1.来自连接的属性、来自父级的属性和由第三方库的 Package 器注入的属性之间有明确的区别,没有冲突
1.有时您(或与您一起工作的开发人员)会为mapStateToProps中的属性选择不清楚的名称,您将不得不一直滚动到文件中的mapStateToProps,以找出哪个选择器用于此特定属性。
1.通过使用钩子,您可以获得钩子的一般优势,其中最大的优势是能够耦合在一起,并在多个组件中重用相关的有状态逻辑
1.对于mapStateToProps,您通常必须处理mapDispatchToProps,后者甚至更麻烦,更容易迷失方向,尤其是阅读别人的代码(对象形式?函数形式?bindActionCreators?)。来自mapDispatchToProps的属性可以与其操作创建者具有相同的名称,但签名不同,因为它在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)
7fyelxc5

7fyelxc53#

  • 请参阅最后的EDIT 2以获取最终答案 *

由于没有人知道如何回答,所以最好的答案似乎是,当您需要组件根级别以外的其他位置的信息时,不应该使用usesselector。由于您不知道组件将来是否会更改,因此根本不要使用usesselector。
如果有人有比这更好的答案,我会更改已接受的答案。
编辑:添加了一些答案,但它们只是强调了为什么你根本不应该使用useselector,直到钩子规则改变的那一天,你也可以在回调中使用它。也就是说,如果你不想在回调中使用它,它可能是一个很好的解决方案。
编辑2:添加了一个带有我想要的所有示例的答案,并展示了useSelectoruseDispatch如何更容易使用。

ulydmbyx

ulydmbyx4#

useSelector钩子返回的redux状态可以像mapStateToProps一样传递到其他任何地方。例如:它也可以传递给另一个函数。唯一的约束是在声明过程中必须遵循钩子规则:
1.它只能在功能组件中声明。
1.在声明过程中,它不能位于任何条件块中。

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

zyfwsgd65#

对于回调函数,可以使用从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 's internal hooks:useState等)被支持框架用来填充每个渲染的存储数据。
钩子的值可以在任何你喜欢的地方使用。
虽然我怀疑这将接近回答您的完整问题,回调不断出现,并没有任何例子已张贴。

bakd9h0s

bakd9h0s6#

这不是答案,但是如果你想获得mapDispatchToProps的解耦特性,同时保持钩子的简单性和开发体验,这个钩子会非常有帮助:
https://gist.github.com/ErAz7/1bffea05743440d6d7559afc9ed12ddc
我没有提到mapStatesToProps的原因是useSelector本身比mapStatesToProps的存储逻辑解耦性更强,所以看不到mapStatesToProps的任何优势。当然,我不是指直接使用useSelector,而是在存储文件中(例如,在reducer文件中)创建一个 Package 器,然后从那里导入,如下所示:

// e.g. userReducer.js
export const useUserProfile = () => useSelector(state => state.user.profile)

相关问题