Ionic/React/TypeScript,使用 react-router history.push、history.replace 和 history.goBack 动画触发两次

Posted

技术标签:

【中文标题】Ionic/React/TypeScript,使用 react-router history.push、history.replace 和 history.goBack 动画触发两次【英文标题】:Ionic/React/TypeScript,using react-router history.push, history.replace and history.goBack Animation triggers twice 【发布时间】:2020-09-29 14:18:19 【问题描述】:

所以基本上我正在开发一个 Ionic/React Typescript 应用程序,当我浏览该应用程序时会出现两次奇怪的页面转换(参见下面的 gif)

我检查了一下,不是渲染调用了两次,因为 componentWillMount/didMount/willUnmount 都像推送页面和推送页面一样触发一次。

如您所见,转换总是发生两次,并且在任何地方都找不到解决方案... 使用的版本: - 离子:5.2.1 - 反应:16.9.0(npm 包) - Typescript 3.6.3(npm 包)


Here is the code for the page with the "Diplomes" Title:
class Diplome extends React.Component<RouteComponentProps> 

  // Function who redirect to the url for edit an situation
  redirectUrlToEditSituation = () => 
    this.props.history.push('/app/edit/diplome/', null);
  

  render() 
    return (
      <IonPage>
          <IonHeader>
            <IonToolbar>
                <ButtonHambToolBar />
                <ToolBarLogo toolbarName=ToolBarName.DIPLOME/>
            </IonToolbar>
          </IonHeader>
          <IonContent>
            <FabButtonAction ClickHandler=() => this.redirectUrlToEditSituation() icon=add/>
            <GenericList type=ModelType.DIPLOME/>
          </IonContent>
      </IonPage>
    );
  


export default (Diplome);

这是点击按钮时带有“版本”标题的页面:

class DiplomeEdit extends Component<DiplomeEditProps, 
    dataIsLoad: boolean,
    label_field: string,
    isAnCreate: boolean,
    openModalDelete: boolean,
    isValidation: boolean,
    currentDiplome: any
>


    constructor(props: DiplomeEditProps) 
        super(props);
        this.state = 
            label_field: '',
            isAnCreate: true,
            openModalDelete: false,
            dataIsLoad: false,
            currentDiplome: '',
            isValidation: false,
        ;
    

    async componentWillMount() 
        console.log("component will mount");
        await this.getCurrentDiplomeToUpdate();
    

    // Function who check if they are param on the url
    // If param exist get the current diplome on the store
    getCurrentDiplomeToUpdate = async () => 
        if (this.props.match.params.idDiplome !== undefined) 

            const diplomes: DiplomeInterface[] = this.props.diplomes;
            if (diplomes.length > 0) 
                const currentDiplomeReceive: DiplomeInterface | undefined = diplomes.find((res: DiplomeInterface) => 
                    return this.props.match.params.idDiplome === res.idDiplome;
                );

                if (currentDiplomeReceive !== undefined) 
                    this.setState(
                        isAnCreate: false,
                        label_field: currentDiplomeReceive.labelDiplome,
                        currentDiplome: currentDiplomeReceive,
                        dataIsLoad: true,
                    );
                
            

         else 
            this.setState(
                isAnCreate: true,
                dataIsLoad: true,
            );
        
    

    // Function who render update button
    renderUpdateButton = () => 
        return (
            <div className="contenaire_button_action_situation_edit">
                <Button hidden=this.state.isAnCreate
                    onClick=() => this.openOrCloseModalDeleteDialog()
                    className="button_delete_situation">Supprimer</Button>
                <Button
                    onClick=e => this.actionClick()
                    className="button_action_situation_edit">Enregistrer</Button>
            </div>
        );
    

    // Function who delete diplome on the databse and on the store
    removeDiplome = async () => 
        await REQUEST.deleteDiplome(this.state.currentDiplome.idDiplome);
        store.dispatch(DELETE_DIPLOME(this.state.currentDiplome.idDiplome));
        this.props.history.replace('/app/diplomes');
    

    // Function who get the label and id for create new diplome on the databae
    // Reset the state to the default init
    saveDiplome = async () => 
        const obj: DiplomeInterface = 
            idDiplome: Math.random().toString(36).substr(2, 9),
            labelDiplome: this.state.label_field.toLowerCase(),
        ;
        this.setState(
            label_field: '',
            isAnCreate: false,
            openModalDelete: false,
        );
        await REQUEST.postDiplome(obj);
        store.dispatch(ADD_DIPLOME(obj));
        //this.props.history.replace('/app/diplomes');
        window.smartAlert("Diplome ajouté avec succès", "success", 5000);
        this.props.history.goBack();
    

    // Function who update a diplome on the store
    // Need the current diplome
    // Label update
    updateDiplome = async () => 
        const currentDiplomeReceive: DiplomeInterface = this.state.currentDiplome;

        const obj: DiplomeInterface = 
            idDiplome: currentDiplomeReceive.idDiplome,
            labelDiplome: this.state.label_field,
        ;

        if (this.props.userConnected.typeAccount === TypeConnect.ADMIN) 
            await REQUEST.updateDiplome(obj);
            store.dispatch(UPDATE_DIPLOME(obj));
            this.props.history.replace('/app/diplomes');
        
    

    // Function who checked what action we need
    actionClick = () => 
        if (this.state.isAnCreate) 
            this.saveDiplome();
         else 
            this.updateDiplome();
        
    

    // Function call when input change
    inputChange = (e: any) => 
        this.setState(
            label_field: e.target.value,
        );
    

    // Function who change the openModalDelete to true
    openOrCloseModalDeleteDialog = () => 
        if (this.props.userConnected.typeAccount === TypeConnect.ADMIN) 
            this.setState(
                openModalDelete: !this.state.openModalDelete,
            );
        
    

    render() 

        return (
            <IonPage>
                <IonHeader>
                    <IonToolbar className="task_cat_toolbar">
                        <IonBackButton
                            className="situation_edit_back_button" />
                        <ToolBarLogo toolbarName=ToolBarName.EDIT />
                    </IonToolbar>
                </IonHeader>

                <IonContent>
                    <div className="contenaire_edit">
                        <div className="contenaire_form_situation">
                            <div className="contenaire_field">
                                <TextField
                                    onChange=e => this.inputChange(e)
                                    value=this.state.label_field
                                    className="field_form_situation"
                                    label="Libelle" />
                            </div>

                            <div className="container_task_situation_edit">

                                !this.state.dataIsLoad && <SpinnerCircular />

                                this.renderUpdateButton()

                                
                                    this.state.openModalDelete &&
                                    <DialogDelete
                                        actionRemove=() => this.removeDiplome()
                                        open=this.state.openModalDelete
                                        actionCloseModal=() => this.openOrCloseModalDeleteDialog() />
                                

                            </div>
                        </div>
                    </div>
                </IonContent>

            </IonPage>
        );
    



const mapStateToProps = (state: DiplomeEditProps) => (
    diplomes: state.diplomes,
    network: state.network,
    role: state.role,
    userConnected: state.userConnected,
);

export default connect(mapStateToProps)(DiplomeEdit);

【问题讨论】:

你没有github链接? @bbortt 很抱歉,如果您认为自己知道这个问题,请随时提出问题^^ 您正在componentWillMount 中进行异步调用。如果你把它移到componentDidMount 会发生什么? async/await 代码在内部工作的方式可能会让您感到困惑。 @rob3c 感谢您的反馈,但没有删除异步不会改变任何东西,console.log(),每次触发一次,无论是否有异步。 @AlexFrau 澄清一下,您是说您从componentWillMount 中删除了对getCurrentDiplomeToUpdate 的调用并将其移至从componentDidMount 调用,但您仍然有同样的问题吗?从您的回复中我不清楚您是否具体尝试过。 【参考方案1】:

您不能将 componentWillMount 生命周期方法声明为异步。

来自React docs about UNSAFE_componentWillMount()

Avoid introducing any side-effects or subscriptions in this method.
For those use cases, use componentDidMount() instead.

和React blog "Update on Async Rendering" (03/2018):

One of the biggest lessons we’ve learned is that some of our legacy component
lifecycles tend to encourage unsafe coding practices. They are:

    componentWillMount
    componentWillReceiveProps
    componentWillUpdate

These lifecycle methods have often been misunderstood and subtly misused; 
furthermore, we anticipate that their potential misuse may be more problematic
with async rendering. Because of this, we will be adding an “UNSAFE_” prefix
to these lifecycles in an upcoming release. (Here, “unsafe” refers not to 
security but instead conveys that code using these lifecycles will be more 
likely to have bugs in future versions of React, especially once async 
rendering is enabled.)

Victoria Fluharty 就该主题撰写了有用的帖子 componentWillMount() vs componentDidMount()。

tl;dr:如果您有异步逻辑,则需要将其移至 componentDidMount()(或迁移到更现代的方法,例如使用 React hooks 的功能组件,而不是部分弃用的生命周期方法。

您可以将componentDidMount() 声明为async,但可能会有一些注意事项:

Is using async componentDidMount() good? How To Use Async Await in React (componentDidMount Async)

【讨论】:

好的,非常有帮助!我会用你给我的链接调查!我会及时通知你! 感谢您对我三天前在评论中提供的答案提供详细的确认?

以上是关于Ionic/React/TypeScript,使用 react-router history.push、history.replace 和 history.goBack 动画触发两次的主要内容,如果未能解决你的问题,请参考以下文章

第一篇 用于测试使用

在使用加载数据流步骤的猪中,使用(使用 PigStorage)和不使用它有啥区别?

今目标使用教程 今目标任务使用篇

Qt静态编译时使用OpenSSL有三种方式(不使用,动态使用,静态使用,默认是动态使用)

MySQL db 在按日期排序时使用“使用位置;使用临时;使用文件排序”

使用“使用严格”作为“使用强”的备份