Apa cara terbaik untuk mengakses toko redux di luar komponen reaksi?

192

@connectberfungsi dengan baik ketika saya mencoba mengakses toko dalam komponen reaksi. Tetapi bagaimana saya harus mengaksesnya dalam beberapa kode lainnya. Sebagai contoh: katakanlah saya ingin menggunakan token otorisasi untuk membuat contoh aksioma saya yang dapat digunakan secara global di aplikasi saya, apa cara terbaik untuk mencapainya?

Ini milik saya api.js

// tooling modules
import axios from 'axios'

// configuration
const api = axios.create()
api.defaults.baseURL = 'http://localhost:5001/api/v1'
api.defaults.headers.common['Authorization'] = 'AUTH_TOKEN' // need the token here
api.defaults.headers.post['Content-Type'] = 'application/json'

export default api

Sekarang saya ingin mengakses titik data dari toko saya, di sini akan terlihat seperti apa jika saya mencoba mengambilnya dalam komponen reaksi menggunakan @connect

// connect to store
@connect((store) => {
  return {
    auth: store.auth
  }
})
export default class App extends Component {
  componentWillMount() {
    // this is how I would get it in my react component
    console.log(this.props.auth.tokens.authorization_token) 
  }
  render() {...}
}

Adakah wawasan atau pola alur kerja di luar sana?

Subodh Pareek
sumber
Anda tidak ingin Anda contoh Axios hidup di muxleware redux? Ini akan tersedia oleh semua aplikasi Anda dengan cara ini
Emrys Myrooin
Anda dapat mengimpor apidalam Appkelas dan setelah mendapatkan token otorisasi yang dapat Anda lakukan api.defaults.headers.common['Authorization'] = this.props.auth.tokens.authorization_token;, dan pada saat yang sama Anda dapat menyimpannya di localStorage juga, jadi ketika pengguna me-refresh halaman, Anda dapat memeriksa apakah token ada di localStorage dan jika tidak, Anda dapat mengaturnya., Saya pikir akan lebih baik untuk mengatur token pada modul api segera setelah Anda mendapatkannya.
Dhruv Kumar Jha
1
Dan Abromov memberikan contoh dalam masalah antrian di sini: github.com/reactjs/redux/issues/916
chrisjlee
1
Jika Anda hanya perlu mengakses negara tertentu dari reducer tertentu, Anda dapat mencoba dengan redux-bernama-reduksi yang memungkinkan Anda mengakses negara terbaru dari mana saja.
miles_christian

Jawaban:

146

Ekspor toko dari modul yang Anda panggil createStore. Maka Anda yakin itu akan dibuat dan tidak akan mencemari ruang jendela global.

MyStore.js

const store = createStore(myReducer);
export store;

atau

const store = createStore(myReducer);
export default store;

MyClient.js

import {store} from './MyStore'
store.dispatch(...)

atau jika Anda menggunakan default

import store from './MyStore'
store.dispatch(...)

Untuk Berbagai Kasus Penggunaan Toko

Jika Anda membutuhkan beberapa instance toko, ekspor fungsi pabrik. Saya akan merekomendasikan membuatnya async(mengembalikan a promise).

async function getUserStore (userId) {
   // check if user store exists and return or create it.
}
export getUserStore

Di klien (di asyncblok)

import {getUserStore} from './store'

const joeStore = await getUserStore('joe')
Steven Spungin
sumber
47
PERINGATAN untuk aplikasi isomorfik: melakukan sisi server ini akan membagikan toko redux di semua pengguna Anda !!!
Lawrence Wagerfield
6
Pertanyaannya juga tidak secara eksplisit menyatakan "browser". Karena Redux dan JavaScript dapat digunakan pada server atau browser, itu jauh lebih aman untuk lebih spesifik.
Lawrence Wagerfield
7
toko ekspor tampaknya menciptakan mimpi buruk impor siklik, createStore menyertakan pengurang Anda, yang memerlukan tindakan Anda (setidaknya jenis tindakan enum, dan antarmuka Tindakan), yang tidak boleh mengimpor apa pun yang mencoba mengimpor toko. Jadi, Anda tidak boleh menggunakan toko dalam reduksi atau tindakan Anda (atau bertengkar dengan cara lain terkait impor siklik.)
Eloff
6
Ini adalah jawaban yang benar, tetapi jika Anda (seperti saya) ingin membaca alih-alih mengirimkan tindakan di toko, Anda perlu meneleponstore.getState()
Juan José Ramírez
3
Nah, interpretasi saya tentang "akses toko redux" adalah "membaca" toko. Hanya berusaha membantu orang lain.
Juan José Ramírez
47

