如何将高阶组件连接到 Redux 存储?



基本上,我有一个AuthenticationHOC必须获取redux状态,检查令牌是否存在,如果存在,则渲染包装的组件。如果没有,则调度一个操作以尝试从本地存储加载令牌。如果此操作失败,请重定向到登录页面。

import React from 'react';
import { connect } from 'react-redux';
import * as UserActions from '../../state/actions/user-actions';
import * as DashboardActions from '../../state/actions/dashboard-actions';
const mapStateToProps = state => {
return {
token: state.user.token,
tried: state.user.triedLoadFromStorage,
};
};
const _AuthenticationHOC = Component => props => {
// if user is not logged and we 've not checked the localStorage
if (!props.token && !props.tried) {
// try load the data from local storage
props.dispatch(DashboardActions.getDashboardFromStorage());
props.dispatch(UserActions.getUserFromStorage());
} else {
// if the user has not token or we tried to load from localStorage 
//without luck, then redirect to /login
props.history.push('/login');
}
// if the user has token render the component
return <Component />;
};
const AuthenticationHOC = connect(mapStateToProps)(_AuthenticationHOC);
export default AuthenticationHOC;

然后我试着像这样使用它

const SomeComponent = AuthenticationHOC(connect(mapStateToProps)(HomeComponent));

但我总是收到一个错误,准确地标记上面的行。

