在创建 React 应用程序时,如果我使用 hook useSelector
,我需要遵守 hooks 调用规则(只能从功能组件的顶层调用它)。如果我使用mapStateToProps
,我会在props中获得状态,我可以在任何地方使用它而没有任何问题......同样的问题useDispatch
与使用钩子相比,除了节省代码行之外,使用钩子还有什么好处mapStateToProps
?
在创建 React 应用程序时,如果我使用 hook useSelector
,我需要遵守 hooks 调用规则(只能从功能组件的顶层调用它)。如果我使用mapStateToProps
,我会在props中获得状态,我可以在任何地方使用它而没有任何问题......同样的问题useDispatch
与使用钩子相比,除了节省代码行之外,使用钩子还有什么好处mapStateToProps
?
可以从组件中的任何位置读取和更改 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
代表了一种新的、不同的思考连接组件的方式,认为组件之间的解耦更重要,组件是自包含的。哪个更好?结论:没有明确的赢家。来源connect
函数通常意味着每个连接的组件都应该有另一个额外的容器组件,其中使用useSelector
和 useDispatch 钩子非常简单。结论:钩子有更好的 DX。useSelector
最新更新的 props 进来之前可以在哪里运行。这些大多是罕见且可以避免的边缘情况,但它们已经存在在旧connect
版本中解决了。结论:connect 比 hooks 稍微稳定一些。来源connect
有一些先进的技术,使用合并props和其他隐藏在连接函数中的选项。useSelector
接受第二个参数 - 用于确定状态是否已更改的相等函数。结论:两者都非常适合在高级情况下的表现。connect
是一场噩梦。我记得自己为每个连接的组件(OwnProps、StateProps、DispatchProps)狂热地编写了三个 props 接口。Redux hooks 以一种相当直接的方式支持类型。结论:使用钩子更容易处理类型。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 相比,除了节省代码行之外,使用钩子还有什么好处?
mapStateToProps
并且您必须一直滚动到mapStateToProps
文件中以找出用于此特定 props 的选择器。这不是钩子的情况,其中选择器和变量及其返回的数据耦合在同一行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
. 示例:它也可以传递给另一个函数。唯一的限制是在其声明期间必须遵循钩子规则:
它必须仅在功能组件中声明。
在声明期间,它不能在任何条件块内。下面的示例代码
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) {
});
}, []);
}