Was ist der beste Weg, um außerhalb einer Reaktionskomponente auf den Redux-Speicher zuzugreifen?

191

@connectfunktioniert hervorragend, wenn ich versuche, innerhalb einer Reaktionskomponente auf den Speicher zuzugreifen. Aber wie soll ich in einem anderen Code darauf zugreifen? Zum Beispiel: Angenommen, ich möchte ein Autorisierungstoken zum Erstellen meiner Axios-Instanz verwenden, das global in meiner App verwendet werden kann. Was wäre der beste Weg, um dies zu erreichen?

Das ist mein 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

Jetzt möchte ich von meinem Geschäft aus auf einen Datenpunkt zugreifen. So würde das aussehen, wenn ich versuchen würde, ihn innerhalb einer Reaktionskomponente mit abzurufen @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() {...}
}

Gibt es da draußen Einblicke oder Workflow-Muster?

Subodh Pareek
quelle
Sie möchten nicht, dass Ihre Axios-Instanz in einer Redux-Middleware lebt? Es wird für alle Ihre Anwendungen auf diese Weise verfügbar sein
Emrys Myrooin
Sie können die apiIn- AppKlasse importieren und nach Erhalt des Autorisierungstokens können Sie dies tun api.defaults.headers.common['Authorization'] = this.props.auth.tokens.authorization_token;. Gleichzeitig können Sie es auch in localStorage speichern. Wenn der Benutzer die Seite aktualisiert, können Sie überprüfen, ob das Token in localStorage vorhanden ist und ob es vorhanden ist Wenn Sie dies tun, können Sie es einstellen. Ich denke, es ist am besten, das Token auf dem API-Modul festzulegen, sobald Sie es erhalten.
Dhruv Kumar Jha
1
Dan Abromov bietet hier ein Beispiel in der
Problemwarteschlange
1
Wenn Sie nur von einem bestimmten Reduzierer auf einen bestimmten Status zugreifen müssen, können Sie versuchen, mit Reduzierungen mit reduzierten Namen von überall aus auf den neuesten Status zuzugreifen.
Meilen_christian

Antworten:

146

Exportieren Sie den Store aus dem Modul, createStoremit dem Sie angerufen haben . Dann können Sie sicher sein, dass beide erstellt werden und den globalen Fensterbereich nicht verschmutzen.

MyStore.js

const store = createStore(myReducer);
export store;

oder

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

MyClient.js

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

oder wenn Sie Standard verwendet haben

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

Für Anwendungsfälle mit mehreren Filialen

Wenn Sie mehrere Instanzen eines Geschäfts benötigen, exportieren Sie eine Factory-Funktion. Ich würde empfehlen, es zu machen async(Rückgabe a promise).

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

Auf dem Client (in einem asyncBlock)

import {getUserStore} from './store'

const joeStore = await getUserStore('joe')
Steven Spungin
quelle
47
WARNUNG für isomorphe Apps: Wenn Sie dies serverseitig tun, wird der Redux-Speicher für alle Benutzer freigegeben !!!
Lawrence Wagerfield
6
Die Frage gibt auch nicht explizit "Browser" an. Da sowohl Redux als auch JavaScript entweder auf dem Server oder im Browser verwendet werden können, ist es viel sicherer, genau zu sein.
Lawrence Wagerfield
7
Das Exportieren von Store scheint einen Albtraum von zyklischen Importen zu verursachen. CreateStore enthält Ihre Reduzierungen, für die Ihre Aktionen erforderlich sind (mindestens die Aktionsaufzählung enum und Aktionsschnittstellen), die nichts importieren dürfen, das versucht, Store zu importieren. Sie dürfen also weder in Ihren Reduzierern noch in Ihren Aktionen Speicher verwenden (oder sich anders um den zyklischen Import
streiten
6
Das ist die richtige Antwort, aber wenn man (wie ich) anstelle der Versendung eine Aktion im Speicher lesen möchten, müssen Sie Anrufstore.getState()
Juan José Ramírez
3
Nun, meine Interpretation zu "Access Redux Store" war "Lesen" des Stores. Ich versuche nur anderen zu helfen.
Juan José Ramírez
47

Eine Lösung gefunden. Also importiere ich den Store in mein API Util und abonniere ihn dort. Und in dieser Listener-Funktion habe ich die globalen Standardeinstellungen der Axios mit meinem neu abgerufenen Token festgelegt.

So api.jssieht mein neues aus:

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

Vielleicht kann es weiter verbessert werden, weil es derzeit etwas unelegant erscheint. Was ich später tun könnte, ist, meinem Shop eine Middleware hinzuzufügen und das Token dann und dort festzulegen.

Subodh Pareek
quelle
1
Können Sie uns mitteilen, wie Ihre store.jsDatei aussieht?
Vic
genau etwas, wonach ich gesucht habe, danke eine Tonne @subodh
Harkirat Saluja
1
Ich weiß, dass die Frage alt ist, aber Sie können Ihre eigene Antwort als richtig akzeptieren. Dies erleichtert es Ihnen, Ihre Antwort für andere zu finden, die möglicherweise hierher kommen.
Filipe Merker
3
Ich habe "TypeError: WEBPACK_IMPORTED_MODULE_2__store_js .b ist undefiniert" erhalten, wenn ich versuche, auf den Speicher außerhalb einer Komponente oder Funktion zuzugreifen. Irgendeine Hilfe, warum das so ist?
Ben
21

Sie können ein storeObjekt verwenden, das von der createStoreFunktion zurückgegeben wird (das bereits in Ihrem Code bei der App-Initialisierung verwendet werden sollte). Dann können Sie dieses Objekt verwenden, um den aktuellen Status mit der store.getState()Methode abzurufen oder store.subscribe(listener)um Speicheraktualisierungen zu abonnieren.

Sie können dieses Objekt sogar in einer windowEigenschaft speichern, um von einem beliebigen Teil der Anwendung aus darauf zuzugreifen, wenn Sie es wirklich möchten ( window.store = store)

Weitere Informationen finden Sie in der Redux-Dokumentation .

Müllgenerator
quelle
19
klingt ein bisschen hacky zu speichern, storeumwindow
Vic
3
@Vic Sicher ist es :) Und normalerweise willst du es nicht tun. Ich wollte nur erwähnen, dass Sie mit dieser Variablen machen können, was Sie wollen. Wahrscheinlich ist die beste Idee, es in einer Datei zu speichern, in der Sie Ihr "createStore" -Leben erstellt haben, und es dann einfach daraus zu importieren.
Müllgenerator
Ich habe mehrere Iframes, die Zugriff auf den Status der anderen Iframes benötigen. Ich weiß, dass es ein bisschen hackig ist, aber ich denke, dass es besser wäre, den Laden am Fenster zu haben, als Nachrichten an Iframes zu verwenden. Irgendwelche Gedanken? @Vic @trashgenerator?
Sean Malter
9

