译者注: 获取 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 更应该是与组件无关,独立的一块东西。当然,在某些场景下,我们不得不采用一些权宜之计。
在最后,我的建议还是:合适的就是最好的!