Каков наилучший способ получить доступ к хранилищу резервов вне реагирующего компонента?

192

@connectпрекрасно работает, когда я пытаюсь получить доступ к магазину в компоненте реакции. Но как мне получить к нему доступ в другом фрагменте кода? Например, скажем, я хочу использовать токен авторизации для создания моего экземпляра axios, который можно использовать в моем приложении в глобальном масштабе. Каков наилучший способ добиться этого?

Это мое 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

Теперь я хочу получить доступ к точке данных из своего магазина, вот как это выглядело бы, если бы я пытался извлечь ее из компонента реакции, используя @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() {...}
}

Есть идеи или шаблоны рабочего процесса?

Субод Парик
источник
Вы не хотите, чтобы ваш экземпляр Axios жил в избыточном промежуточном программном обеспечении? Это будет доступно всем вашим приложениям таким образом
Emrys Myrooin
Вы можете импортировать класс apiin Appи после получения токена авторизации вы можете это сделать api.defaults.headers.common['Authorization'] = this.props.auth.tokens.authorization_token;, и в то же время вы можете сохранить его и в localStorage, поэтому, когда пользователь обновляет страницу, вы можете проверить, существует ли токен в localStorage, и если он Да, вы можете установить его. Я думаю, что будет лучше установить токен на модуль API, как только вы получите его.
Дхрув Кумар Джа
1
Дан Абромов приводит пример в очереди вопросов здесь: github.com/reactjs/redux/issues/916
chrisjlee
1
Если вам просто нужно получить доступ к определенному состоянию из определенного редуктора, вы можете попробовать с помощью redux-named-redurs, это позволит вам получить доступ к последнему состоянию из любого места.
миль_христиан

Ответы:

146

Экспортируйте магазин из модуля, с которым вы звонили createStore. Тогда вы уверены, что он будет создан и не будет загрязнять пространство глобального окна.

MyStore.js

const store = createStore(myReducer);
export store;

или

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

MyClient.js

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

или если вы использовали по умолчанию

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

Для нескольких случаев использования магазина

Если вам нужно несколько экземпляров магазина, экспортируйте фабричную функцию. Я бы порекомендовал сделать это async(возвращая promise).

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

На клиенте (в asyncблоке)

import {getUserStore} from './store'

const joeStore = await getUserStore('joe')
Стивен Спунгин
источник
47
ПРЕДУПРЕЖДЕНИЕ для изоморфных приложений: выполнение этой серверной части разделит хранилище редуксов среди всех ваших пользователей !!!
Лоуренс Вагерфилд
6
Вопрос также явно не заявляет «браузер». Поскольку Redux и JavaScript можно использовать как на сервере, так и в браузере, гораздо безопаснее быть конкретным.
Лоуренс Вагерфилд
7
экспорт магазина, похоже, создает кошмарный цикл циклического импорта, createStore включает ваши редукторы, которые требуют ваших действий (по крайней мере, тип действия enum и интерфейсы действий), которые не должны импортировать все, что пытается импортировать хранилище. Таким образом, вы не должны использовать хранилище ни в своих редукторах, ни в действиях (или спорить по-другому с циклическим импортом.)
Eloff
6
Это правильный ответ, но если вы (как и я) хотите прочитать, а не отправлять действие в магазине, вам нужно позвонитьstore.getState()
Хуан Хосе Рамирес
3
Что ж, моя интерпретация «доступа к избыточному магазину» была «прочитана» в магазине. Просто пытаюсь помочь другим.
Хуан Хосе Рамирес
47

Нашел решение. Поэтому я импортирую магазин в своем утилите api и подписываюсь на него там. И в этой функции слушателя я установил глобальные значения по умолчанию для axios с помощью своего недавно извлеченного токена.

Вот как api.jsвыглядит мой новый :

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

Может быть, это может быть улучшено еще больше, потому что в настоящее время это выглядит немного не элегантно. Что я мог сделать позже, так это добавить промежуточное ПО в мой магазин и установить токен тут же.

