不断重复调用ComponentWillMount

时间:2016-10-24 05:11:33

标签: javascript reactjs redux

我试图调用后端api来获取页面加载时用户的个人资料:

考虑以下行动:

export const GET_MY_PROFILE_START = 'GET_MY_PROFILE_START';
export const GET_MY_PROFILE_ERROR = 'GET_MY_PROFILE_ERROR';
export const GET_MY_PROFILE_SUCCESS = 'GET_MY_PROFILE_SUCCESS';

let a = 0;
export function getMyProfile() {
    a = a+1;
    window.console.log("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
    window.console.log(a);
    return dispatch => {
    window.console.log("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
        dispatch(getMyProfileStart());
    $.ajax({
            type: 'GET',
            url: getMyProfileURL,
            contentType: "application/json",
            dataType: 'json',
        }).done(function(res){
            if (!res.values || res.values.count > 0) {
                dispatch(getMyProfileSuccess(res.data))
            } else {
                dispatch(getMyProfileError())
            }
        }).fail(function(error) {
                dispatch(getMyProfileError())
        })
    }
}

function getMyProfileStart() {
    return {
        type: GET_MY_PROFILE_START,
    }
}

function getMyProfileSuccess(profile) {
    return {
        type: GET_MY_PROFILE_SUCCESS,
        profile: profile,
    }
}

function getMyProfileError() {
    return {
        type: GET_MY_PROFILE_ERROR,
    }
}

并关注reducer:

import { SET_USER_PROFILE, CLEAR_USER_PROFILE, GET_MY_PROFILE_START, GET_MY_PROFILE_ERROR, GET_MY_PROFILE_SUCCESS } from '../serActions'

export default (state = {
    loggedIn: false,
    profiledLoading: false,
    profile: {},
}, action) => {
    switch (action.type) {
        case SET_USER_PROFILE:
            return {
                loggedIn: true,
                profiledLoading: false,
                profile: {},
            }
        case CLEAR_USER_PROFILE:
            return {
                loggedIn: false,
                profiledLoading: false,
                profile: {},
            }
        case GET_MY_PROFILE_START:
            return {
                loggedIn: false,
                profiledLoading: true,
                profile: {},
            }
        case GET_MY_PROFILE_ERROR:
            return {
                loggedIn: true,
                profiledLoaded: false,
                profile: {},
            }
        case GET_MY_PROFILE_SUCCESS:
            return {
                loggedIn: true,
                profiledLoading: false,
                profile: action.profile,
            }
        default:
            return state
    }
}

以及以下组件:

class AvatarButton extends Component {
    componentWillMount() {
        window.console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
        this.props.dispatch(getMyProfile())
    }

    render() {
        //const { profile, toggleMenu, showHideMenu } = this.props
        const { user } = this.props
        window.console.log(user);
        return (
            <a className="user-button nav-button avatar-button"
                onClick={user.toggleMenu}
                onBlur={this.hideMenu.bind(this)}
            >
                <i className="glyphicon glyphicon-user"/>
            </a>
        )
    }

    hideMenu() {
        this.props.user.showHideMenu(false)
    }
}

AvatarButton.propTypes = {
    toggleMenu: PropTypes.func.isRequired,
    showHideMenu: PropTypes.func.isRequired,
}

function select(state) {
    return {
        user: state.user,
    }
}

// Wrap the component to inject dispatch and state into it
export default connect(select)(AvatarButton)

此组件用于:

class UserNavContainer extends Component {
    constructor(props) {
        super(props)
        this.state = {
            displayMenu: false,
        }
    }
    render() {
        const { dispatch, user, pathname } = this.props
        if (!user.loggedIn)
            return (
                <LoginButton pathname={pathname}/>
            )

        return (
            <div className="user-nav">
                <AvatarButton
                    toggleMenu={this.toggleMenu.bind(this)}
                    showHideMenu={this.showHideMenu.bind(this)}
                />
                <UserMenu
                    visible={this.state.displayMenu}
                    logoutHandler={this.logout.bind(this)}
                    hideMenu={this.showHideMenu.bind(this, false)}
                />
            </div>
        )
    }

    logout() {
        window.location = "some url"
    }

    toggleMenu() {
        this.showHideMenu(!this.state.displayMenu)
    }

    showHideMenu(show) {
        this.setState({
            displayMenu: show,
        })
    }
}

function select(state) {
    return {
        user: state.user,
    }
}

// Wrap the component to inject dispatch and state into it
export default connect(select)(UserNavContainer)

最后是使用UserNavContainer的顶级组件:

class AppHandler extends Component {
    componentWillMount() {
        authUser(this.authSuccess.bind(this), this.authFail.bind(this))
    }

    authSuccess() {
        this.props.dispatch(login())
    }

    authFail() {
        this.props.dispatch(logout())
    }

    render() {
        window.console.log("renderrenderrenderrenderrenderrenderrenderrenderrenderrender");
        return (
            <div className="app-container">
            <div className="top-nav row">
                <div className="col-md-8 col-md-offset-2 col-sm-10 col-sm-offset-1 col-xs-12">
                    <BackButton
                        pathname={this.props.location.pathname}
                    />
                    <UserNavContainer
                        pathname={this.props.location.pathname}
                    />
                    <div className="header">
                        <img src="https://www.wewherego.com/img/logo/logo_wherego.png"/>
                        <h1>{this.props.pageHeader}</h1>
                    </div>
                </div>
            </div>
            <ReactCSSTransitionGroup
                transitionName="example"
                transitionEnterTimeout={1000}
                transitionLeaveTimeout={1000}
            >
                {React.cloneElement(this.props.children, {
                    key: this.props.location.pathname,
                })}
            </ReactCSSTransitionGroup>
            </div>
        )
    }
}

function select(state) {
    return {
        selectedCity: state.selectedCity,
        pageHeader: state.pageHeader,
    }
}

// Wrap the component to inject dispatch and state into it
export default connect(select)(AppHandler)

在AppHandler中,它在ComponentWillMount中调用以下方法:

export function authUser(loginCb,logoutCb){     const data = readCookie('something')

if (!data) {
    logoutCb()
} else {
    loginCb()
}
return

}

export function signoutUser(){    //清理一些东西 }

当我打开页面时,我只能看到1行 renderrenderrenderrenderrenderrenderrenderrenderrenderrenderrender

但我看到日志不断打印:

UserNavContainerUserNavContainerUserNavContainerUserNavContainerUserNavContainerUserNavContainer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
1057
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
1058
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
1059
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

显然,由于某种原因,componentWillMount方法被保持被调用,而getMyProfile()中的a现在变为1059(并且仍在计数中)。

我不知道为什么会这样?

3 个答案:

答案 0 :(得分:0)

如果您要在组件生命周期方法中调度操作,则应在componentDidMount而不是willMount中执行。您似乎正在触发阻止组件安装的无限循环。

答案 1 :(得分:0)

尝试将onClick函数调用包装为 -

 onClick={() => user.toggleMenu}

答案 2 :(得分:0)

真正的问题是,在组件中,它们调度SET_USER_PROFILE事件,它将loggedIn变量更改为true,但随后应用程序调度GET_MY_PROFILE_START,这会将loggedIn变量更改为false。这将返回触发新循环并将loggedIn移至true,因此循环继续。

正确的方法是在reducer中,不使用设置变量,而是使用object.assign来更改变量的值。所以它应该是:

export default (state = {
    loggedIn: false,
    profiledLoading: false,
    profile: {},
}, action) => {
    switch (action.type) {
        case GET_MY_PROFILE_START:
            return Object.assign({}, state, {
                loggedIn: true,
                profiledLoading: true,
            })
        case CLEAR_USER_PROFILE:
            return Object.assign({}, state, {
                loggedIn: false,
            })
        case GET_MY_PROFILE_ERROR:
            return Object.assign({}, state, {
                profiledLoaded: false,
                profile: {},
            })
        case GET_MY_PROFILE_SUCCESS:
            return Object.assign({}, state, {
                profiledLoading: false,
                profile: action.profile,
            })
        default:
            return state
    }
}