如何在 React Component 之外获取 Redux Store

原文链接: Access the Redux Store Outside a React Component

译者注: 获取 redux 中的 store 的方法是个不起眼的小知识点,最近也有个小需求要在普通函数中引用 redux 的 store,习惯了在组件中通过 connect mapStateToProps 获取 store 中的数据,在外部暴力引用 store 时油然生出一种政治不正确的负罪感。瞅瞅国外的大佬们怎么看待这个问题,本人的感触就是只要能解决问题推动业务,没什么大问题,黑猫白猫能捉耗子就是好猫。国庆痛玩了几天,可以借着翻译一篇不太难而又有点意思的文章,从心理上平稳的向工作状态过渡一下也是极好的 ?

文章的作者是Dave Ceddia,也是书籍 Pure React 的作者

你有过在 React 组件之外用到 Redux store 的需求吗 ?

问题有点奇怪甚至在某种程度上带一点反讽,store 是一个全局的数据,有时我却没有合适的方法去获取它!

在组件中调用 connect 方法获取 store 是 react-redux 的标准用法,但是在组件之外的普通函数中获取 store 从技术上来讲会稍微有点不同。

本文中举例的场景是需要从 redux store 中获取 JWT Token

方法一:导出 store

这应该是最简单省事同时也容易想到的方法,但是有一点需要注意:

不要在服务端渲染中使用该方法

如果你在使用服务端渲染的 app 中直接导出 store,那么 app 的所有 user 都会获得一个单例的 store (包括相同的 JWT Token),这肯定不是你乐见的。

除去上述这种情况,当我们需要获取 store 中的 JWT Token 时,可以这么做:

/*
   store.js
 */
import { createStore } from 'redux';
import reducer from './reducer';

const store = createStore(reducer);

export default store;

创建 store 的方法还和以前一样,仅仅是导出它,以便于在其他文件中使用。

不要担心你使用的 createStore 方法可能会很复杂,放心的使用各种中间件比如 thunk, saga, devtools ,你需要做的仅仅是导出 store 而已。

紧接着,在需要用到 store 中数据的地方,引入 (import) 它。下面的例子就是我们在一个普通函数 api.js 中将 store 中的 JWT Token 通过 ajax 传递给服务端。

/**
	api.js
**/
import store from './store';

export function getProtectedThing() {
  // grab current state
  const state = store.getState();

  // get the JWT token out of it
  // (obviously depends on how your store is structured)
  const authToken = state.currentUser.token;

  // Pass the token to the server
  return fetch('/user/thing', {
    method: 'GET',
    headers: {
      Authorization: `Bearer ${authToken}`
    }
  }).then(res => res.json());
}

在 Redux 官网的 FAQ 中有这样一个问题 我可以在组件中直接引入 store 并使用吗 ?
其实如果需要在 React 组件中使用 store,我们有更好的选择 (译者注:也是官方提倡的):那就是使用 React-Redux 中的 connect 方法。

Mark Erikson,Redux 的维护者之一,在 Reddit 上就这个问题发表过如下看法:

通常情况下,不建议在 component 中通过 import 的方式来使用 store.
在某些情况下,可以试着通过 thunk 或者其他一些 Redux 中间件来操作 store 而不是直接去引用它。
但是凡事总有例外,打我自己的比方,有一个项目中 70% 的代码使用 Backbone 写的,在项目的一些模块中,我们同时需要来自 Backbone models 和 Redux store 中的数据,在这部分模块中,我们的确需要 import {store} from “store” 这种直接的引用方式,因为我们的确没有其他选择。
所以,直接引用并不是理想的方式,但是,如果有必要,也可以。

React Component 之外 dispatch action

如果你在 React Component 之外需要 dispatch action,那么同样的方法也是适用的:引入 store 然后再调用 store.dispatch(),这和你在 react-redux 中通过 connect 函数获取到 dispatch 是一样的。

方法二:从 thunk 中获取 redux 的 state

