Jaki jest najlepszy sposób na dostęp do sklepu redux poza komponentem reagującym?

192

@connectdziała świetnie, gdy próbuję uzyskać dostęp do sklepu w komponencie reagowania. Ale jak powinienem uzyskać do niego dostęp w innym kodzie kodu. Na przykład: powiedzmy, że chcę użyć tokena autoryzacyjnego do utworzenia mojej instancji axios, która może być używana globalnie w mojej aplikacji, jaki byłby najlepszy sposób na osiągnięcie tego?

To jest mój 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

Teraz chcę uzyskać dostęp do punktu danych z mojego sklepu, oto jak by to wyglądało, gdybym próbował pobrać go w komponencie reagującym za pomocą @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() {...}
}

Jakieś spostrzeżenia lub wzorce przepływu pracy?

Subodh Pareek
źródło
Nie chcesz, aby Twoja instancja Axios żyła w redux middleware? Będzie w ten sposób dostępny dla wszystkich aplikacji
Emrys Myrooin
Można importować apiw Appklasie i po uzyskaniu zezwolenia żeton można zrobić api.defaults.headers.common['Authorization'] = this.props.auth.tokens.authorization_token;, a jednocześnie można przechowywać go w localStorage, tak więc gdy odświeża użytkowników na stronie, można sprawdzić, czy token istnieje w localStorage i jeśli to tak, możesz to ustawić. Myślę, że najlepiej będzie ustawić token na module API, jak tylko go dostaniesz.
Dhruv Kumar Jha,
1
Dan Abromov podaje przykład w kolejce problemów tutaj: github.com/reactjs/redux/issues/916
chrisjlee
1
Jeśli potrzebujesz tylko uzyskać dostęp do określonego stanu z określonego reduktora, możesz spróbować z reduktorami nazwanymi reduktorami, co pozwala uzyskać dostęp do najnowszego stanu z dowolnego miejsca.
miles_christian

Odpowiedzi:

146

Wyeksportuj sklep z modułu, z którym zadzwoniłeś createStore. Wtedy masz pewność, że zostanie on utworzony i nie będzie zanieczyszczał globalnej przestrzeni okien.

MyStore.js

const store = createStore(myReducer);
export store;

lub

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

MyClient.js

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

lub jeśli użyłeś domyślnego

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

W przypadku użycia w wielu sklepach

Jeśli potrzebujesz wielu instancji sklepu, wyeksportuj funkcję fabryki. Polecam zrobienie tego async(zwrot a promise).

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

Na kliencie (w asyncbloku)

import {getUserStore} from './store'

const joeStore = await getUserStore('joe')
Steven Spungin
źródło
47
OSTRZEŻENIE dla aplikacji izomorficznych: wykonanie tej strony po stronie udostępni sklep redux wszystkim użytkownikom !!!
Lawrence Wagerfield,
6
Pytanie to również nie określa wprost „przeglądarki”. Ponieważ zarówno Redux, jak i JavaScript mogą być używane na serwerze lub w przeglądarce, o wiele bezpieczniej jest być konkretnym.
Lawrence Wagerfield,
7
eksportowanie sklepu wydaje się tworzyć koszmar cyklicznych importów, createStore zawiera twoje reduktory, które wymagają twoich akcji (przynajmniej wyliczenia typu akcji i interfejsów akcji), które nie mogą importować niczego, co próbuje zaimportować sklep. Nie możesz więc używać sklepu w swoich reduktorach lub działaniach (lub sprzeczać się w inny sposób z cyklicznym importem.)
Eloff
6
To poprawna odpowiedź, ale jeśli (podobnie jak ja) chcesz przeczytać zamiast wysłać akcję w sklepie, musisz zadzwonićstore.getState()
Juan José Ramírez,
3
Cóż, moją interpretacją „access redux store” było „odczytanie” sklepu. Po prostu próbuję pomóc innym.
Juan José Ramírez
47

Znalazłem rozwiązanie. Więc importuję sklep w moim interfejsie API i tam subskrybuję. I w tej funkcji detektora ustawiam globalne ustawienia domyślne axios za pomocą mojego nowo pobranego tokena.

Tak api.jswygląda mój nowy :

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

Być może można go jeszcze ulepszyć, ponieważ obecnie wydaje się nieco nieelegancki. Mogę później dodać oprogramowanie pośrednie do mojego sklepu i ustawić token tam i teraz.

Subodh Pareek
źródło
1
czy możesz udostępnić wygląd swojego store.jspliku?
Vic
dokładnie coś, czego szukałem, dzięki ton @ sububh
Harkirat Saluja
1
Wiem, że pytanie jest stare, ale możesz zaakceptować własną odpowiedź jako poprawną. Ułatwia to znalezienie odpowiedzi dla innych, którzy w końcu mogą tu przyjść.
Filipe Merker
3
Dostałem „TypeError: WEBPACK_IMPORTED_MODULE_2__store_js .b jest niezdefiniowany”, gdy próbuję uzyskać dostęp do sklepu poza komponentem lub funkcją. Jakaś pomoc, dlaczego tak jest?
ben
21

