在React组件之外访问Redux存储的最佳方法是什么?


191

@connect当我尝试在React组件中访问商店时,它的工作效果很好。但是我应该如何通过其他一些代码来访问它。例如:假设我想使用授权令牌创建可以在我的应用程序中全局使用的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

现在我想从我的商店访问一个数据点,这是如果我尝试使用以下方法在react组件中获取数据点的情况: @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() {...}
}

有任何见解或工作流程模式吗?


您不希望自己是Redux中间件中的Axios实例吗?这将可被所有应用这种方式
Emrys Myrooin

您可以导入apiin App类,并在获得授权令牌后执行操作api.defaults.headers.common['Authorization'] = this.props.auth.tokens.authorization_token;,同时也可以将其存储在localStorage中,因此,当用户刷新页面时,可以检查令牌是否存在于localStorage中以及是否存在确实可以设置。我认为最好在api模块上立即设置令牌。
Dhruv Kumar Jha'9

1
丹·阿布罗莫夫(Dan Abromov)在此处的问题队列中提供了一个示例:github.com/reactjs/redux/issues/916
chrisjlee

1
如果您只需要从特定的reducer访问特定的状态,则可以尝试使用redux-named-reducers,它允许您从任何地方访问最新的状态。
miles_christian

Answers:


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
同构应用警告:在此服务器端进行操作将在所有用户之间共享Redux存储!
劳伦斯·沃格菲尔德'17

6
这个问题也没有明确指出“浏览器”。由于Redux和JavaScript都可以在服务器或浏览器上使用,因此更加安全。
劳伦斯·瓦格菲尔德'17

7
导出存储似乎造成了周期性导入的噩梦,createStore包含化简器,它需要您的操作(至少是操作类型枚举和Action接口),该操作不能导入任何尝试导入存储的内容。因此,您不得在减速器或操作中使用存储(或以其他方式绕过循环导入)
。– Eloff,

6
这是正确的答案,但是,如果您(像我一样)想要阅读而不是在商店中分发操作,则需要致电store.getState()
JuanJoséRamírez18

3
好吧,我对“访问redux商店”的解释是“读取”商店。只是想帮助别人。
JuanJoséRamírez18年

47

找到了解决方案。所以我将存储导入到我的api util中并在那里订阅。然后在该侦听器函数中,使用新获取的令牌设置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文件的外观吗?
2016年

正是我正在寻找的东西,非常感谢@subodh
Harkirat Saluja'5

1
我知道这个问题很旧,但是您可以接受自己的回答作为正确的答案。这使您的答案更容易为最终可能会来到这里的其他人找到。
Filipe Merker '18

3
当我尝试访问组件或函数外部的存储时,出现“ TypeError:WEBPACK_IMPORTED_MODULE_2__store_js .b未定义”。有什么帮助吗?

21

您可以使用storecreateStore函数返回的对象(应用初始化中的代码中应该已经使用了该对象)。比您可以使用此对象通过store.getState()方法获取当前状态或store.subscribe(listener)订阅商店更新。

您甚至可以将该对象保存为window属性,以在需要时从应用程序的任何部分访问它(window.store = store

可以在Redux文档中找到更多信息。


19
听起来有点头,要保存storewindow
Vic

3
@Vic当然是:)而且通常您不想这样做。我只想提及您可以使用此变量执行任何操作。最好的想法可能是将其存储在您创建“ createStore”生活的文件中,然后从中导入。
垃圾产生器

我有多个iframe需要访问其他iframe的状态。我知道这有点怪异,但我认为在窗口上放置存储要比对iframe使用消息要好。有什么想法吗??@Vic @trashgenerator吗?
肖恩·马尔特


9

就像@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))

它将在每个操作上设置令牌,但是您只能监听例如更改令牌的操作。


3

对于TypeScript 2.0,它看起来像这样:

我的商店

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(...)}

3
如果您投反对票,请添加评论原因。
Ogglas

3
投反对票,因为您的答案缺乏解释,并使用TypeScript而不是Javascript。
威尔

2
@Will感谢您说为什么。Imao的代码不需要规范,但是如果您想解释一些具体的内容,请说什么。确实使用了TypeScript,但是如果删除了键入内容,则相同的代码将在ES6中运行而不会出现问题。
奥格拉斯

请记住,如果您正在执行服务器端渲染,这将非常糟糕,它将与所有请求共享状态。
罗布·埃文斯

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

用钩子做。我遇到了类似的问题,但是我在使用带有钩子的react-redux。我不想用大量专用于从商店检索信息/向商店发送信息的代码来充实我的界面代码(即,对组件进行反应)。相反,我希望使用具有通用名称的函数来检索和更新数据。我的路径是将应用程序的

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

放入名为的模块store.jsexport然后const在store.js中添加常规的react-redux导入,然后添加。文件。然后,我index.js在应用程序级别导入,然后使用常规的import {store} from "./store.js"“子”组件导入到index.js,然后使用useSelector()useDispatch()钩子访问商店。

为了使用非组件式前端代码访问商店,我使用了类似的导入(即import {store} from "../../store.js"),然后使用store.getState()store.dispatch({*action goes here*})处理了商店的检索和更新(或向商店发送操作)。


0

访问令牌的一种简单方法是将令牌放入带有React Native的LocalStorage或AsyncStorage中。

下面是一个带有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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.