环境
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