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

时间:2019-08-07 02:15:45

标签: javascript reactjs redux react-redux higher-order-components

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

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));

但是我总是在正确标记上面的行时出现错误。

  

TypeError:Object(...)不是函数

然后我做了一个简化版

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

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

,这也不起作用。然后我从HOC中删除了connect函数,只导出了这个组件和tada! ...现在可以使用!

因此,我怀疑connect返回的对象不能用作HoC函数。它是否正确?我在这里可以做什么?

3 个答案:

答案 0 :(得分:4)

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


连接Higher-Order Component

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
});

export default WrappedComponent => connect(mapState)(
  AuthenticationHOC(WrappedComponent)
);

然后,您可以透明地使用auth HoC。

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

编写干净且可测试的HoC

为了将auth组件与商店和路由隔离开,我们可以将其拆分为多个文件,每个文件都有自己的职责。

- 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';

export default Component => ({
  // 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 <Component {...props} />;
};

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

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

// withAuth/index.js
import React from 'react';
import { connect, compose } from 'react-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作为存储操作的好处是,现在不同的化简器可以对此做出反应,例如擦除用户数据,仪表板数据等。

测试HoC

然后,可以使用Jestenzyme之类的东西来测试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从商店接收道具,HomeComponent被正确的HoC正确包装,这将返回一个新的有效组件。

话虽如此,我们仍然可以做很多工作来改善此HoC!


1。如果不确定将 index.js 文件用作容器组件,则可以根据需要进行重构,例如说一个 withAuthContainer.jsx 文件,该文件可以导出到索引,或者让开发人员选择所需的索引。

答案 1 :(得分:0)

首次尝试描述: const SomeComponent = AuthenticationHOC(connect(mapStateToProps)(HomeComponent))

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

将纯组件作为某些return <connect(mapStateToProps) />传递会起作用,因为它只是一个组件。

我的猜测是,HomeComponent在幕后currying。它所做的是返回一个组件包装器,其中包含其connect()mapStateToProps作为注入的其他道具。来源-https://react-redux.js.org/api/connect#connect-returns

答案 2 :(得分:0)

connect()中所述:

  

connect()的返回是一个包装函数,可将您的   组件,并返回包装器组件及其附加道具   注入。

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

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

并确保在您的HOC中通过props

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