不可变,为什么使用fromJS时我的嵌套对象在使用reselect时不是不可变的

时间:2016-11-29 07:38:46

标签: javascript reactjs immutable.js reselect

我有一个不可变的错误并重新选择。

我在reactjs应用程序中有以下redux存储:

a

根据immutableJS文档,fromJS为嵌套对象创建不可变对象。

这就是记录下一行的原因:

/*
 * The reducer takes care of our data
 * Using actions, we can change our application state
 * To add a new action, add it to the switch statement in the homeReducer function
 *
 * Example:
 * case YOUR_ACTION_CONSTANT:
 *   return assign({}, state, {
 *       stateVariable: action.var
 *   });
 */

import { fromJS } from 'immutable';

import {
  CHANGE_FORM,
  SENDING_REQUEST,
  REQUEST_SUCCESS,
  CLEAR_SUCCESS,
  REQUEST_ERROR,
  CLEAR_ERROR,
} from './constants';

// The initial application state
const initialState = fromJS({
  formState: {
    username: 'dka',
    password: '',
  },
  success: false,
  error: false,
  isCurrentlySending: false,
});

console.log(initialState.getIn(['formState','username']));

// Takes care of changing the application state
function loginReducer(state = initialState, action) {
  switch (action.type) {
    case CHANGE_FORM:
      return state
        .set('formState', action.newFormState);
    case SENDING_REQUEST:
      return state
        .set('isCurrentlySending', action.sending);
    case REQUEST_SUCCESS:
      return state
        .set('success', action.success)
        .set('isCurrentlySending', false);
    case REQUEST_ERROR:
      return state
        .set('error', action.error)
        .set('isCurrentlySending', false);
    case CLEAR_SUCCESS:
      return state
        .set('success', null);
    case CLEAR_ERROR:
      return state
        .set('error', null);
    default:
      return state;
  }
}

export default loginReducer;

然而,当我尝试将其用于重新选择时,这不再起作用了:

