环境
react:16.13.1
react-router:5.2.0参考文章
history.listen
场景 1:在查询页面,修改查询表单后,刷新数据.然后跳转页面再返回,需要把之前的表单数据还原
场景 2:在其他的业务页面,点击带有参数的链接,在跳转后需要将带过来的参数设置的到表单中
方案 1(放弃)
使用动态路由,例如:/list/:type/:cat/:page
.这种方式不适合,因为参数的数量不确定,而且查询时允许不带参数
方案 2
使用search
参数.该方案可以解决方案 1 的问题
实现
- 使用类装饰器.
- 允许使用默认值
- 自动监听
search
变化 - 将
search
转化为对象,方便使用 - 提供
search
字符串和{key:value}
对象的互转工具
代码
方案中没有使用history.listen
,因为无法获取旧的location
,从而判断路由中的search
是否变化(可能是其他的变化)
src/utils/search-listener.js
import { withRouter } from 'react-router-dom'
/**
* @desc 将Location中的search字符串转换为对象
* @param {string} search
*/
export function getSearchObject(search = '') {
const result = {}
if (search) {
const searchStr = search.split('?')[1]
const searchKeyValueArr = searchStr.split('&')
for (let i = 0; i < searchKeyValueArr.length; i++) {
const [key, value] = searchKeyValueArr[i].split('=')
result[key] = decodeURIComponent(value)
}
}
return result
}
/**
* @desc 将对象转化为search字符串
* @param {object} obj
*/
export function objectToSearch(obj = {}) {
let searchStr = ''
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = encodeURIComponent(obj[key])
searchStr += `${key}=${value}&`
}
}
return searchStr ? '?' + searchStr.slice(0, -1) : ''
}
/**
* @desc 可监听search变化的装饰器
*
* @desc 限制:
* @desc state.search用于存放参数对象,不能命名冲突
* @desc onRouteSearchUpdate用于监听更新,不能命名冲突
*
* @param {boolean?} listenOnDidMount 是否在组件初始化时就触发'onRouteSearchUpdate'
*/
export default function withSearchListener(listenOnDidMount = true) {
let initSearch = {}
return WrappedComponent =>
withRouter(
class extends WrappedComponent {
componentDidMount() {
// 初始化默认的search
initSearch = this.state.search || {}
if (typeof WrappedComponent.prototype.componentDidMount === 'function') {
WrappedComponent.prototype.componentDidMount.call(this)
}
if (listenOnDidMount) {
this.onRouteSearchUpdate(getSearchObject(this.props.location.search), this.props.location)
}
}
componentDidUpdate(prevProps) {
if (typeof WrappedComponent.prototype.componentDidUpdate === 'function') {
WrappedComponent.prototype.componentDidUpdate.call(this)
}
if (prevProps.location.search !== this.props.location.search) {
this.onRouteSearchUpdate(getSearchObject(this.props.location.search), this.props.location)
}
}
/**
* @desc 当路由中的'search'更新时触发
* @param {string?} search
* @param {object?} location
*/
onRouteSearchUpdate(search = {}, location = {}) {
// 根据默认的search来合并出新的search
const nextSearch = { ...initSearch, ...search }
this.setState({ search: nextSearch }, () => {
if (typeof WrappedComponent.prototype.onRouteSearchUpdate === 'function') {
WrappedComponent.prototype.onRouteSearchUpdate.call(this, nextSearch, location)
}
})
}
}
)
}
使用
import React, { Component } from 'react'
import withSearchListener from '@/utils/search-listener'
@withSearchListener()
class Page extends Component {
state = { search: { a: '1', b: '1' } }
onRouteSearchUpdate(search = {}, location = {}) {
console.log('search updated', search, location)
}
render() {
return (
<div>
<h1>Search route</h1>
<h2>search :{JSON.stringify(this.state.search)}</h2>
</div>
)
}
}
完整的例子
/**
* @name SearchRoute
* @author darcrand
* @desc
*/
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import withSearchListener, { objectToSearch } from '@/utils/search-listener'
async function apiGetData(params = {}) {
console.log('apiGetData', params)
return Array(10)
.fill(0)
.map(_ => ({ id: Math.random(), title: `title - ${~~(Math.random() * 100)}` }))
}
const optionsA = Array(5)
.fill(0)
.map((_, i) => ({ value: String(i + 1), label: `A-${i + 1}` }))
const optionsB = Array(5)
.fill(0)
.map((_, i) => ({ value: String(i + 1), label: `B-${i + 1}` }))
@withSearchListener()
class SearchRoute extends Component {
state = { search: { a: '1', b: '1' }, list: [] }
onParamsChange = (field = {}) => {
const { search } = this.state
this.props.history.replace('/search-route' + objectToSearch({ ...search, ...field }))
}
onRouteSearchUpdate(search = {}, location = {}) {
console.log('onRouteSearchUpdate', search, location)
this.getData()
}
getData = async () => {
const res = await apiGetData(this.state.search)
this.setState({ list: res })
}
render() {
return (
<>
<h1>拥有 路由监听功能的组件</h1>
<p>通过链接跳转</p>
<ul>
<li>
<Link replace to='/search-route'>
空参数
</Link>
</li>
<li>
<Link replace to='/search-route?a=3'>
参数 a
</Link>
</li>
<li>
<Link replace to='/search-route?b=2'>
参数 b
</Link>
</li>
<li>
<Link replace to='/search-route?a=4&b=3'>
参数 ab
</Link>
</li>
</ul>
<p>通过表单参数模拟跳转</p>
<section>
{optionsA.map(v => (
<button key={v.value}>
<label>
<input
type='radio'
name='a'
value={v.value}
checked={this.state.search.a === v.value}
onChange={e => this.onParamsChange({ a: e.target.value })}
/>
<span>{v.label}</span>
</label>
</button>
))}
</section>
<section>
{optionsB.map(v => (
<button key={v.value}>
<label>
<input
type='radio'
name='b'
value={v.value}
checked={this.state.search.b === v.value}
onChange={e => this.onParamsChange({ b: e.target.value })}
/>
<span>{v.label}</span>
</label>
</button>
))}
</section>
<h2>search :{JSON.stringify(this.state.search)}</h2>
<ol>
{this.state.list.map(v => (
<li key={v.id}>{v.title}</li>
))}
</ol>
</>
)
}
}
export default SearchRoute
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。