在我的redux容器中,我必须从商店中调出很多复杂的动作。在没有破坏表演的情况下,我找不到合适的模式来解决问题。
我们以一个只包含发送按钮的容器为例发送消息:
(对于这样一个小例子,以下任何一种方法都可以正常工作,我只是想说明我遇到的更大容器的问题。)
function mapStateToProps(state) {
return {
user: selectors.selectedUser(state),
title: selectors.title(state),
message: selectors.message(state),
};
}
function dispatchProps(state) {
return {
onClickSend: function(user, title, message) {
actions.sendMessage({user, title, message});
}
};
}
如果我这样做,我的简单发送按钮必须知道很多无用的属性:
SendBtn.propTypes = {
user: PropTypes.string,
title: PropTypes.string,
message: PropTypes.string,
onClickSend: PropTypes.func,
}
还要用所有这些道具调用onClickSend:
onClickSend(user, title, message)
对于“发送”按钮的了解太多了。按钮应该只知道当我们点击它时必须调用onClickSend
,这个组件是一个简单的按钮。它不应该知道任何其他道具。此外,在更复杂的情况下,它可能不仅仅需要2个道具(标题和消息),而是5或10个。
另一个问题是表演,每次我要修改信息或标题(每次击键时===),发送按钮都将被重新渲染。
我的应用目前正在使用的方法是依赖mergeProps:
function mapStateToProps(state) {
return {
user: selectors.selectedUser(state),
title: selectors.title(state),
message: selectors.message(state),
};
}
function mergeProps(stateProps, dispatchProps, ownProps) {
const {user, title, message} = stateProps;
const newProps = {
onClickSend: actions.sendMessage.bind(null, {
user,
title,
message
});
};
return R.mergeAll([stateProps, dispatchProps, ownProps, newProps]);
}
我发现这种方法更好,因为“发送”按钮只需知道它必须触发其唯一属性:单击时onClickSend。无需担心用户,标题或消息。
然而,这种方法有一个很大的缺点:onClickSend的引用会在每次商店更改时发生变化,这会导致在更大的现实案例中表现非常糟糕。
性能问题的解决方案可能是使用redux-thunk并直接在动作中访问状态。
// Action file
var sendMessageAction = createAction("SEND_MESSAGE", function(dispatch, getState) {
// executed by redux thunk
const state = getState();
const args = {
user: selectors.selectedUser(state),
title: selectors.title(state),
message: selectors.message(state),
}
dispatch(sendMessage(args))
})
// Container file
function mapDispatchToProps(dispatch) {
onClickSend: dispatch(sendMessageAction())
}
但我不喜欢这种方法,因为:
我现在已经在一个大型的redux应用程序上工作了一段时间,这是迄今为止我对Redux的最大痛苦。令人惊讶的是,我没有找到太多关于如何解决它的信息,所以我想知道我是否遗漏了一些基本的东西。
你对这个问题有什么看法?
答案 0 :(得分:4)
你错过了第四种方法(类似于你的mergeProps
选项):让父组件传递一个捕获这些值的绑定函数或回调闭包,例如:
// using bind()
<SendButton onClick={sendMessage.bind(null, user, title, message)}
// or, as an inline arrow function
SendButton onClick={() => this.sendMessage(user, title, message)}
我认为理论上第五种方法可能是连接SendButton
的父,让它通过一个非常简单的点击处理程序到按钮,并让父担心来自其自身道具的参数(即,保持按钮本身完全呈现,并将逻辑放在按钮的父级中)。实际上,我猜这几乎是@samanime的建议。
您使用哪种方法取决于您。我个人可能会倾向于最后一个,只是为了每次重新渲染时它都不会重新创建一个函数。 (特别是,我会避免mergeProps
方法,因为每次商店更新时都会重新创建一个函数,这比组件重新渲染更频繁。)
我实际上在我的博文Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and Reusability中解决了关于访问状态是否打破“单向数据流”的问题。总而言之,我认为它确实没有打破单向流,我认为以thunks方式访问状态是一种完全有效的方法。
作为旁注,我通常建议人们使用对象速记参数来绑定connect
的方法,而不是编写实际的mapDispatchToProps
函数:
const mapDispatch = {
onClickSend : actions.sendMessage
};
export default connect(mapState, mapDispatch)(MyComponent);
对我而言,几乎没有理由真正写出单独的mapDispatch
函数(我在帖子Idiomatic Redux: Why Use Action Creators?中也会谈到这一点)。
答案 1 :(得分:3)
不是尝试使用Redux来解决所有问题,而是最好从容器中传递属性。然后,您还可以冒泡(通过回调)。
我可能会这样实现:
one
如果您将值发送到更合适的级别,那么让按钮之类的东西只是触发事件,您可以获得更清晰的实现。
很难用一些理论上的例子给出超级具体的建议,但我想指出绝对有可能过度使用Redux。
我建议您查看应用程序数据的位置(忽略操作),然后查看您已经拥有所需数据的位置,然后查看从哪里发送最有意义的操作。
答案 2 :(得分:0)
为什么不同时将状态和调度传递到mergeProps:
const mapStateToProps = (state) => ({state});
const mapDispatchToProps = (dispatch) => ({dispatch});
const mergeProps = (stateProps, dispatchProps) => {
const {state} = stateProps;
const {dispatch} = dispatchProps;
return {
onClickSend: () => {
dispatch(
actions.sendMessage({
user: selectors.selectedUser(state),
title: selectors.title(state),
message: selectors.message(state),
})
);
}
};
}
答案 3 :(得分:0)
我认为唯一的方法就是像my question中那样包装组件。
此刻,您的mapStateToProps返回一个新对象,该对象可能具有与先前渲染相同的值。
这将导致您的渲染函数(或者如果您具有功能组件,它将是函数本身)被调用,并且React会对结果进行DOM比较,因为先前的道具与当前道具的引用不同。 / p>
因此connect不会为您返回纯组件。
要解决此问题,您可以记住mapStateToProps,假设您的selectors.selectedUser和其他选择器函数是用reselect创建的,则被记住或返回原始值:
import { createSelector, defaultMemoize } from 'reselect';
const createMemoizedState = () =>
//return a memoized function that will return previous
// result if user, title and message are same as last time
defaultMemoize((user, title, message) => ({
user,
title,
message,
}));
function mapStateToProps() {
//initialize memoized function for creating state
const createState = createMemoizedState();
//return a function that will call memoized crate state
// function. if user, title or message doesn't change
// then returned object won't have a different reference
// {user:'hi'}!=={user:'hi'} is true but
// crateState('hi') !== createState('hi') is false
return state =>
createState(
//assuming the following functions return primitive
//or are memoized as well (createSelector from reselect)
selectors.selectedUser(state),
selectors.title(state),
selectors.message(state)
);
}
如果您有功能组件,也可以使用React.memo
export default connect(
mapStateToProps,
mapDispatchToProps
)(React.memo(functionalComponent));