console.log(initialState.getIn(['formState','username']));
// or 
console.log(initialState.get('formState').get('username');

首先阅读文档后,我认为这是selectUsername的正确答案:

import { createSelector } from 'reselect';

const selectLogin = () => (state) => state.get('login');

const selectFormState = () => createSelector(
  selectLogin(),
  (loginState) => loginState.get('formState')
);

const selectUsername = () => createSelector(
  selectFormState(),
  // (formState) => formState.username // work fine but disabled because username should be accessed using .get or .getIn
  (formState) => formState.get('username') // doesn't work because formState is a plain object
);

这是我处理changeForm操作的LoginForm:

const selectUsername = () => createSelector(
  selectLogin(),
  selectFormState(),
  (formState) => formState.get('username')
);

这是包括在内的

/**
 * LoginForm
 *
 * The form with a username and a password input field, both of which are
 * controlled via the application state.
 *
 */
import React from 'react';
import Input from 'components/bootstrap/atoms/Input';
import Label from 'components/bootstrap/atoms/Label';
import H2 from 'components/bootstrap/atoms/H2';
import Form from 'components/bootstrap/atoms/Form';
import Button from 'components/bootstrap/atoms/Button';
import LoadingButton from 'components/kopax/atoms/LoadingButton';
import { FormattedMessage } from 'react-intl';
import messages from './messages';
import { url } from 'config';
import { changeForm, requestError, clearError, clearSuccess } from 'containers/LoginPage/actions';
import Alert from 'components/bootstrap/atoms/Alert';
import LocaleToggle from 'containers/LocaleToggle';

export class LoginForm extends React.Component { // eslint-disable-line react/prefer-stateless-function

  static propTypes = {
    isCurrentlySending: React.PropTypes.bool.isRequired,
    onSubmit: React.PropTypes.func.isRequired,
    data: React.PropTypes.object.isRequired,
    success: React.PropTypes.object,
    error: React.PropTypes.object,
    dispatch: React.PropTypes.func.isRequired,
  };

  render() {
    const { success, error } = this.props;
    return (
      <Form action={url.login} onSubmit={this.onSubmit}>
        <H2><FormattedMessage {...messages.title} /></H2>
        {success && <Alert className="alert-success" onDismiss={this.hideSuccess}><FormattedMessage {...success} /></Alert>}
        {error && <Alert className="alert-danger" onDismiss={this.hideError}><FormattedMessage {...error} /></Alert>}
        <Label htmlFor="username"><FormattedMessage {...messages.username} /></Label>
        <Input
          type="text"
          onChange={this.changeUsername}
          placeholder="bob"
          autoCorrect="off"
          autoCapitalize="off"
          spellCheck="false"
        />
        <Label htmlFor="password"><FormattedMessage {...messages.password} /></Label>
        <Input
          type="password"
          onChange={this.changePassword}
          placeholder="••••••••••"
        />
        {this.props.isCurrentlySending ? (
          <LoadingButton className="btn-primary">
            <FormattedMessage {...messages.buttonLogin} />
          </LoadingButton>
        ) : (
          <div>
            <LocaleToggle />
            <Button className="primary">
              <FormattedMessage {...messages.buttonLogin} />
            </Button>
          </div>
        )}
      </Form>
    );
  }

  // Change the username in the app state
  changeUsername = (evt) => {
    const newState = this.mergeWithCurrentState({
      username: evt.target.value,
    });
    this.emitChange(newState);
  }
  // Change the password in the app state
  changePassword = (evt) => {
    const newState = this.mergeWithCurrentState({
      password: evt.target.value,
    });
    this.emitChange(newState);
  }
  // Merges the current state with a change
  mergeWithCurrentState(change) {
    return this.props.data.merge(change);
  }
  // Emits a change of the form state to the application state
  emitChange(newState) {
    this.props.dispatch(changeForm(newState));
  }
  // onSubmit call the passed onSubmit function
  onSubmit = (evt) => {
    evt.preventDefault();
    const username = this.props.data.get('username').trim();
    const password = this.props.data.get('password').trim();
    const isValidated = this.validateForm(username, password);
    if (isValidated) {
      this.props.onSubmit(username, password);
    } else {
      this.props.dispatch(requestError(messages.errorFormEmpty));
    }
  }
  // validate the form
  validateForm(username, password) {
    this.props.dispatch(clearError());
    this.props.dispatch(clearSuccess());
    return username.length > 0 && password.length > 0;
  }

  hideError = () => {
    this.props.dispatch(clearError());
  }

  hideSuccess = () => {
    this.props.dispatch(clearSuccess());
  }

}

export default LoginForm;

这是我的

/**
 * FormPageWrapper
 */

import React from 'react';
import Alert from 'components/bootstrap/atoms/Alert';
import styled, { keyframes } from 'styled-components';
import defaultThemeProps from 'styled/themes/mxstbr/organisms/FormPageWrapper';
import LoginForm from '../../molecules/LoginForm';
import { FormattedMessage } from 'react-intl';
import messages from './messages';
import cn from 'classnames';

const propTypes = {
  isCurrentlySending: React.PropTypes.bool.isRequired,
  onSubmit: React.PropTypes.func.isRequired,
  className: React.PropTypes.string,
  data: React.PropTypes.object.isRequired,
  success: React.PropTypes.oneOfType([
    React.PropTypes.object,
    React.PropTypes.bool,
  ]),
  error: React.PropTypes.oneOfType([
    React.PropTypes.object,
    React.PropTypes.bool,
  ]),
  dispatch: React.PropTypes.func.isRequired,
};

const defaultProps = {
  theme: {
    mxstbr: {
      organisms: {
        FormPageWrapper: defaultThemeProps,
      },
    },
  },
};

class FormPageWrapper extends React.Component {
  render() {
    const { className, onSubmit, dispatch, data, isCurrentlySending, success, error } = this.props;
    return (
      <div className={cn(className, 'form-page__wrapper')}>
        <div className="form-page__form-wrapper">
          <div className="form-page__form-header">
            <h2 className="form-page__form-heading"><FormattedMessage {...messages.title} /></h2>
          </div>
          {success && <Alert className="mx-2 alert-success" onDismiss={this.hideSuccess}><FormattedMessage {...success} /></Alert>}
          {error && <Alert className="mx-2 alert-danger" onDismiss={this.hideError}><FormattedMessage {...error} /></Alert>}
          <LoginForm
            onSubmit={onSubmit}
            data={data}
            dispatch={dispatch}
            isCurrentlySending={isCurrentlySending}
          />
        </div>
      </div>
    );
  }

}


const shake = keyframes`
  0% {
    transform: translateX(0);
  }
  25% {
    transform: translateX(10px);
  }
  75% {
    transform: translateX(-10px);
  }
  100% {
    transform: translateX(0);
  }
`;

// eslint-disable-next-line no-class-assign
FormPageWrapper = styled(FormPageWrapper)`
  ${(props) => `

    margin-top: ${props.theme.mxstbr.organisms.FormPageWrapper['$margin-x']};

    &.form-page__wrapper {
      display: flex;
      align-items: center;
      justify-content: center;
      height: 100%;
      width: 100%;
    }

    .form-page__form-wrapper {
      max-width: 325px;
      width: 100%;
      border: 1px solid ${props.theme.mxstbr.organisms.FormPageWrapper['$very-light-grey']};
      border-radius: 3px;
      box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25);
      background-color: #fff;
    }

    .form-page__form-heading {
      text-align: center;
      font-size: 1em;
      user-select: none;
    }

    .form-page__form-header {
      padding: 1em;
    }

    & .js-form__err-animation {
      animation: ${shake} 150ms ease-in-out;
    }

  `}
`;