Menemukan solusi. Jadi saya mengimpor toko di util api saya dan berlangganan di sana. Dan dalam fungsi pendengar itu saya menetapkan default global aksioma dengan token yang baru saya ambil.

Seperti inilah api.jspenampilan baru saya :

// tooling modules
import axios from 'axios'

// store
import store from '../store'
store.subscribe(listener)

function select(state) {
  return state.auth.tokens.authentication_token
}

function listener() {
  let token = select(store.getState())
  axios.defaults.headers.common['Authorization'] = token;
}

// configuration
const api = axios.create({
  baseURL: 'http://localhost:5001/api/v1',
  headers: {
    'Content-Type': 'application/json',
  }
})

export default api

Mungkin bisa lebih ditingkatkan lagi, karena saat ini sepertinya agak tidak elok. Yang bisa saya lakukan nanti adalah menambahkan middleware ke toko saya dan mengatur token di sana-sini.

Subodh Pareek
sumber
1
dapatkah Anda membagikan seperti apa store.jsfile Anda ?
Vic
persis sesuatu yang saya cari, terima kasih banyak @subodh
Harkirat Saluja
1
Saya tahu pertanyaannya sudah lama, tetapi Anda dapat menerima jawaban Anda sendiri sebagai yang benar. Ini membuat jawaban Anda lebih mudah ditemukan bagi orang lain yang mungkin akhirnya datang ke sini.
Filipe Merker
3
Saya mendapat "TypeError: WEBPACK_IMPORTED_MODULE_2__store_js .b tidak terdefinisi" ketika saya mencoba mengakses toko di luar komponen atau fungsi. Adakah yang bisa membantu mengapa demikian?
ben
21

Anda dapat menggunakan storeobjek yang dikembalikan dari createStorefungsi (yang seharusnya sudah digunakan dalam kode Anda dalam inisialisasi aplikasi). Daripada Anda dapat menggunakan objek ini untuk mendapatkan keadaan saat ini dengan store.getState()metode atau store.subscribe(listener)untuk berlangganan pembaruan toko.

Anda bahkan dapat menyimpan objek ini ke windowproperti untuk mengaksesnya dari bagian mana pun dari aplikasi jika Anda benar-benar menginginkannya ( window.store = store)

Info lebih lanjut dapat ditemukan di dokumentasi Redux .

trashgenerator
sumber
19
Kedengarannya agak gila untuk menyimpan storekewindow
Vic
3
@Vic Tentu itu :) Dan biasanya Anda tidak ingin melakukannya. Saya hanya ingin menyebutkan bahwa Anda dapat melakukan apa pun yang Anda inginkan dengan variabel ini. Mungkin ide terbaik adalah menyimpannya di file tempat Anda membuat "createStore" Anda dan kemudian hanya mengimpor dari situ.
trashgenerator
Saya memiliki beberapa iframe yang memerlukan akses ke status iframe lainnya. Saya tahu ini agak berantakan tapi saya pikir memiliki toko di jendela akan lebih baik daripada menggunakan pesan ke iframe. Ada pemikiran ?? @Vic @trashgenerator?
Sean Malter
10

Seperti @sanchit yang diusulkan middleware adalah solusi yang bagus jika Anda sudah mendefinisikan instance aksioma Anda secara global.

Anda dapat membuat middleware seperti:

function createAxiosAuthMiddleware() {
  return ({ getState }) => next => (action) => {
    const { token } = getState().authentication;
    global.axios.defaults.headers.common.Authorization = token ? `Bearer ${token}` : null;

    return next(action);
  };
}

const axiosAuth = createAxiosAuthMiddleware();

export default axiosAuth;

Dan gunakan seperti ini:

import { createStore, applyMiddleware } from 'redux';
const store = createStore(reducer, applyMiddleware(axiosAuth))

Ini akan mengatur token pada setiap tindakan tetapi Anda hanya bisa mendengarkan tindakan yang mengubah token misalnya.

erksch
sumber
bagaimana Anda bisa mencapai hal yang sama dengan contoh aksioma khusus?
Anatol
3

Untuk TypeScript 2.0 akan terlihat seperti ini:

MyStore.ts

export namespace Store {

