NextJS开发H5项目使用弹框模拟实现页面路由监听路由变化
【摘要】 背景:我现在开发的项目是一个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模式下,每次路由的跳转,都是销毁了原页面,进入下一个页面。而路由回退就是销毁当前页面加载之前的页面。
为了解决这个问题,我跟同事们商量了一下,有了三个方案:
- 改为CSR渲染,这样页面不会因为路由跳转而销毁,但数据依旧要存储下来,可以存到全局Store或本地存储;
- 依旧使用SSR,在页面变动时,使用本地存储来将填写的数据存下来,每次加载页面时先从本地存储里加载数据,但会随着以后表单项的增多以及逻辑的变化,维护成本高;
- 使用弹框来模拟RN那种图层式页面,跳进下一个页面时,打开一个像页面一样的弹框,并且有合理的路由处理逻辑。在返回时,关闭弹框,这样原页面的数据完全不用变动,不需要做多余的处理。复杂在样式处理,还有如果有多次嵌套页面的话,弹框层叠可能会出问题。
经过思考和对比,我感觉第三种方案更加适合我现在的场景。
我将这个弹框命名为PageModal,意味着这是个像路由页面一样的Modal
注意点:
- 样式:需要将PageModal展示的效果和其他路由页面展示的效果一模一样,让用户感知不出来这是一个弹框而是一个页面;
- 交互:当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)