Mengakses status Redux di pembuat tindakan?

296

Katakanlah saya memiliki yang berikut ini:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
  }
}

Dan dalam pembuat tindakan itu, saya ingin mengakses kondisi toko global (semua reducer). Apakah lebih baik melakukan ini:

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

atau ini:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return (dispatch, getState) => {
    const {items} = getState().otherReducer;

    dispatch(anotherAction(items));
  }
}
ffxsam
sumber

Jawaban:

522

Ada beberapa pendapat berbeda tentang apakah mengakses negara dalam pembuat tindakan adalah ide yang baik:

  • Pencipta Redux Dan Abramov merasa bahwa itu harus dibatasi: "Beberapa kasus penggunaan di mana saya pikir itu dapat diterima adalah untuk memeriksa data yang di-cache sebelum Anda membuat permintaan, atau untuk memeriksa apakah Anda diautentikasi (dengan kata lain, melakukan pengiriman bersyarat). Saya pikir menyampaikan data seperti state.something.itemsdalam pembuat tindakan jelas merupakan pola-anti dan tidak disarankan karena mengaburkan riwayat perubahan: jika ada bug dan itemstidak benar, sulit untuk melacak dari mana nilai-nilai yang salah berasal karena mereka sudah menjadi bagian dari tindakan, daripada langsung dihitung oleh peredam dalam menanggapi suatu tindakan. Jadi lakukan ini dengan hati-hati. "
  • Pemelihara Redux saat ini, Mark Erikson, mengatakan tidak apa-apa dan bahkan dianjurkan untuk menggunakannya getStatedi thunks - karena itulah ia ada . Dia membahas pro dan kontra dari mengakses negara dalam tindakan pencipta di posting blognya Idiomatic Redux: Pikiran pada Thunks, Sagas, Abstraction, dan Reusability .

Jika Anda merasa perlu ini, kedua pendekatan yang Anda sarankan baik-baik saja. Pendekatan pertama tidak memerlukan middleware:

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

Namun Anda dapat melihat bahwa itu bergantung pada storeekspor tunggal dari beberapa modul. Kami tidak menyarankan hal itu karena akan lebih sulit untuk menambahkan rendering server ke aplikasi Anda karena dalam kebanyakan kasus di server Anda ingin memiliki toko terpisah per permintaan . Jadi, sementara secara teknis pendekatan ini berhasil, kami tidak menyarankan mengekspor toko dari modul.

Inilah mengapa kami merekomendasikan pendekatan kedua:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return (dispatch, getState) => {
    const {items} = getState().otherReducer;

    dispatch(anotherAction(items));
  }
}

Ini mengharuskan Anda untuk menggunakan Redux Thunk middleware tetapi berfungsi dengan baik pada klien dan di server. Anda dapat membaca lebih lanjut tentang Redux Thunk dan mengapa perlu dalam kasus ini di sini .

Idealnya, tindakan Anda tidak boleh "gemuk" dan harus mengandung informasi sesedikit mungkin, tetapi Anda harus merasa bebas untuk melakukan apa yang terbaik untuk Anda dalam aplikasi Anda sendiri. Redux FAQ memiliki informasi tentang logika pemisahan antara pembuat tindakan dan reduksi dan waktu di mana mungkin berguna untuk digunakan getStatedalam pembuat tindakan .

Dan Abramov
sumber
5
Saya memiliki satu situasi ketika memilih sesuatu dalam suatu komponen dapat memicu PUT atau POST, tergantung apakah toko berisi atau tidak data yang terkait dengan komponen. Apakah lebih baik untuk menempatkan logika bisnis untuk pemilihan PUT / POST dalam komponen daripada pencipta tindakan berbasis thunk?
vicusbass
3
Apa praktik terbaik? Saya sekarang menghadapi masalah serupa di mana saya menggunakan getState di pembuat tindakan saya. Dalam kasus saya, saya menggunakannya untuk menentukan apakah nilai jika formulir memiliki perubahan yang tertunda (dan jika demikian saya akan mengirimkan tindakan yang menampilkan dialog).
Arne H. Bitubekk
42
Tidak apa-apa untuk membaca dari toko di pencipta aksi. Saya mendorong Anda untuk menggunakan pemilih sehingga Anda tidak bergantung pada bentuk negara yang tepat.
Dan Abramov
2
Saya menggunakan middleware untuk mengirim data ke mixpanel. Jadi saya punya kunci meta di dalam aksi. Saya perlu memasukkan variabel yang berbeda dari negara ke mixpanel. Menempatkan mereka pada pencipta aksi tampaknya menjadi anti-pola. Apa yang akan menjadi pendekatan terbaik untuk menangani kasus penggunaan semacam ini?
Aakash Sigdel
2
Oh man! Saya tidak getState
mengaitkan redux
33