    export type Login = { isLoggedIn: boolean }

    export type All = {
        login: Login
    }
}

import { reducers } from '../Reducers'
import * as Redux from 'redux'

const reduxStore: Redux.Store<Store.All> = Redux.createStore(reducers)

export default reduxStore;

MyClient.tsx

import reduxStore from "../Store";
{reduxStore.dispatch(...)}
Ogglas
sumber
3
Jika Anda memilih untuk turun, tolong tambahkan komentar mengapa.
Ogglas
3
Turut memilih karena jawaban Anda kurang penjelasan dan menggunakan TypeScript daripada Javascript.
Will
2
@Apakah Terima kasih telah mengatakan alasannya. Imao kode tidak memerlukan spesifikasi tetapi jika Anda ingin sesuatu yang spesifik dijelaskan tolong katakan apa. TypeScript memang digunakan tetapi jika pengetikan dihapus kode yang sama akan berjalan di ES6 tanpa masalah.
Ogglas
Perlu diingat ini akan sangat buruk jika Anda melakukan render side-rendering, ia akan berbagi keadaan dengan semua permintaan.
Rob Evans
2

Mungkin agak terlambat tetapi saya pikir cara terbaik adalah menggunakan axios.interceptorsseperti di bawah ini. Impor url dapat berubah berdasarkan pengaturan proyek Anda.

index.js

import axios from 'axios';
import setupAxios from './redux/setupAxios';
import store from './redux/store';

// some other codes

setupAxios(axios, store);

setupAxios.js

export default function setupAxios(axios, store) {
    axios.interceptors.request.use(
        (config) => {
            const {
                auth: { tokens: { authorization_token } },
            } = store.getState();

            if (authorization_token) {
                config.headers.Authorization = `Bearer ${authorization_token}`;
            }

            return config;
        },
       (err) => Promise.reject(err)
    );
}
S. Mert
sumber
1

Melakukannya dengan kait. Saya mengalami masalah yang sama, tetapi saya menggunakan react-redux with hooks. Saya tidak ingin menambahkan kode antarmuka saya (yaitu, komponen reaksi) dengan banyak kode yang didedikasikan untuk mengambil / mengirim informasi dari / ke toko. Sebaliknya, saya ingin fungsi dengan nama generik untuk mengambil dan memperbarui data. Jalan saya adalah menempatkan aplikasi

const store = createSore(
   allReducers,
   window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
 );

ke dalam modul bernama store.jsdan menambahkan exportsebelumnya constdan menambahkan impor reak-reduks yang biasa di store.js. mengajukan. Kemudian, saya mengimpor ke index.jstingkat aplikasi, yang kemudian saya impor ke index.js dengan import {store} from "./store.js"komponen anak biasa kemudian mengakses toko menggunakan useSelector()dan useDispatch()kait.

Untuk mengakses toko dalam kode ujung depan non-komponen, saya menggunakan impor analog (yaitu, import {store} from "../../store.js") dan kemudian digunakan store.getState()dan store.dispatch({*action goes here*})untuk menangani pengambilan dan pembaruan (eh, mengirim tindakan ke) toko.

Charles Goodwin
sumber
0

Cara mudah untuk memiliki akses ke token, adalah dengan meletakkan token di LocalStorage atau AsyncStorage dengan React Native.

Di bawah ini adalah contoh dengan proyek React Native

authReducer.js

import { AsyncStorage } from 'react-native';
...
const auth = (state = initialState, action) => {
  switch (action.type) {
    case SUCCESS_LOGIN:
      AsyncStorage.setItem('token', action.payload.token);
      return {
        ...state,
        ...action.payload,
      };
    case REQUEST_LOGOUT:
      AsyncStorage.removeItem('token');
      return {};
    default:
      return state;
  }
};
...

dan api.js

import axios from 'axios';
import { AsyncStorage } from 'react-native';

const defaultHeaders = {
  'Content-Type': 'application/json',
};

const config = {
  ...
};

const request = axios.create(config);

const protectedRequest = options => {
  return AsyncStorage.getItem('token').then(token => {
    if (token) {
      return request({
        headers: {
          ...defaultHeaders,
          Authorization: `Bearer ${token}`,
        },
        ...options,
      });
    }
    return new Error('NO_TOKEN_SET');
  });
};

export { request, protectedRequest };

Untuk web, Anda bisa menggunakan Window.localStorageAsyncStorage

Denis Zheng
sumber