您可以只使用可选参数并通过更改来处理组件中的查询或缺少查询,而不是分割路由<Route path="/search/:search" component={Search} />
to <Route path="/search/:search?" component={Search} />
并删除<Route exact path='/search' component={() => <Search query={props.text} />} />
完全。
进行此更改后,您可以通过查看值来获取当前查询props.match.params.search
在这个组件中。由于每次用户更改输入时您都会更新 URL,因此您无需担心在组件状态下管理它。此解决方案的最大问题是您可能希望在渲染后延迟一点搜索,否则您将在每次击键时触发调用。
编辑以回应问题更新
你是对的,如果 applyInitialResult 只是一个动作创建者,它不会是异步的或thenable的。不过,你仍然有选择。
例如,您可以更新操作创建器,以便它接受回调来处理数据获取的结果。我还没有测试过这个,所以将其视为伪代码,但这个想法可以这样实现:
动作创造者
export function applyInitialResult(
query,
page,
// additional params
signal,
onSuccess,
onFailure
// alternatively, you could just use an onFinished callback to handle both success and failure cases
){
return function(dispatch){
getFilmsFromApiWithSearchedText(query, page, signal) // pass signal so you can still abort ongoing fetches if input changes
.then(data => {
onSuccess(data); // pass data back to component here
if(page === 1){
dispatch({
type:AT_SEARCH_MOVIE.SETRESULT,
query:query,
payload:data,
})
}
})
.catch(err => {
onFailure(data); // alert component to errors
dispatch({
type:AT_SEARCH_MOVIE.FETCH_FAILED, // tell store to handle failure
query:query,
payload:data,
err
})
})
}
}
搜索电影减速器:
// save in store results of search, whether successful or not
export function searchMovieReducer(state = {}, action) => {
switch (action.type){
case AT_SEARCH_MOVIE.SETRESULT:
const {query, payload} = action;
state[query] = payload;
break;
case AT_SEARCH_MOVIE.FETCH_FAILED:
const {query, err} = action;
state[query] = err;
break;
}
}
然后,您仍然可以在触发获取操作的组件中直接获得结果/错误。虽然您仍然可以通过商店获取结果,但您可以使用这些触发器来管理initialChange
在组件状态中以避免冗余动作调度或在这些情况下可能出现的无限循环。
在这种情况下,您的搜索栏组件可能如下所示:
class SearchBar extends React.Component {
constructor(props){
this.controller = new AbortController();
this.signal = this.controller.signal;
this.state = {
fetched: false,
results: props.results // <== probably disposable based on your feedback
}
}
componentDidMount(){
// If search is not undefined, get results
if(this.props.match.params.search){
this.search(this.props.match.params.search);
}
}
componentDidUpdate(prevProps){
// If search is not undefined and different from prev query, search again
if(this.props.match.params.search
&& prevProps.match.params.search !== this.props.match.params.search
){
this.search(this.props.match.params.search);
}
}
setParams({ query }) {
const searchParams = new URLSearchParams();
searchParams.set("query", query || "");
return searchParams.toString();
}
handleChange = (event) => {
const query = event.target.value
const url = this.setParams({ query: query });
this.props.history.replace(`/search/${url}`);
}
search = (query) => {
if(!query) return; // do nothing if empty string passed somehow
// If some search occurred already, let component know there's a new query that hasn't yet been fetched
this.state.fetched === true && this.setState({fetched: false;})
// If some fetch is queued already, cancel it
if(this.willFetch){
clearInterval(this.willFetch)
}
// If currently fetching, cancel call
if(this.fetching){
this.controller.abort();
}
// Finally queue new search
this.willFetch = setTimeout(() => {
this.fetching = this.props.applyInitialResult(
query,
1,
this.signal,
handleSuccess,
handleFailure
)
}, 500 /* wait half second before making async call */);
}
handleSuccess(data){
// do something directly with data
// or update component to reflect async action is over
}
handleFailure(err){
// handle errors
// or trigger fetch again to retry search
}
render() {
return (
<div>
<input
type="text"
defaultValue={this.props.match.params.search} // <== make input uncontrolled
placeholder="Search movie..."
className={style.field}
onChange={this.handleChange.bind(this)}
/>
<div>
);
}
}
const mapStateToProps = (state, ownProps) => ({
// get results from Redux store based on url/route params
results: ownProps.match.params.search
? state.searchMovie[ownProps.match.params.search]
: []
});
const mapDispatchToProps = dispatch => ({
applyInitialResult: // however you're defining it
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(SearchBar)
EDIT 2
感谢您对您的想象进行澄清。
原因this.props.match.params
始终为空,因为它仅适用于搜索组件 - 搜索栏完全位于路由设置之外。它还渲染当前路径是否是/search/:search
, 这就是为什么withRouter
没工作。
另一个问题是您的搜索路线正在寻找该匹配参数,但您正在重定向到/search?query=foo
, not /search/foo
,因此搜索上的匹配参数也将为空。
我还认为你的管理方式initialChange
state 是导致您的搜索值保持不变的原因。您的处理程序会在输入的每个更改事件上被调用,但它会在第一次击键后自行关闭,并且在清除输入之前不会再次打开。看:
if (event.target.value === '') {
this.props.history.goBack()
this.setState({ initialChange: true }) // <== only reset in class
return;
}
...
if(event.target.value.length && this.state.initialChange){
this.setState({
initialChange:false
}, ()=> {
// etc...
})
}
这就是我建议的模式要实现的目标 - 不要立即关闭处理程序,而是为调度设置延迟并继续侦听更改,仅在用户完成输入后进行搜索。
我没有在这里复制另一个代码块,而是在codesandbox上制作了一个工作示例here解决这些问题。它仍然可以使用一些优化,但是如果您想查看我如何处理搜索页面、搜索栏和操作创建器,那么这个概念就在那里。
搜索栏还有一个切换开关,可以在两种不同的 url 格式之间切换(查询如/search?query=foo
与 match.param 类似/search/foo
)这样您就可以了解如何将每一项协调到代码中。