我应该使用useselector代替mapStateToProps

时间:2020-01-15 09:14:20

标签: javascript reactjs react-redux react-hooks

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

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

5 个答案:

答案 0 :(得分:14)

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

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

答案 1 :(得分:2)

由于没有人知道如何回答,因此似乎最好的答案是,当您需要组件根目录以外的其他地方的信息时,不应使用useselector。由于您不知道组件将来是否会更改,因此根本不要使用useselector。

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

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

答案 2 :(得分:1)

我认为您误解了“顶级”是什么。它仅意味着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. 通过在需要访问存储区时不编写连接功能来节省时间,并在不再需要访问存储区时将其删除。 react devtools中没有无尽的包装器
  2. 您有明显的区分,并且来自连接的道具,来自父级的道具与来自第三方库的包装器注入的道具之间没有冲突
  3. 有时您(或与您合作的开发人员)会在mapStateToProps中为道具选择不清楚的名称,并且您必须一直滚动到文件中的mapStateToProps,以找出使用哪个选择器对于这个特定的道具。钩子不是这种情况,钩子将选择器和变量及其返回的数据耦合在同一行上
  4. 通过使用钩子,您可以获得钩子的一般优势,其中最大的优点是能够耦合在一起并在多个组件中重用相关的状态逻辑
  5. 使用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)

答案 3 :(得分:1)

对于回调函数,您可以像使用 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 在他的回答中所说的那样,hook 语句本身不能是动态的/有条件的。这是因为基础钩子(react 的内部钩子:useState 等)的顺序被后备框架用于填充每次渲染的存储数据。

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

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

答案 4 :(得分:0)

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) {
      });
}, []);
}
相关问题