环境
react:16.13.1
react-router:5.2.0

参考文章
history.listen

场景 1:在查问页面,批改查问表单后,刷新数据.而后跳转页面再返回,须要把之前的表单数据还原
场景 2:在其余的业务页面,点击带有参数的链接,在跳转后须要将带过去的参数设置的到表单中

计划 1(放弃)

应用动静路由,例如:/list/:type/:cat/:page.这种形式不适宜,因为参数的数量不确定,而且查问时容许不带参数

计划 2

应用search参数.该计划能够解决方案 1 的问题

实现

  1. 应用类装璜器.
  2. 容许应用默认值
  3. 主动监听search变动
  4. search转化为对象,方便使用
  5. 提供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