关于javascript:reactrouter-基于路由参数的表单数据缓存

78次阅读

共计 4555 个字符,预计需要花费 12 分钟才能阅读完成。

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

正文完
 0