请参阅此答案的底部以阅读对问题内容的直接回复。我将从我们在日常开发中使用的良好实践开始。
Redux 提供了一个有用的compose utility function。
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;
然后,您可以透明地使用您的 auth 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 现在不知道存储和路由逻辑。我们可以将重定向移动到 store 中间件或其他任何地方,如果 store 不是您想要的位置,它甚至可以在 prop <Component onUnauthenticated={() => console.log('No token!')} /> 中自定义。
那么,我们只提供index.js中的props,就像一个容器组件。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 作为存储操作的好处是不同的 reducer 现在可以对其做出反应,例如擦除用户数据、重置仪表板数据等。
测试 HoC
然后,可以使用Jest 和enzyme 之类的东西来测试withAuth HoC 的隔离逻辑。
// 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 returns an 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 接收来自 store 的 props,HomeComponent 被正确的 HoC 正确包装,这将返回一个新的有效组件。
话虽如此,我们可以做很多事情来改进这个 HoC!
1.如果您不确定是否将 index.js 文件用于容器组件,您可以随意重构它,比如导出一个 withAuthContainer.jsx 文件索引或让开发人员选择他们需要的索引。