根据Redux

时间:2017-03-06 23:20:01

标签: javascript reactjs redux react-router

下午好,

当我尝试根据州的变化重定向我的应用用户时,我在使用React和Redux时遇到了一些困难。

处于较高级别:当我的user状态对象填充了从主页//student/:username的信息时,我希望我的应用的路线发生变化。

现在我已经以一种黑客的方式完成了这件事。

在我的Login组件中,当来自第三方API的访问令牌从我的Express服务器传回客户端时,我使用componentDidUpdate生命周期挂钩来监听和分派操作。

import React from "react";

import LoginForm from "../minor/LoginForm";

const Login = React.createClass({

  componentDidUpdate(){
    const {success, access_token} = this.props.loginStatus;

    if (success === true && access_token !== null){
      console.log("It worked getting your data now...");
      this.props.getUserData(access_token);
    } else if (success === false || access_token === null){
      console.log("Something went wrong...");
    } 

  }, 
render(){

    return(

        <div className="loginComponentWrapper">
          <h1>Slots</h1>
          <LoginForm loginSubmit={this.props.loginSubmit}
                     router={this.props.router}
                     user={this.props.user} />
          <a href="/register">New User?</a>
        </div>
      )
  }
});

请注意,我正在将routeruser传递给我的LoginForm组件。我这样做是为了使用另一个componentDidUpdate我在.push使用router方法,如下所示:

import React from "react";

const LoginForm = React.createClass({


  componentDidUpdate(){
    const {router, user} = this.props;

    if (user.username !== null){
       router.push(`/student/${user.username}`);
    }
  },


  render(){

      return(
        <div className="loginWrapper">
          <div className="loginBox">
            <form className="loginForm" action="">
              <input ref={(input) => this.username_field = input} type="text" placeholder="username" defaultValue="kitties@kit.com" />
              <input ref={(input) => this.password_field = input} type="text" placeholder="password" defaultValue="meowMeow3" />
              <button onClick={this.loginAttempt}>Login</button>
            </form>
          </div>
        </div>
        )
  }

});

当然有效,但我确信这是一个过于复杂的解决方案而不是被认为是最佳做法。我已经尝试在我的Login组件中添加自定义侦听器方法,但我从来没有成功启动它,而是我的应用程序陷入循环。

有没有办法可以使用Redux来保持我的组件整洁,或者以更有效的方式利用componentDidUpdate

一如既往,我会对我的问题有一些更有经验的眼睛,并期待一些反馈!

由于

更新

App.js

import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import * as actionCreators from "../actions/userActions.js";

import StoreShell from "./StoreShell.js";

function mapStateToProps(state){
  return{
    loginStatus: state.loginStatus,
    user: state.user
  }
}

function mapDispatchToProps(dispatch){
  return bindActionCreators(actionCreators, dispatch)
}

const App = connect(mapStateToProps, mapDispatchToProps)(StoreShell);

export default App;

这个组件将所有我的redux东西和状态数据“洒”到名为StoreShell的容器组件中,后者又将所有数据传递给构成UI的元素的props,如Login和{ {1}}我采取了太多步骤吗?

LoginForm

StoreShell.js

2 个答案:

答案 0 :(得分:2)

有几件事可以使登录流程更易于管理,并且可以稍微整理和整理您的组件。首先要说几点:

我不确定为什么你在两个组件之间划分了登录逻辑?惯用的React / Redux方法是拥有一个处理逻辑的容器组件,以及一个处理表示的表示组件。目前,两个组件都有一点点。

您无需在组件中上下传递props.router。 React路由器提供了一个HOC,它提供路由器作为带有路由器(docs here)的道具。你只需在withRouter中包装一个组件,props.router就可以了 - 其他一切都保持不变。

export default withRouter(LoginForm);

我个人认为URI是应用程序状态的一部分,因此它应该在商店中维护并通过调度操作进行更新。有一个破解库可以执行此操作 - react-router-redux。完成设置后,您可以将push方法传递给组件(或其他地方......请参阅下一点)进行导航:

import { push } from 'react-router-redux';
import { connect } from 'react-redux';

const NavigatingComponent = props => (
    <button onClick={() => props.push('/page')}>Navigate</button>
);

const mapStateToProps = null;
const mapDispatchToProps = {
    push
};

export default connect(mapStateToProps, mapDispatchToProps)(NavigatingComponent);

