【问题标题】:What is the best way to access redux store outside a react component?在反应组件之外访问 redux 存储的最佳方法是什么?
【发布时间】:2016-11-22 11:47:28
【问题描述】:

@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 在 React 组件中获取它会是什么样子

// 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 实例存在于 redux 中间件中吗?通过这种方式,您的所有应用程序都可以使用它
  • 你可以在App类中导入api,得到授权令牌后你可以做api.defaults.headers.common['Authorization'] = this.props.auth.tokens.authorization_token;,同时你也可以把它存储在localStorage中,所以当用户刷新页面,可以查看localStorage中是否存在token,如果存在,可以设置,我觉得最好一拿到就在api模块上设置token。
  • Dan Abromov 在此处的问题队列中提供了一个示例:github.com/reactjs/redux/issues/916
  • 如果你只需要从特定的reducer访问特定的状态,你可以试试redux-named-reducers它允许你从任何地方访问最新的状态。

标签: reactjs redux react-redux


【解决方案1】:

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

如果您真的需要,您甚至可以将此对象保存到 window 属性,以便从应用程序的任何部分访问它 (window.store = store)

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

【讨论】:

  • store 保存到window 听起来有点老套
  • @Vic 当然是 :) 通常你不想这样做。我只是想提一下,你可以用这个变量做任何你想做的事情。最好的办法可能是将其存储在您创建“createStore”的文件中,然后从中导入。
  • 我有多个 iframe 需要访问其他 iframe 的状态。我知道这有点骇人听闻,但我认为将商店放在窗口上会比使用 iframe 消息更好。有什么想法吗?? @Vic @trashgenerator?
【解决方案2】:

找到了解决办法。所以我在我的 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

也许它可以进一步改进,因为目前它似乎有点不雅。我以后可以做的就是在我的商店中添加一个中间件,然后在那里设置令牌。

【讨论】:

  • 您能分享一下您的store.js 文件是什么样的吗?
  • 正是我正在寻找的东西,非常感谢@subodh
  • 我知道这个问题很老,但你可以接受你自己的答案是正确的。这让最终可能会来到这里的其他人更容易找到您的答案。
  • 当我尝试访问组件或函数之外的存储时,出现“TypeError: WEBPACK_IMPORTED_MODULE_2__store_js.b is undefined”。为什么会这样?
  • @Subodh,我认为它不再起作用了!
【解决方案3】:

从您调用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')

【讨论】:

  • 对同构应用的警告:在服务器端执行此操作将在所有用户之间共享 redux 存储!!!
  • 该问题也没有明确说明“浏览器”。由于 Redux 和 JavaScript 都可以在服务器或浏览器上使用,因此具体化会更安全。
  • exporting store 似乎会造成循环导入的噩梦,createStore 包括您的 reducer,这需要您的操作(至少是操作类型枚举和操作接口),它不能导入任何尝试导入的内容店铺。所以你不能在你的 reducer 或 action 中使用 store(或者以其他方式围绕循环导入。)
  • 这是正确答案,但如果你(像我一样)想阅读而不是在商店中发送操作,你需要致电store.getState()
  • 好吧,我对“访问 redux 商店”的解释是“阅读”商店。只是想帮助别人。
【解决方案4】:

你可以根据How can I access the store in non react components?使用Middleware

中间件

function myServiceMiddleware(myService) {
  return ({ dispatch, getState }) => next => action => {
    if (action.type == 'SOMETHING_SPECIAL') {
      myService.doSomething(getState());
      myService.doSomethingElse().then(result => dispatch({ type: 'SOMETHING_ELSE', result }))
    }
    return next(action);
  }
}

用法

import { createStore, applyMiddleware } from 'redux'
const serviceMiddleware = myServiceMiddleware(myService)
const store = createStore(reducer, applyMiddleware(serviceMiddleware))

延伸阅读Redux Docs > Middleware

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • 如果您投反对票,请添加评论原因。
    • 投了反对票,因为您的答案缺乏解释并且使用 TypeScript 而不是 Javascript。
    • @Will 谢谢你说出原因。 Imao 代码不需要规范,但如果您想要具体解释,请说什么。确实使用了 TypeScript,但如果删除了类型,相同的代码将在 ES6 中运行而不会出现问题。
    • 请记住,如果您进行服务器端渲染,这将非常糟糕,它将与所有请求共享状态。
    【解决方案6】:

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

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

    【讨论】:

    • 如何使用自定义 axios 实例实现同样的效果?
    【解决方案7】:

    访问令牌的一种简单方法是将令牌放入 LocalStorage 或带有 React Native 的 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

    【讨论】:

    • 我不认为这很有用,因为你是直接访问 localstorage,而不使用 redux store
    【解决方案8】:

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

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

    进入一个名为 store.js 的模块并在 const 之前添加 export 并在 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*}) 来处理检索和更新(呃,发送操作)商店。

    【讨论】:

    • 我发现从索引等导出存储的技巧非常有用!
    【解决方案9】:

    可能有点晚了,但我认为最好的方法是使用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)
        );
    }
    

    【讨论】:

      【解决方案10】:

      导出我的商店变量

      export const store = createStore(rootReducer, applyMiddleware(ReduxThunk));

      在动作文件或你的文件需要他们导入这个(存储)

      从“./path...”导入{store};

      这一步用函数从存储变量中得到满足

      const state = store.getState();

      并获取您的应用的所有状态

      【讨论】:

        猜你喜欢
        • 2016-05-27
        • 2021-09-09
        • 2020-05-26
        • 2021-03-01
        • 2010-10-22
        • 1970-01-01
        • 2018-04-13
        • 2015-12-31
        • 1970-01-01
        相关资源
        最近更新 更多