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