Ketika skenario Anda sederhana, Anda dapat menggunakannya

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

Namun terkadang Anda action creatorperlu memicu multi aksi

misalnya permintaan async sehingga Anda perlu REQUEST_LOAD REQUEST_LOAD_SUCCESS REQUEST_LOAD_FAILtindakan

export const [REQUEST_LOAD, REQUEST_LOAD_SUCCESS, REQUEST_LOAD_FAIL] = [`REQUEST_LOAD`
    `REQUEST_LOAD_SUCCESS`
    `REQUEST_LOAD_FAIL`
]
export function someAction() {
    return (dispatch, getState) => {
        const {
            items
        } = getState().otherReducer;
        dispatch({
            type: REQUEST_LOAD,
            loading: true
        });
        $.ajax('url', {
            success: (data) => {
                dispatch({
                    type: REQUEST_LOAD_SUCCESS,
                    loading: false,
                    data: data
                });
            },
            error: (error) => {
                dispatch({
                    type: REQUEST_LOAD_FAIL,
                    loading: false,
                    error: error
                });
            }
        })
    }
}

Catatan: Anda perlu redux-thunk untuk mengembalikan fungsi di pembuat tindakan

Nour Sammour
sumber
3
Bisakah saya bertanya apakah harus ada pemeriksaan pada status negara 'memuat' sehingga permintaan ajax lain tidak dibuat saat yang pertama selesai?
JoeTidee
@ JoTidee Dalam contoh pengiriman status pemuatan dilakukan. Jika Anda melakukan tindakan ini dengan tombol misalnya, Anda akan memeriksa jika loading === trueada dan menonaktifkan tombol.
croraf
4

Saya setuju dengan @Bloomca. Melewati nilai yang dibutuhkan dari toko ke fungsi pengiriman sebagai argumen tampaknya lebih sederhana daripada mengekspor toko. Saya membuat contoh di sini:

import React from "react";
import {connect} from "react-redux";
import * as actions from '../actions';

class App extends React.Component {

  handleClick(){
    const data = this.props.someStateObject.data;
    this.props.someDispatchFunction(data);
  }

  render(){
    return (
      <div>       
      <div onClick={ this.handleClick.bind(this)}>Click Me!</div>      
      </div>
    );
  }
}


const mapStateToProps = (state) => {
  return { someStateObject: state.someStateObject };
};

const mapDispatchToProps = (dispatch) => {
  return {
    someDispatchFunction:(data) => { dispatch(actions.someDispatchFunction(data))},

  };
}


export default connect(mapStateToProps, mapDispatchToProps)(App);
Jason Allshorn
sumber
Metode ini cukup logis.
Ahmet Şimşek
Ini cara yang benar untuk melakukannya dan bagaimana saya melakukannya. Pembuat tindakan Anda tidak perlu mengetahui seluruh kondisi, hanya bit yang relevan dengannya.
Ash
3

Saya ingin menunjukkan bahwa tidak terlalu buruk untuk membaca dari toko - mungkin lebih nyaman untuk memutuskan apa yang harus dilakukan berdasarkan pada toko, daripada menyerahkan semuanya ke komponen dan kemudian sebagai parameter dari sebuah fungsi. Saya setuju dengan Dan sepenuhnya, bahwa jauh lebih baik untuk tidak menggunakan store sebagai singletone, kecuali jika Anda 100% yakin bahwa Anda hanya akan menggunakan rendering di sisi klien (jika tidak, bug yang sulit dilacak akan muncul).

Saya telah membuat perpustakaan baru-baru ini untuk menangani verbositas redux, dan saya pikir itu ide yang baik untuk meletakkan segala sesuatu di middleware, sehingga Anda memiliki semuanya sebagai suntikan ketergantungan.

Jadi, contoh Anda akan terlihat seperti itu:

import { createSyncTile } from 'redux-tiles';

const someTile = createSyncTile({
  type: ['some', 'tile'],
  fn: ({ params, selectors, getState }) => {
    return {
      data: params.data,
      items: selectors.another.tile(getState())
    };
  },
});

Namun, seperti yang Anda lihat, kami tidak benar-benar mengubah data di sini, jadi ada peluang bagus bahwa kami bisa menggunakan pemilih ini di tempat lain untuk menggabungkannya di tempat lain.

Bloomca
sumber
1

