web-dev-qa-db-ja.com

Reduxのストアでの単一のプロパティ変更の購読

Reduxでは、変更を保存するために簡単にサブスクライブできます。

store.subscribe(() => my handler goes here)

しかし、ストアにさまざまなオブジェクトがいっぱいで、アプリの特定の場所で、ストア内の特定のオブジェクトにのみ加えられた変更をサブスクライブしたい場合はどうすればよいでしょうか?

47
karol.barkowski

subscribeを直接使用する場合、ストアの一部にサブスクライブする方法はありませんが、Reduxの作成者が言うように- subscribeを直接使用しないでください! データの場合Reduxアプリのフローが実際に機能するためには、アプリ全体をラップする1つのコンポーネントが必要になります。このコンポーネントはストアにサブスクライブします。残りのコンポーネントは、このラッパーコンポーネントの子となり、必要な状態の部分のみを取得します。

ReduxをReactとともに使用している場合、良いニュースがあります。公式の react-redux パッケージがこれを処理します。 <Provider />と呼ばれるそのラッパーコンポーネントを提供します。次に、ストアからProviderによって渡される状態の変更をリッスンする少なくとも1つの「スマートコンポーネント」があります。状態のどの部分をリッスンするかを指定できます。状態のそれらの部分は、そのコンポーネントの小道具として渡されます(そしてもちろん、それらを自分の子に渡すことができます)。これを指定するには、「スマート」コンポーネントで connect() 関数を使用し、mapStateToPropsfunctionを最初のパラメーターとして使用します。要点をまとめると:

変更を保存するためにサブスクライブするProviderコンポーネントでルートコンポーネントをラップする

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

これで、connect()でラップされた<App />の子はすべて「スマート」コンポーネントになります。 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が子のそれぞれについてリッスンする必要がある状態の部分を選択できます。 Reactでの使用 のドキュメントを読んで、このフローをよりよく理解することをお勧めします。

89
Andy Noelker

Reduxは、ストアがいつ更新されたかを知るための単一の一般的な方法であるsubscribeメソッドのみを提供します。 subscribe AP​​Iは意図的に低レベルであり、単に引数なしで各コールバックを実行するため、subscribeへのコールバックは、変更された可能性のある情報を取得しません。知っているのは、ストアが何らかの方法で更新されたことだけです。

そのため、誰かが古い状態と新しい状態を比較する特定のロジックを作成し、何か変更があったかどうかを確認する必要があります。これを処理するには、React-Reduxを使用して、コンポーネントにmapStateToProps関数を指定し、コンポーネントにcomponentWillReceivePropsを実装し、ストアの特定の小道具が変更されたかどうかを確認します。

このケースを処理しようとするいくつかのアドオンライブラリもあります: https://github.com/ashaffer/redux-subscribe および https://github.com/jprichardson/redux -watch 。どちらも基本的に、さまざまなアプローチを使用して、状態の特定の部分を指定して確認できます。

11
markerikson

Andy Noelkerが言ったことに加えて、mapStateToPropsは状態の一部をコンポーネントツリーに適切に渡すだけでなく、状態のこれらのサブスクライブされた部分で直接行われた変更にもサブスクライブします。

ストアにバインドするすべてのmapStateToProp関数は、状態の一部が変更されるたびに呼び出されますが、呼び出しの結果は前の呼び出しと比較して浅くなります-サブスクライブした最上位キーが実行した場合変更しません(参照は同じままです)。その後、mapStateToPropsは再レンダリングを呼び出しません。そのため、コンセプトを機能させるには、mapStateToPropsをシンプルに保ち、マージ、タイプ変更などを行わないようにする必要があります。それらは単に状態の一部を渡すだけです。

サブスクライブ時に状態からデータを減らしたい場合、たとえば、状態にリストデータがあり、IDをキーとしてオブジェクトに変換する場合、または複数の状態をデータ構造に結合する場合は、mapStateToPropsを結合する必要がありますcreateSelectorライブラリのreselectを使用して、セレクター内でこれらすべての変更を行います。セレクターは、入力として渡された状態チャンクを削減およびキャッシュする純粋な関数であり、入力が変更されなかった場合-最後の呼び出しで行ったのとまったく同じ参照を返します-削減を実行しません。

2
IvanX

ハックで作成され、サブスクライバーの理解を支援するために、ストアデータに基づいて複数のストア機能を区別できます。

//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.

*/
0
Khazi Afzal