环境
react:16.13.1
react-router:5.2.0参考文章
history.listenreact
场景 1:在查询页面,修改查询表单后,刷新数据.而后跳转页面再返回,须要把以前的表单数据还原
场景 2:在其余的业务页面,点击带有参数的连接,在跳转后须要将带过来的参数设置的到表单中git
使用动态路由,例如:/list/:type/:cat/:page
.这种方式不适合,由于参数的数量不肯定,并且查询时容许不带参数es6
使用search
参数.该方案能够解决方案 1 的问题github
search
变化search
转化为对象,方便使用search
字符串和{key:value}
对象的互转工具方案中没有使用history.listen
,由于没法获取旧的location
,从而判断路由中的search
是否变化(多是其余的变化)api
src/utils/search-listener.js
react-router
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