Menghadirkan cara alternatif untuk menyelesaikan ini. Ini mungkin lebih baik atau lebih buruk daripada solusi Dan, tergantung pada aplikasi Anda.

Anda bisa memasukkan status dari reduksi ke tindakan dengan membagi aksi dalam 2 fungsi terpisah: pertama meminta data, tindakan kedua pada data. Anda dapat melakukannya dengan menggunakan redux-loop.

Pertama, silakan minta datanya.

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
    return {
        type: SOME_ACTION,
    }
}

Di reducer, potong tanda tanya dan berikan data ke tindakan tahap kedua dengan menggunakan redux-loop.

import { loop, Cmd } from 'redux-loop';
const initialState = { data: '' }
export default (state=initialState, action) => {
    switch(action.type) {
        case SOME_ACTION: {
            return loop(state, Cmd.action(anotherAction(state.data))
        }
    }
}

Dengan data di tangan, lakukan apa pun yang awalnya Anda inginkan

export const ANOTHER_ACTION = 'ANOTHER_ACTION';
export function anotherAction(data) {
    return {
        type: ANOTHER_ACTION,
        payload: data,
    }
}

Semoga ini bisa membantu seseorang.

Andrei Cioara
sumber
0

Saya tahu saya terlambat ke pesta di sini, tetapi saya datang ke sini untuk pendapat tentang keinginan saya sendiri untuk menggunakan negara dalam tindakan, dan kemudian membentuk saya sendiri, ketika saya menyadari apa yang saya pikir adalah perilaku yang benar.

Di sinilah pemilih paling masuk akal bagi saya. Komponen Anda yang mengeluarkan permintaan ini harus diberi tahu apakah saatnya untuk mengeluarkannya melalui seleksi.

export const SOME_ACTION = 'SOME_ACTION';
export function someAction(items) {
  return (dispatch) => {
    dispatch(anotherAction(items));
  }
}

Mungkin terasa seperti abstraksi yang bocor, tetapi komponen Anda jelas perlu mengirim pesan dan muatan pesan harus berisi status terkait. Sayangnya pertanyaan Anda tidak memiliki contoh konkret karena kami dapat bekerja melalui 'model yang lebih baik' dari penyeleksi dan tindakan seperti itu.

SqueeDee
sumber
0

Saya ingin menyarankan alternatif lain yang saya temukan terbersih, tetapi membutuhkan react-reduxatau sesuatu yang simular - juga saya menggunakan beberapa fitur mewah lainnya di sepanjang jalan:

// actions.js
export const someAction = (items) => ({
    type: 'SOME_ACTION',
    payload: {items},
});
// Component.jsx
import {connect} from "react-redux";

const Component = ({boundSomeAction}) => (<div
    onClick={boundSomeAction}
/>);

const mapState = ({otherReducer: {items}}) => ({
    items,
});

const mapDispatch = (dispatch) => bindActionCreators({
    someAction,
}, dispatch);

const mergeProps = (mappedState, mappedDispatches) => {
    // you can only use what gets returned here, so you dont have access to `items` and 
    // `someAction` anymore
    return {
        boundSomeAction: () => mappedDispatches.someAction(mappedState.items),
    }
});

export const ConnectedComponent = connect(mapState, mapDispatch, mergeProps)(Component);
// (with  other mapped state or dispatches) Component.jsx
import {connect} from "react-redux";

const Component = ({boundSomeAction, otherAction, otherMappedState}) => (<div
    onClick={boundSomeAction}
    onSomeOtherEvent={otherAction}
>
    {JSON.stringify(otherMappedState)}
</div>);

const mapState = ({otherReducer: {items}, otherMappedState}) => ({
    items,
    otherMappedState,
});

const mapDispatch = (dispatch) => bindActionCreators({
    someAction,
    otherAction,
}, dispatch);

const mergeProps = (mappedState, mappedDispatches) => {
    const {items, ...remainingMappedState} = mappedState;
    const {someAction, ...remainingMappedDispatch} = mappedDispatch;
    // you can only use what gets returned here, so you dont have access to `items` and 
    // `someAction` anymore
    return {
        boundSomeAction: () => someAction(items),
        ...remainingMappedState,
        ...remainingMappedDispatch,
    }
});

export const ConnectedComponent = connect(mapState, mapDispatch, mergeProps)(Component);

Jika Anda ingin menggunakan kembali ini, Anda harus mengekstrak spesifik mapState, mapDispatchdan mergePropske dalam fungsi untuk menggunakan kembali di tempat lain, tetapi ini membuat ketergantungan sangat jelas.

Sam96
sumber