FormPageWrapper.propTypes = propTypes;
FormPageWrapper.defaultProps = defaultProps;

export default FormPageWrapper;

这是我处理登录的传奇:

/*
 * LoginPage
 *
 * This is the first thing users see of our App, at the '/' route
 *
 */
import React from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { selectLogin } from './selectors';
import { loginRequest } from './actions';
import FormPageWrapper from 'components/mxstbr/organisms/FormPageWrapper';

export class LoginPage extends React.Component { // eslint-disable-line react/prefer-stateless-function

  static propTypes = {
    data: React.PropTypes.object.isRequired,
    dispatch: React.PropTypes.func.isRequired,
    onSubmitFormLogin: React.PropTypes.func.isRequired,
  };

  render() {
    const dispatch = this.props.dispatch;
    const formState = this.props.data.get('formState');
    const isCurrentlySending = this.props.data.get('isCurrentlySending');
    const success = this.props.data.get('success');
    const error = this.props.data.get('error');
    return (
      <FormPageWrapper
        onSubmit={this.props.onSubmitFormLogin}
        success={success}
        error={error}
        data={formState}
        dispatch={dispatch}
        isCurrentlySending={isCurrentlySending}
      />
    );
  }

}

export function mapDispatchToProps(dispatch) {
  return {
    dispatch,
    onSubmitFormLogin: (username, password) => {
      dispatch(loginRequest({ username, password }));
    },
  };
}

const mapStateToProps = createStructuredSelector({
  data: selectLogin(),
});

// Wrap the component to inject dispatch and state into it
export default connect(mapStateToProps, mapDispatchToProps)(LoginPage);

我很乐意理解为什么没有正确选择

2 个答案:

答案 0 :(得分:2)

我无法解释为什么您的示例不适用于您... reselect代码显示了Immutable.js结构的there is no magic

这段代码非常适合我(注意我删除了一级&#34;因子和#34;选择器,因此不再有selector = () => (state) => ...;说实话我不能说出来&#39 ; s问题的根源,但它不是必需的代码):

const { createSelector } = require('reselect');
const { fromJS } = require('immutable');

const initialState = fromJS({
  login: {
    formState: {
      username: 'dka',
      password: '',
    },
    success: false,
    error: false,
    isCurrentlySending: false,
  }
});

const selectLogin = (state) => state.get('login');

const selectFormState = createSelector(
  selectLogin,
  (loginState) => loginState.get('formState')
);

const selectUsername = createSelector(
  selectFormState,
  (formState) => formState.get('username')
);

console.log(selectUsername(initialState));

答案 1 :(得分:2)

您是如何在动作创建者case CHANGE_FORM: return state .set('formState', action.newFormState); 中设置newFormState有效内容的?

// ..
return state.setIn(['formState', 'username'], action.newFormState.username)

检查action.newFormState是否是普通对象?如果是这样,你应该明确设置所选字段。

.btn-info.focus, .btn-info:focus {
    color: #fff;
    background-color: #31b0d5;
    border-color: #1b6d85;
}