类型错误:对象(...(不是函数

然后我做了一个简化版本

我将代码从我的 HOC 替换为最简单的版本

const _AuthenticationHOC = Component => props => {
return <Component {...props}/>;
};

这也行不通。然后我从我的 HOC 中删除了连接功能,只需导出此组件和 tada!...现在工作!

所以我怀疑连接返回了一个不能用作 HoC 函数的对象。这是对的吗?我能在这里做什么?

请参阅此答案的底部以阅读对问题内容的直接回答。我将从我们在日常开发中使用的良好实践开始。


连接高阶元件

Redux 提供了一个有用的compose实用程序函数。

compose所做的只是让你编写深度嵌套的函数转换,而不会让代码向右漂移。

所以在这里,我们可以使用它来嵌套 HoC,但以一种可读的方式。

// Returns a new HoC (function taking a component as a parameter)
export default compose(
// Parent HoC feeds the Auth HoC
connect(({ user: { token, triedLoadFromStorage: tried } }) => ({
token,
tried
})),
// Your own HoC
AuthenticationHOC
);

这类似于手动创建新的容器 HoC 函数。

const mapState = ({ user: { token, triedLoadFromStorage: tried } }) => ({
token,
tried
});
const withAuth = (WrappedComponent) => connect(mapState)(
AuthenticationHOC(WrappedComponent)
);
export default withAuth;

然后,您可以透明地使用您的身份验证 HoC。

import withAuth from '../AuthenticationHOC';
// ...
export default withAuth(ComponentNeedingAuth);

编写干净且可测试的 HoC

为了将身份验证组件与存储和路由隔离,我们可以将其拆分为多个文件,每个文件都有自己的责任。

- withAuth/
- index.js           // Wiring and exporting (container component)
- withAuth.jsx       // Defining the presentational logic
- withAuth.test.jsx  // Testing the logic

我们将withAuth.jsx文件集中在渲染和逻辑上,无论它来自何处。

// withAuth/withAuth.jsx
import React from 'react';
const withAuth = (WrappedComponent) => ({
// Destructure props here, which filters them at the same time.
tried,
token,
getDashboardFromStorage, 
getUserFromStorage, 
onUnauthenticated, 
...props
}) => {
// if user is not logged and we 've not checked the localStorage
if (!token && !tried) {
// try load the data from local storage
getDashboardFromStorage();
getUserFromStorage();
} else {
// if the user has no token or we tried to load from localStorage
onUnauthenticated();
}
// if the user has token render the component PASSING DOWN the props.
return <WrappedComponent {...props} />;
};
export default withAuth;

看?我们的 HoC 现在不知道存储和路由逻辑。我们可以将重定向移动到商店中间件或其他任何地方,如果商店不是您想要的地方,甚至可以在道具<Component onUnauthenticated={() => console.log('No token!')} />中对其进行自定义。

然后,我们只提供index.js中的道具,就像容器组件一样。1

// withAuth/index.js
import React from 'react';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { getDashboardFromStorage, onUnauthenticated } from '../actions/user-actions';
import { getUserFromStorage } from '../actions/dashboard-actions';
import withAuth from './withAuth';
export default compose(
connect(({ user: { token, triedLoadFromStorage: tried } }) => ({
token,
tried
}), {
// provide only needed actions, then no `dispatch` prop is passed down.
getDashboardFromStorage,
getUserFromStorage,
// create a new action for the user so that your reducers can react to
// not being authenticated
onUnauthenticated,
}),
withAuth
);

onUnauthenticated作为商店操作的好处是,不同的化简器现在可以对它做出反应,例如擦除用户数据、重置仪表板数据等。

测试网络

然后,可以使用Jest和酶之类的东西测试withAuthHoC的隔离逻辑。

// withAuth/withAuth.test.jsx
import React from 'react';
import { mount } from 'enzyme';
import withAuth from './withAuth';
describe('withAuth HoC', () => {
let WrappedComponent;
let onUnauthenticated;
beforeEach(() => {
WrappedComponent = jest.fn(() => null).mockName('WrappedComponent');
// mock the different functions to check if they were called or not.
onUnauthenticated = jest.fn().mockName('onUnauthenticated');
});
it('should call onUnauthenticated if blah blah', async () => {
const Component = withAuth(WrappedComponent);
await mount(
<Component 
passThroughProp
onUnauthenticated={onUnauthenticated} 
token={false}
tried
/>
);
expect(onUnauthenticated).toHaveBeenCalled();
// Make sure props on to the wrapped component are passed down
// to the original component, and that it is not polluted by the
// auth HoC's store props.
expect(WrappedComponent).toHaveBeenLastCalledWith({
passThroughProp: true
}, {});
});
});

为不同的逻辑路径添加更多测试。

<小时 />

关于您的情况

所以我怀疑connect返回一个不能用作 HoC 函数的对象。

react-redux 的connect返回一个 HoC。

import { login, logout } from './actionCreators'
const mapState = state => state.user
const mapDispatch = { login, logout }
// first call: returns a hoc that you can use to wrap any component
const connectUser = connect(
mapState,
mapDispatch
)
// second call: returns the wrapper component with mergedProps
// you may use the hoc to enable different components to get the same behavior
const ConnectedUserLogin = connectUser(Login)
const ConnectedUserProfile = connectUser(Profile)

在大多数情况下,包装器函数将立即被调用,没有 保存在临时变量中:

export default connect(mapState, mapDispatch)(Login)

然后我尝试像这样使用它

AuthenticationHOC(connect(mapStateToProps)(HomeComponent))

你很接近,尽管你连接HoC的顺序是相反的。它应该是:

connect(mapStateToProps)(AuthenticationHOC(HomeComponent))

这样,AuthenticationHOC从商店接收道具,并且HomeComponent被正确的 HoC 正确包装,这将返回一个新的有效组件。

话虽如此,我们可以做很多事情来改进这个 HoC!

<小时 />

1。如果您不确定是否将index.js文件用于容器组件,您可以根据需要重构它,例如withAuthContainer.jsx文件,该文件要么导出到索引中,要么让开发人员选择他们需要的文件。

正如您的第一次尝试所描述的那样:const SomeComponent = AuthenticationHOC(connect(mapStateToProps)(HomeComponent))

根据定义,这意味着将参数作为纯组件传递给AuthenticationHOC将返回另一个组件。但是在这里您正在通过另一个 HOC,即connect()它不是一个组件,而是一个包装器。因此,根据定义,解析为return <connect(mapStateToProps) />return <Component />会产生语法错误或运行时错误。

将纯组件作为某些HomeComponent传递将起作用,因为它只是一个组件。

我的猜测是,在幕后,connect()做咖喱。它的作用是返回一个组件包装器,其mapStateToPropsmapDispatchToProps作为注入的附加道具。来源 - https://react-redux.js.org/api/connect#connect-returns

connect()所述:

connect(( 的返回是一个包装函数,它采用你的 组件并返回一个带有附加属性的包装器组件 注入。

因此,我们可以将序列反转为:

const AuthenticationHOC = _AuthenticationHOC(HomeComponent);
export default connect(mapStateToProps)(AuthenticationHOC);

并确保在您的 HOC 中传递props

const _AuthenticationHOC = Component => props => {
return <Component {...props} />; // pass props
};

最新更新