ComponentWillMount一直被重复调用

ComponentWillMount keeps being called repeately

本文关键字:调用 一直 ComponentWillMount      更新时间:2023-09-26

我正在尝试调用后端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,
    }
}

及以下减速器:

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中的以下方法:

导出authUser(loginCb, logoutCb) {const data = readCookie('something')

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

}

导出函数signoutUser() {//清理一些东西}

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

但是我看到日志一直在打印:

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

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

我不知道为什么会发生这种情况?

如果你在组件生命周期方法中调度动作,你应该在componentDidMount而不是willMount中执行。看来你正在触发一个无限循环,阻止组件安装。

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

 onClick={() => user.toggleMenu}

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

正确的做法是在reducer中使用object而不是设置变量。赋值以更改变量的值。所以应该是:

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