Możesz użyć storeobiektu zwróconego z createStorefunkcji (który powinien być już użyty w kodzie podczas inicjalizacji aplikacji). Następnie możesz użyć tego obiektu, aby uzyskać bieżący stan za pomocą store.getState()metody lub store.subscribe(listener)zapisać się, aby przechowywać aktualizacje.

Możesz nawet zapisać ten obiekt we windowwłaściwości, aby uzyskać do niego dostęp z dowolnej części aplikacji, jeśli naprawdę tego chcesz ( window.store = store)

Więcej informacji można znaleźć w dokumentacji Redux .

trashgenerator
źródło
19
Brzmi trochę hacky, aby zapisać storesię dowindow
Vic
3
@Vic Pewnie, że tak :) I zwykle nie chcesz tego robić. Chciałem tylko wspomnieć, że za pomocą tej zmiennej możesz robić, co chcesz. Prawdopodobnie najlepszym pomysłem byłoby przechowanie go w pliku, w którym utworzyłeś swoje życie „createStore”, a następnie po prostu zaimportować z niego.
trashgenerator
Mam wiele elementów iframe, które potrzebują dostępu do stanu innych elementów iframe. Wiem, że to trochę hacky, ale myślę, że sklep w oknie byłby lepszy niż używanie wiadomości do iframe. Jakieś pomysły?? @Vic @trashgenerator?
Sean Malter
10

Podobnie jak @sanchit proponowane oprogramowanie pośrednie jest dobrym rozwiązaniem, jeśli już definiujesz instancję axios na całym świecie.

Możesz utworzyć oprogramowanie pośrednie, takie jak:

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;

I użyj tego w ten sposób:

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

Ustawi token dla każdej akcji, ale możesz nasłuchiwać tylko akcji, które na przykład zmieniają token.

erksch
źródło
jak można to zrobić za pomocą niestandardowej instancji axios?
Anatol
3

W przypadku TypeScript 2.0 wygląda to tak:

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
źródło
3
Jeśli głosujesz, dodaj komentarz dlaczego.
Ogglas,
3
Down głosował, ponieważ twoja odpowiedź nie zawiera wyjaśnień i używa TypeScript zamiast Javascript.
Czy
2
@Will Dziękujemy za powiedzenie dlaczego. Imao kod nie wymaga specyfikacji, ale jeśli chcesz wyjaśnić coś konkretnego, powiedz co. Rzeczywiście używany jest TypeScript, ale jeśli zostaną usunięte te same kody, ten sam kod będzie działał bez problemu w ES6.
Ogglas
Pamiętaj, że będzie to bardzo złe, jeśli wykonujesz renderowanie po stronie serwera, będzie ono współużytkować stan ze wszystkimi żądaniami.
Rob Evans
2

Może być trochę za późno, ale myślę, że najlepszym sposobem jest użycie axios.interceptorsjak poniżej. Importowane adresy URL mogą ulec zmianie w zależności od konfiguracji projektu.

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
źródło
1

Robiąc to za pomocą haczyków. Zetknąłem się z podobnym problemem, ale użyłem React-Redux z hakami. Nie chciałem tworzyć kodu interfejsu (tj. Reagować na komponenty) z dużą ilością kodu przeznaczonego do pobierania / wysyłania informacji z / do sklepu. Zamiast tego chciałem, aby funkcje o nazwach ogólnych pobierały i aktualizowały dane. Moją ścieżką było umieszczenie aplikacji

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

do modułu o nazwie store.jsi dodawania exportprzed consti dodawania zwykłych importów React-Redux w store.js. plik. Następnie zaimportowałem do index.jsna poziomie aplikacji, którą następnie zaimportowałem do index.js zwykłymi import {store} from "./store.js"komponentami podrzędnymi, a następnie uzyskałem dostęp do sklepu za pomocąuseSelector()useDispatch() haków i .

Aby uzyskać dostęp do sklepu w niekomponentowym kodzie frontonu, użyłem analogicznego importu (tj. import {store} from "../../store.js"), A następnie użyłem store.getState()i store.dispatch({*action goes here*})obsłużyłem pobieranie i aktualizację (er, wysyłanie akcji do) sklepu.

Charles Goodwin
źródło
0

Prostym sposobem na uzyskanie dostępu do tokena jest umieszczenie go w LocalStorage lub AsyncStorage z React Native.

Poniżej przykład z projektem 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;
  }
};
...

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

W przypadku sieci można użyć Window.localStoragezamiast AsyncStorage

Denis Zheng
źródło