Субод Парик
источник
1
можете поделиться как store.jsвыглядит ваш файл?
Вик
именно то, что я искал, спасибо огромное @subodh
Harkirat Saluja
1
Я знаю, что вопрос старый, но вы можете принять свой собственный ответ как правильный. Это облегчает поиск вашего ответа для других, которые могут в конечном итоге прийти сюда.
Филипе Меркер
3
Я получил « Ошибка типа: WEBPACK_IMPORTED_MODULE_2__store_js .b не определена», когда я пытаюсь получить доступ к хранилищу вне компонента или функции. Любая помощь, почему это?
Бен
21

Вы можете использовать storeобъект, возвращаемый createStoreфункцией (который уже должен использоваться в вашем коде при инициализации приложения). Чем вы можете использовать этот объект, чтобы получить текущее состояние с помощью store.getState()метода или store.subscribe(listener)подписаться на обновления магазина.

Вы даже можете сохранить этот объект в windowсвойстве, чтобы получить к нему доступ из любой части приложения, если вы действительно этого хотите ( window.store = store)

Более подробную информацию можно найти в документации Redux .

trashgenerator
источник
19
звучит немного хакерски, чтобы спасти storeдляwindow
Вик
3
@Vic Конечно, это так :) И обычно вы не хотите этого делать. Я просто хотел упомянуть, что с этой переменной вы можете делать все, что захотите. Вероятно, лучшая идея будет сохранить его в файле, в котором вы создали свою жизнь «createStore», а затем просто импортировать из него.
trashgenerator
У меня есть несколько фреймов, которым нужен доступ к состоянию других фреймов. Я знаю, что это немного странно, но я думаю, что лучше хранить хранилище в окне, чем использовать сообщения для фреймов. Есть предположения?? @Vic @trashgenerator?
Шон
10

Как и @sanchit, предлагаемое промежуточное ПО будет хорошим решением, если вы уже определяете свой экземпляр axios глобально.

Вы можете создать промежуточное ПО, например:

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;

И используйте это так:

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

Он будет устанавливать токен на каждое действие, но вы можете прослушивать только действия, которые меняют токен, например.

erksch
источник
Как вы можете добиться того же с помощью специального экземпляра Axios?
Анатолий
3

Для TypeScript 2.0 это будет выглядеть так:

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
источник
3
Если вы отказываетесь, добавьте комментарий, почему.
Огглас
3
Отказался, потому что в вашем ответе нет объяснения и используется TypeScript, а не Javascript.
Будет ли
2
@ Буду благодарен, что сказал почему. Imao код не требует спецификации, но если вы хотите что-то конкретное объясненное, пожалуйста, скажите что. TypeScript действительно используется, но если убрать набор текста, тот же код будет запущен в ES6 без проблем.
Ogglas
Имейте в виду, что это будет очень плохо, если вы выполняете серверный рендеринг, он будет делить состояние со всеми запросами.
Роб Эванс
2

Это может быть немного поздно, но я думаю, что лучше всего использовать, axios.interceptorsкак показано ниже. URL-адреса импорта могут меняться в зависимости от настроек вашего проекта.

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)
    );
}
С. Мерт
источник
1

Делаем это с помощью крючков. Я столкнулся с подобной проблемой, но я использовал реактив-редукс с крючками. Я не хотел загружать свой код интерфейса (т. Е. Реагировать на компоненты) большим количеством кода, предназначенного для извлечения / отправки информации из / в магазин. Скорее я хотел функции с родовыми именами для извлечения и обновления данных. Мой путь должен был поставить приложение

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

в модуль с именем store.jsи добавлением exportдо constи добавлением обычного импортаact-redux в store.js. файл. Затем я импортировал в index.jsна уровне приложений, которые я затем импортирован в index.js с обычным import {store} from "./store.js"Дочерние компоненты затем доступ в магазин , используя useSelector()и useDispatch()крючки.

Чтобы получить доступ к хранилищу в некомпонентном коде переднего плана, я использовал аналогичный импорт (т. Е. import {store} from "../../store.js"), А затем использовал store.getState()и store.dispatch({*action goes here*})для обработки поиска и обновления (т. Е. Отправки действий) хранилища.

Чарльз Гудвин
источник
0

Простой способ получить доступ к токену - это поместить токен в LocalStorage или AsyncStorage с React Native.

Ниже приведен пример с проектом 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;
  }
};
...

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

Для веб вы можете использовать Window.localStorageвместо AsyncStorage

Денис Чжэн
источник