如果你需要在 thunk 中获取 redux store 中的某项数据,更简单不过。你根本不需要直接引入 store,因为 thunk 的 action 中有一个 getState 的输入参数。

下面的例子就是从 thunk 的 action 创建函数中获取 JWT Token.

/** actions.js **/
export function getProtectedThing() {
  return (dispatch, getState) => {
    // grab current state
    const state = getState();

    // get the JWT token out of it
    // (obviously depends on how your store is structured)
    const authToken = state.currentUser.token;

    // Pass the token to the server
    return fetch('/user/thing', {
      method: 'GET',
      headers: {
        Authorization: `Bearer ${authToken}`
      }
    }).then(res => res.json());
  }
}

另外,如果你不想在 thunk 的 action 函数中直接使用 fetch,你可以选择将 fetch 封装到另外一个类中比如 api.js. 当然你需要额外的参数将所需数据传递出去。

方法三:使用中间件(middleware)截获 action 中的数据

如果你不喜欢上述两种方法 (或是无法使用),那么也可以试试下面这种方法。

开发一个自定义的 redux 中间件,“拦截” 特定的 action. 在 action 触达 store 之前获取其携带的数据,甚至在中间件中直接获取 store.

我们还以 JWT Token 为例。假设应用程序在用户成功登陆后会触发一个 LOG_SUCCESS 的 action,该 action 携带的数据包含 JWT Token.

/*
	index.js
*/
const saveAuthToken = store => next => action => {
  if(action.type === 'LOGIN_SUCCESS') {
    // after a successful login, update the token in the API
    api.setToken(action.payload.authToken);
  }

  // continue processing this action
  return next(action);
}

const store = createStore(
  reducer,
  applyMiddleware(saveAuthToken)
);

在你的 api.js 中会有一个 setToken 的方法将 token 赋值给本地变量。

/*
	api.js
*/
let currentAuthToken = null;

export function setToken(token) {
  currentAuthToken = token;
}

export function getProtectedThing() {
  // Pass the token to the server
  return fetch('/user/thing', {
    method: 'GET',
    headers: {
      Authorization: `Bearer ${currentAuthToken}`
    }
  }).then(res => res.json());
}

如果你使用的 http 库是 axios, 你在 setToken 中可以直接将 token 赋值到 axios 实例的 Authorization header,axios 在发送请求时请求头会自动包含该字段。

import axios from 'axios';

export function setToken(token) {
  axios.defaults.headers.common['Authorization'] =
      `Bearer ${token}`;
}

方法四:在 React 组件中传值

(译者注:这种方法和主题无关了,就是组件内通过 connect 的方式)

import React from 'react';
import { connect } from 'react-redux';
import * as api from 'api';

const ItemList = ({ authToken, items }) => {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          {item.name}
          <button
            onClick={
              () => api.deleteItem(item, authToken)
            }>
            DELETE THIS ITEM
          </button>
        </li>
      )}
    </ul>
  )
}

const mapStateToProps = state => ({
  authToken: state.currentUser && state.currentUser.authToken,
  items: state.items
});

export connect(mapStateToProps)(ItemList);

那种方法最好?

采用哪种方式取决你自己的需要。

我自己喜欢方法一:直接引用 store. 原因是这种方法足够简单,当然前提是你的应用没有采用服务端渲染。

在个别情况下,方法一可能会有点麻烦,原因在于潜在的循环依赖。那么这是,方法三中间件的方式可以尝试一下。

方法二 thunk 的方式也不错,在 thunk 中获取相应的数据也是可以的。当时有时从可读性上可能会有点奇怪,因为这种方式会把 Redux 流程和一些特定的业务混杂起来。

从个人喜好来说,我不喜欢方法四,因为我不愿意把鉴权 token 这种东西和组件绑定起来。token 更应该是与组件无关,独立的一块东西。当然,在某些场景下,我们不得不采用一些权宜之计。

在最后,我的建议还是:合适的就是最好的!

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值