在创建React应用程序时,如果我使用钩子useSelector
,则需要遵守钩子调用规则(只能从功能组件的顶层调用)。如果我使用mapStateToProps
,则会在道具中获得状态,并且可以在任何地方使用它而不会出现任何问题...
与mapStateToProps
相比,使用钩子除了节省代码行之外还有什么好处?
答案 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 钩子做不到的。
话虽如此,这两种方法之间存在一些值得考虑的差异:
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相比,使用钩子除了节省代码行之外还有什么好处?
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)
答案 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
所做的一样。示例:它也可以传递给另一个函数。唯一的限制是在其声明期间必须遵循挂钩规则:
只能在功能组件中声明它。
在声明期间,它不能在任何条件块中。下面的示例代码
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) {
});
}, []);
}