在我们的商店中拥有URI的一个好处是我们不需要访问props.router来更改位置,这就打开了将登录逻辑移到我们组件之外的途径。有几种方法我们可以做到这一点,最简单的可能是redux-thunk,它允许我们的动作创建者返回函数而不是对象。然后我们可以编写我们的组件来简单地调用一个输入了用户名和密码的login函数,我们的thunk负责其余部分:

import { push } from 'react-router-redux';

// action creators....
const loginStarted = () => ({
    type: 'LOGIN_STARTED'
)};

const loginFailed = (error) => ({
    type: 'LOGIN_FAILED',
    payload: {
       error
    }
};

const loginSucceeded = (user) => ({
    type: 'LOGIN_SUCCEEDED',
    payload: {
       user
    }
};

const getUserData = (access_token) => (dispatch) => {
    Api.getUserData(access_token) // however you get user data
    .then(response => {
        dispatch(loginSucceeded(response.user));
        dispatch(push(`/student/${response.user.username}`));
    });

export const login = (username, password) => (dispatch) => {
    dispatch(loginStarted());

    Api.login({ username, password }) // however you call your backend
    .then(response => {
        if (response.success && response.access_token) {
            getUserData(access_token)(dispatch);
        } else {
            dispatch(loginFailed(response.error));
        }
    });
}

你的组件唯一做的就是调用初始登录函数,它可以实现类似:

容器:

import React from 'react';
import { connect } from 'react-redux';

import { login } from '../path/to/actionCreators';
import LoginForm from './LoginForm';

const LoginContainer = React.createClass({
    handleSubmit() {
        this.props.login(
           this.usernameInput.value,
           this.passwordInput.value
        );
    },

    setUsernameRef(input) {
        this.usernameInput = input;
    },

    setPasswordRef(input) {
        this.passwordInput = input;
    },

    render() {
        return (
           <LoginForm
              handleSubmit={this.handleSubmit.bind(this)}
              setUsernameRef={this.setUsernameRef.bind(this)}
              setPasswordRef={this.setPasswordRef.bind(this)}
           />
        );
    }
});

const mapStateToProps = null;
const mapDispatchToProps = {
    login
};

export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer);

组件:

import React from 'react';

export const LoginForm = ({ handleSubmit, setUsernameRef, setPasswordRef }) => (  
    <div className="loginWrapper">
        <div className="loginBox">
            <form className="loginForm" action="">
                <input ref={setUsernameRef} type="text" placeholder="username" defaultValue="kitties@kit.com" />
                <input ref={setPasswordRef} type="text" placeholder="password" defaultValue="meowMeow3" />
                <button onClick={handleSubmit}>Login</button>
            </form>
        </div>
    </div>
);

这实现了逻辑/数据提供容器和无状态表示组件的分离。 LoginForm组件可以简单地编写为上面的函数,因为它没有处理逻辑的责任。容器也是一个非常简单的组件 - 逻辑已经在我们的动作创建者/ thunk中被隔离,并且更容易阅读和推理。

redux-thunk只是使用redux管理异步副作用的一种方法 - 还有许多其他方法有不同的方法。我个人的偏好是redux-saga,这对你来说很有意思。然而,在我自己的redux旅程中,我确实从使用和理解redux-thunk中获益,然后才发现它的局限性/缺点并继续前进,并建议这条路线给其他人。

答案 1 :(得分:1)

如果您使用的是 react-router版本4.xx :您只需呈现一个处理重定向的Redirect组件(请参阅example in react-router docs。。

import React from "react";
import { Redirect } from "react-router";

import LoginForm from "../minor/LoginForm";

const Login = React.createClass({

  componentDidUpdate(){
    const {success, access_token} = this.props.loginStatus;

    if (success === true && access_token !== null){
      console.log("It worked getting your data now...");
      this.props.getUserData(access_token);
    } else if (success === false || access_token === null){
      console.log("Something went wrong...");
    } 

  }

  render(){
    // if user is defined, redirect to /student/:username
    if (this.props.user.username !== null) {
      return (<Redirect to={ `/student/${this.props.user.username}` } />)
    }

    // otherwise render the login form
    return(
      <div className="loginComponentWrapper">
        <h1>Slots</h1>
        <LoginForm loginSubmit={this.props.loginSubmit}
                   router={this.props.router}
                   user={this.props.user} />
        <a href="/register">New User?</a>
      </div>
    )
  }
});

如果您使用 react-router版本3.x.x :您执行此操作的方式大多正确。我会将重定向从LoginForm组件移到Login组件;那样LoginForm不需要依赖user道具。

我知道,我也不喜欢这种模式。

希望这有帮助!