【发布时间】:2016-07-12 18:59:54
【问题描述】:
在 Redux 中,我可以轻松订阅存储更改
store.subscribe(() => my handler goes here)
但是,如果我的商店充满了不同的对象,并且在我的应用程序的特定位置我想订阅仅在商店中的特定对象中所做的更改,该怎么办?
【问题讨论】:
标签: react-native redux redux-framework
在 Redux 中,我可以轻松订阅存储更改
store.subscribe(() => my handler goes here)
但是,如果我的商店充满了不同的对象,并且在我的应用程序的特定位置我想订阅仅在商店中的特定对象中所做的更改,该怎么办?
【问题讨论】:
标签: react-native redux redux-framework
直接使用subscribe时无法订阅部分商店,但正如Redux的创建者自己所说-don't use subscribe directly!要让Redux应用程序的数据流真正起作用,您将需要一个组件包装您的整个应用程序。该组件将订阅您的商店。您的其余组件将是此包装器组件的子组件,并且只会获取它们需要的状态部分。
如果您使用 Redux 和 React,那么有个好消息 - 官方的 react-redux 包会为您解决这个问题!它提供了称为<Provider /> 的包装器组件。然后,您将拥有至少一个“智能组件”来侦听由来自商店的Provider 传递的状态更改。您可以指定它应该侦听状态的哪些部分,并且这些状态部分将作为道具传递给该组件(当然,它可以将这些传递给它自己的孩子)。您可以通过在“智能”组件上使用connect() 函数并将mapStateToProps 函数用作第一个参数来指定它。回顾一下:
用订阅存储更改的Provider组件包装根组件
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
现在<App /> 的任何被connect() 包裹的孩子都将成为“智能”组件。您可以传入mapStateToProps 来选择状态的某些部分并将其作为道具。
const mapStateToProps = (state) => {
return {
somethingFromStore: state.somethingFromStore
}
}
class ChildOfApp extends Component {
render() {
return <div>{this.props.somethingFromStore}</div>
}
}
//wrap App in connect and pass in mapStateToProps
export default connect(mapStateToProps)(ChildOfApp)
显然<App /> 可以有多个孩子,您可以选择mapStateToProps 应该为其每个孩子听的州的哪些部分。我建议阅读usage with React 上的文档,以更好地了解此流程。
【讨论】:
NavBar 调度一个动作toggleDrawer。此操作在 store 中存储一个值,表示抽屉 isDrawerOpen 的状态。那么,当动作被调度时,我如何让抽屉打开呢?这里还有一个潜在的冲突:抽屉本身可以打开并控制它的open属性,因此从抽屉更改isDrawerOpen会导致无限循环......
connect 中仍然有代码可以响应所有更改并在比较 mapStateToProps 的上一个和下一个返回之前执行至少一些代码,从而不必要地影响性能。我只是想知道我们是否可以检测到所需键的名称,例如:function mapStateToProps( ({someKey, anotherReducerKey}) => ... 并且只订阅了那些。在 javascript 中,您可以获得参数的名称——所以问题是您能否获得这些参数的解构键的名称。
func.toString(),看看我是否可以获得解构的对象参数,而你能够!所以我们可以重写那个函数来获取你想要订阅的状态键。那么我们只需要实现一个store.subscribeByKey()函数。
connect 运行您的 mapStateToProps(以及 mapDispatchToProps 和 mergeProps)函数。这在某种程度上取决于您的应用程序,而不仅仅是connect 函数。也就是说,我认为我们只需要在启动时运行一次即可获取您正在观看的密钥,因为我认为它假设 connect 函数调用可以静态分析,即不能在运行时更改。
export default connect..的目的是什么?为什么要导出它?
Redux 仅提供了一种通用的方法来了解商店何时更新:subscribe 方法。对subscribe 的回调不会获得任何可能发生变化的信息,因为subscribe API 是故意低级的,并且只是在不带参数的情况下运行每个回调。你只知道商店已经以某种方式更新了。
因此,必须有人编写特定的逻辑来比较旧状态与新状态,看看是否有任何变化。你可以通过使用 React-Redux 来处理这个问题,为你的组件指定一个 mapStateToProps 函数,在你的组件中实现 componentWillReceiveProps,并检查 store 中的特定 props 是否发生了变化。
还有几个插件库试图处理这种情况:https://github.com/ashaffer/redux-subscribe 和 https://github.com/jprichardson/redux-watch。两者基本上都允许您使用不同的方法指定要查看的特定状态部分。
【讨论】:
除了 Andy Noelker 所说的之外,mapStateToProps 不仅将部分状态正确地传递到您的组件树中,它还订阅了直接在这些订阅的状态部分中所做的更改。
确实,每次更改状态的任何部分时都会调用绑定到存储的每个 mapStateToProp 函数,但调用的结果与之前的调用相比会变浅 - 如果您订阅的顶级键没有改变(参考保持不变)。然后 mapStateToProps 不会调用重新渲染。所以如果你想让这个概念起作用,你必须保持 mapStateToProps 简单,不要合并,改变类型或任何东西,它们应该简单地传递状态的一部分。
如果您想在订阅时从状态中减少数据,例如您在状态中有列表数据,并且您想将其转换为以 ids 为键的对象,或者您想将多个状态连接到数据结构中,您应该通过在选择器中进行所有这些修改,将 mapStateToProps 与来自reselect 库的createSelector 结合起来。选择器是纯函数,它减少和缓存作为输入传入的状态块,如果输入没有改变 - 它们返回与上次调用完全相同的引用 - 不执行减少。
【讨论】:
创建了一个技巧,以帮助了解订阅者可以根据商店数据进行区分,并具有多个商店功能。
//import { createStore } from 'redux';
let createStore = require('redux').createStore;
let combineReducers = require('redux').combineReducers;
/**
* This is a reducer, a pure function with (state, action) => state signature.
* It describes how an action transforms the state into the next state.
*
* The shape of the state is up to you: it can be a primitive, an array, an object,
* or even an Immutable.js data structure. The only important part is that you should
* not mutate the state object, but return a new object if the state changes.
*
* In this example, we use a `switch` statement and strings, but you can use a helper that
* follows a different convention (such as function maps) if it makes sense for your
* project.
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
function messanger(state = 'Mr, khazi', action) {
switch(action.type) {
case 'WELCOME':
return 'Hello, Mr Khazi';
case 'BYE':
return 'Bye, Mr Khazi';
case 'INCREMENT':
return 'Incremented khazi';
default:
return state;
}
};
function latestAction(state = null, action) {
switch(action.type) {
case 'WELCOME':
return '$messanger';
case 'BYE':
return '$messanger';
case 'INCREMENT':
return '$messanger, $counter';
case 'DECREMENT':
return '$counter';
default:
return state;
}
};
let reducers = {
counts: counter,
message: messanger,
action: latestAction
};
let store = createStore(
combineReducers(reducers, latestAction)
);
// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
//let store = createStore(counter)
// You can use subscribe() to update the UI in response to state changes.
// Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly.
// However it can also be handy to persist the current state in the localStorage.
store.subscribe(() => {
if(store.getState().action.indexOf('messanger') !== -1) {
console.log('subscribed for counter actions', store.getState());
}
});
store.subscribe(() => {
if (store.getState().action.indexOf('counter') !== -1) {
console.log('subscribed for messanger actions', store.getState());
}
});
// The only way to mutate the internal state is to dispatch an action.
// The actions can be serialized, logged or stored and later replayed.
console.log('----------------Action with both subscriber-------------');
store.dispatch({ type: 'INCREMENT' });
console.log('---------------Action with counter subscriber-----------');
store.dispatch({ type: 'DECREMENT' });
console.log('---------------Action with messenger subscriber---------');
store.dispatch({ type: 'WELCOME' });
/*
every reducer will execute on each action.
*/
【讨论】: