NextJS开发H5项目使用弹框模拟实现页面路由监听路由变化

举报
楚楚冻人玥玥仙女 发表于 2021/11/09 21:57:56 2021/11/09
【摘要】 背景:我现在开发的项目是一个H5的项目,使用的框架是NextJS,NextJS是SSR服务端渲染模式,每次使用Router.push进入下一个路由后,如果再返回到上一个页面,上一个页面的状态就会丢失,就相当于重刷了页面。而我现在的需求中,有很多这种场景,A页面是一个表单页,需要填写很多数据,而其中有列表数据,需要点击新增按钮,跳转到下一个页面B,去新增/修改填写列表的每一行数据,而使用Rou...

背景:我现在开发的项目是一个H5的项目,使用的框架是NextJS,NextJS是SSR服务端渲染模式,每次使用Router.push进入下一个路由后,如果再返回到上一个页面,上一个页面的状态就会丢失,就相当于重刷了页面。

而我现在的需求中,有很多这种场景,A页面是一个表单页,需要填写很多数据,而其中有列表数据,需要点击新增按钮,跳转到下一个页面B,去新增/修改填写列表的每一行数据,而使用Router.push进入B页面后,填完了信息,此时点击保存,应该带着数据回到A页面,这时候如果使用Router.back方法的话,回到A页面,A页面刚才填过的数据不会存在,因为此时路由发生了变化,就相当于这个页面重新加载了,这不是我们想要的效果。

我看RN开发的同事他们的路由有回调方法,路由进入下一个页面后,在下一个页面填好内容,关闭了后,上一个页面能取到下一个页面带回来的数据,并且因为他们RN那种图层式,跳转新页面,原本的页面并没有销毁,只是处于不可见状态,等上层的页面关闭了,原本的页面又会被激活。

而在这种NextJS-SSR模式下,每次路由的跳转,都是销毁了原页面,进入下一个页面。而路由回退就是销毁当前页面加载之前的页面。

为了解决这个问题,我跟同事们商量了一下,有了三个方案:

  1. 改为CSR渲染,这样页面不会因为路由跳转而销毁,但数据依旧要存储下来,可以存到全局Store或本地存储;
  2. 依旧使用SSR,在页面变动时,使用本地存储来将填写的数据存下来,每次加载页面时先从本地存储里加载数据,但会随着以后表单项的增多以及逻辑的变化,维护成本高;
  3. 使用弹框来模拟RN那种图层式页面,跳进下一个页面时,打开一个像页面一样的弹框,并且有合理的路由处理逻辑。在返回时,关闭弹框,这样原页面的数据完全不用变动,不需要做多余的处理。复杂在样式处理,还有如果有多次嵌套页面的话,弹框层叠可能会出问题。

经过思考和对比,我感觉第三种方案更加适合我现在的场景。

我将这个弹框命名为PageModal,意味着这是个像路由页面一样的Modal

注意点:

  1. 样式:需要将PageModal展示的效果和其他路由页面展示的效果一模一样,让用户感知不出来这是一个弹框而是一个页面;
  2. 交互:当PageModal打开时,用户在手机上触发返回事件,我们需要关闭这个PageModal,而不是让它前一个页面发生返回操作,也就是打开PageModal的时候,要触发history的路由入栈,在手动关闭PageModal的时候,要触发路由出栈,而此时还要监听是否触发了返回事件,,也就是PageModal还得监听popstate事件,如果popstate事件触发了,也需要关闭PageModal。

PageModal.tsx

import React, { useEffect } from 'react'
import ReactDOM from 'react-dom'
import { NavBar } from 'antd-mobile'
/*注意:这里是引入了我们项目自己对NextJS二次封装的组件,如需使用,可以直接引用Next官方的依赖*/
import { NextFC } from '***'
import Router from '***'

export interface PageModalProps {
  [key: string]: any
  visible: boolean
  title: string
  onClose: () => void
}

const PageModal: NextFC<PageModalProps> = (props: PageModalProps) => {
  const { visible, title, onClose, children } = props
  const nextApp = document.getElementById('__next')
  const modalRoot = document.getElementById('modal-root')
  const mo = function (e) {
    e.preventDefault()
  }
  function show() {
    nextApp.style.overflow = 'hidden'
    modalRoot.addEventListener('touchmove', mo, false)
  }

  function close() {
    nextApp.style.overflow = ''
    modalRoot.removeEventListener('touchmove', mo, false)
    onClose()
  }

  useEffect(() => {
    visible ? show() : close()
  }, [visible])

  useEffect(() => {
    window.addEventListener('popstate', close, false)
    return () => {
      window.removeEventListener('popstate', close, false)
    }
  }, [])

  if (!visible) return null
  return ReactDOM.createPortal(
    <div className="open-modal">
      <NavBar
        onBack={Router.back}
        {...props}
        back={props.back || '返回'}
        style={{ backgroundColor: '#4B8AF6', color: '#FFFFFF' }}
      >
        {title}
      </NavBar>
      <div className="bottom-navbar">{children}</div>
    </div>,
    modalRoot,
  )
}

export default PageModal

使用处:

const [visible, setVisible] = useState(false)
handleAdd() {
  window.history.pushState(null, null, window.document.URL)
  setVisible(true)
  form.resetFields()
},

async handleSubmit() {
 // 此处省略数据处理
 
  	setVisible(false)
}

handleBack() {
	setVisible(false)	
}
<Button block onClick={handleAdd}>
  添加
</Button>
<PageModal title={`添加`} visible={visible} onClose={ctx.handleBack}>
   <Form
     layout="horizontal"
     form={form}
     footer={
       <div className="form-footer-two-btn-group">
         <Button block onClick={handleBack}>
           取消
         </Button>
         <Button block onClick={handleSubmit}>
           保存
         </Button>
       </div>
     }
   >
     <Form.Item
       name="name"
       label="测试名称"
       rules={[{ required: true, message: '请填写' }]}
     >
       <Input />
     </Form.Item>
   </Form>
 </PageModal>
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。