Wie bei @sanchit ist die vorgeschlagene Middleware eine gute Lösung, wenn Sie Ihre Axios-Instanz bereits global definieren.

Sie können eine Middleware erstellen wie:

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;

Und benutze es so:

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

Das Token wird für jede Aktion festgelegt, Sie können jedoch nur auf Aktionen warten, die beispielsweise das Token ändern.

erksch
quelle
3

Für TypeScript 2.0 würde es so aussehen:

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
quelle
3
Wenn Sie abstimmen, fügen Sie bitte einen Kommentar hinzu, warum.
Ogglas
3
Abgestimmt, weil Ihre Antwort keine Erklärung enthält und TypeScript anstelle von Javascript verwendet.
Will
2
@ Will Danke, dass du gesagt hast warum. Imao erfordert der Code keine Spezifikation, aber wenn Sie etwas Spezifisches erklären möchten, sagen Sie bitte was. TypeScript wird zwar verwendet, aber wenn die Typisierungen entfernt werden, wird derselbe Code problemlos in ES6 ausgeführt.
Ogglas
Denken Sie daran, dass dies sehr schlecht ist, wenn Sie das Rendern auf mehreren Seiten durchführen. Es teilt den Status mit allen Anforderungen.
Rob Evans
2

Es mag etwas spät sein, aber ich denke, der beste Weg ist, axios.interceptorswie unten zu verwenden. Die Import-URLs können sich je nach Projekt-Setup ändern.

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
quelle
1

Mach es mit Haken. Ich hatte ein ähnliches Problem, aber ich habe React-Redux mit Hooks verwendet. Ich wollte meinen Schnittstellencode (dh Reaktionskomponenten) nicht mit viel Code versehen, der zum Abrufen / Senden von Informationen aus / zum Geschäft bestimmt ist. Vielmehr wollte ich Funktionen mit generischen Namen, um die Daten abzurufen und zu aktualisieren. Mein Weg war es, die App zu setzen

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

in ein Modul mit dem Namen store.jsund Hinzufügen exportvor constund Hinzufügen der üblichen React-Redux-Importe in der store.js. Datei. Dann habe ich index.jsauf App-Ebene importiert, die ich dann mit den üblichen untergeordneten import {store} from "./store.js"Komponenten in index.js importiert habe. Die untergeordneten Komponenten haben dann mit den Hooks useSelector()und auf den Store zugegriffen useDispatch().

Um auf den Store im Nicht-Komponenten-Front-End-Code zuzugreifen, habe ich den analogen Import (dh import {store} from "../../store.js") verwendet und dann das Abrufen und Aktualisieren (äh, Senden von Aktionen an den Store ) verwendet store.getState()und store.dispatch({*action goes here*})behandelt.

Charles Goodwin
quelle
0

Eine einfache Möglichkeit, auf das Token zuzugreifen, besteht darin, das Token mit React Native in den LocalStorage oder den AsyncStorage zu legen.

Unten ein Beispiel mit einem React Native- Projekt

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;
  }
};
...

und 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 };

Für das Web können Sie Window.localStorageanstelle von AsyncStorage verwenden

Denis Zheng
quelle