我目前在如何执行AJAX请求方面遇到问题,该请求可能由多个触发 页面上的不同UI元素。 AJAX请求始终转到相同的端点,并始终发送相同的属性 从redux存储到端点(尽管属性值可能会由于用户交互而更改)。
我完全意识到我目前的执行情况很糟糕。
为了绘制更清晰的图片,我正在构建一个搜索页面,其中多个UI元素可以触发新的搜索被激活。 有一个端点称为“ / api / search /”,该端点期望查询字符串包含从Redux Store中提取的数据。看起来像这样:
term=some%20string&categories=243,2968,292&tags=11,25,99&articleType=All
当UI元素需要触发对商店的同步更新时,还需要触发执行搜索的Thunk时,我会陷入困境。这是我的顶级组件,在其中我将“ executeSearch” Thunk函数传递给需要触发搜索的所有子组件。我最初的想法是,我可以使用一个thunk来处理所有需要执行搜索的交互,而不必为每个单独编写一个thunk。
P.S。如果对您不敏感,请不要过度分析以下代码。如果您浏览了以下部分,请阅读“三种情况”部分,这可能有助于您更好地理解所有工作原理。图片也包含在该部分中。
class App extends Component {
executeSearch = () => {
this.props.executeSearch(this.props.store); // This is my Thunk
};
render() {
const { updateSearchTerm, clearAll, dropdownActive, dropdownType } = this.props;
return (
<section className="standard-content">
<div className="inner-container narrow">
<div className="centered">
<h1>Search</h1>
<h2 className="alt">Search our extensive database of research articles.</h2>
</div>
<SearchTerm initSearch={this.executeSearch} updateSearchTerm={updateSearchTerm} />
<ExtraOptions clearAll={clearAll} />
<Filters executeSearch={this.executeSearch} />
</div>
{dropdownActive ? (
dropdownType === 'categories' ? (
<CategoryDropdown executeSearch={this.executeSearch} />
) : (
<TagDropdown executeSearch={this.executeSearch} />
)
) : null}
<SearchResults />
</section>
);
}
}
const mapStateToProps = state => {
return {
store: state,
dropdownActive: state.dropdownActive,
dropdownType: state.dropdownType
};
};
executeSearch函数从存储中获取所有值,但仅使用本期开始时概述的值。如果有帮助的话,这篇文章的底部有整个redux存储的代码示例。无论如何,Thunk的外观如下:
export const executeSearch = criteria => {
const searchQueryUrl = craftSearchQueryUrl(criteria);
// if (term === '' && !selectedCategories && !selectedTags && articleType === 'All') {
// return { type: ABORT_SEARCH };
// }
return async dispatch => {
dispatch({ type: FETCH_SEARCH_RESULTS });
try {
const res = await axios.post(`${window.siteUrl}api/search`, searchQueryUrl);
dispatch({ type: FETCH_SEARCH_RESULTS_SUCCESS, searchResults: res.data });
} catch (err) {
dispatch({ type: FETCH_SEARCH_RESULTS_FAILED });
}
};
};
// Helper function to craft a proper search query string
const craftSearchQueryUrl = criteria => {
const { term, articleType, selectedCategories, selectedTags } = criteria;
let categoriesString = selectedCategories.join(',');
let tagsString = selectedTags.join(',');
return `term=${term}&articleType=${articleType}&categories=${categoriesString}&tags=${tagsString}&offset=${offset}`;
};
请记住,这里的“条件”参数是我作为App.js内部参数传递的整个商店对象。您将看到,我只在craftSearchQueryUrl函数内部使用所需的属性。
我提供了一个屏幕快照(标有字母),希望在其中解释在什么地方有效,在哪些地方无效。
A。)用户应该能够填写此文本字段,并且当他们按放大镜时,它应该触发Thunk。之所以能正常工作,是因为每次击键时都会在商店中更新文本字段中的值,这意味着商店中的值始终是最新的,甚至用户甚至没有机会按下放大镜。
B。)默认情况下,初始页面加载时选中了“全部”复选框。如果用户单击旁边列出的其他复选框之一,则应立即导致启动搜索。这是我的问题开始发生的地方。这是我目前对此代码的要求:
export default ({ handleCheckboxChange, articleType, executeSearch }) => (
<div style={{ marginTop: '15px', marginBottom: '20px' }}>
<span className="search-title">Type: </span>
{articleTypes.map(type => (
<Checkbox
key={type}
type={type}
handleCheckboxChange={() => {
handleCheckboxChange('articleType', { type });
executeSearch();
}}
isChecked={type === articleType}
/>
))}
</div>
);
当复选框更改时,它将更新商店中的articleType值(通过handleCheckboxChange),然后执行从App.js传递来的搜索功能。但是,更新后的articleValue类型不是更新后的,因为我相信在商店有机会更新此值之前会调用搜索功能。
C。)这里也发生了来自B的相同问题。当您单击“ Refine by”部分中的按钮之一(类别或标签)时,将显示此下拉列表并带有多个复选框。我实际上存储了哪些复选框在本地状态下被选中/取消选中,直到用户单击“保存”按钮。按下保存按钮后,应在商店中更新新选中/未选中的复选框值,然后应通过从App.js传递的Thunk启动新搜索。
export default ({ children, toggleDropdown, handleCheckboxChange, refineBy, executeSearch }) => {
const handleSave = () => {
handleCheckboxChange(refineBy.classification, refineBy);
toggleDropdown('close');
executeSearch(); // None of the checkbox values that were changed are reflected when the search executes
};
return (
<div className="faux-dropdown">
<button className="close-dropdown" onClick={() => toggleDropdown('close')}>
<span>X</span>
</button>
<div className="checks">
<div className="row">{children}</div>
</div>
<div className="filter-selection">
<button className="refine-save" onClick={handleSave}>
Save
</button>
</div>
</div>
);
};
重要的是要注意,执行搜索时使用的值不是B和C的更新值,但实际上它们在商店内部已正确更新。
我的另一个想法是也许要创建一个redux中间件,但是说实话,在尝试其他任何事情之前,我真的可以在此方面使用一些专家帮助。理想情况下,公认的解决方案应该是详尽的解释, 并包括一个解决方案,该解决方案在处理Redux应用程序时会考虑最佳架构实践。也许我只是在做一些根本错误的事情。
如果有帮助,这里是我的完整商店(处于初始状态)的样子:
const initialState = {
term: '',
articleType: 'All',
totalJournalArticles: 0,
categories: [],
selectedCategories: [],
tags: [],
selectedTags: [],
searchResults: [],
offset: 0,
dropdownActive: false,
dropdownType: '',
isFetching: false
};
答案 0 :(得分:1)
App
中的以下块是问题的症结所在:
executeSearch = () => {
this.props.executeSearch(this.props.store); // This is my Thunk
};
此executeSearch
方法在渲染store
时已将App
引入其中。
当您这样做时:
handleCheckboxChange('articleType', { type });
executeSearch();
到executeSearch
时,您的Redux存储将被同步更新,但这会导致一个新的store
对象,这将导致App
重新呈现,但是不会影响store
正在使用的executeSearch
对象。
正如其他人在评论中指出的那样,我认为处理此问题的最直接方法是使用中间件,该中间件提供一种机制来执行副作用,以响应商店更新后的还原动作。我个人会为此目的推荐Redux Saga,但我知道还有其他选择。在这种情况下,您将拥有一个传奇,可以监视应该触发搜索的任何操作,然后传奇是唯一调用executeSearch
的事物,并且传奇可以传递更新的store
到executeSearch
。
答案 1 :(得分:0)
使用Ryan Cogswell解决方案非常出色。我能够删除所有使事情变得混乱的代码,并且不再需要担心将某些状态从商店传递给我的异步操作创建者,因为我现在正在使用Redux Saga。
我重构的App.js现在看起来像:
class App extends Component {
render() {
const {
updateSearchTerm,
clearAll,
dropdownActive,
dropdownType,
fetchSearchResults
} = this.props;
return (
<section className="standard-content">
<div className="inner-container narrow">
<div className="centered">
<h1>Search</h1>
<h2 className="alt">Search our extensive database of research articles.</h2>
</div>
<SearchTerm fetchSearchResults={fetchSearchResults} updateSearchTerm={updateSearchTerm} />
<ExtraOptions clearAll={clearAll} />
<Filters />
</div>
{dropdownActive ? (
dropdownType === 'categories' ? (
<CategoryDropdown />
) : (
<TagDropdown />
)
) : null}
<SearchResults />
</section>
);
}
}
我要做的就是将fetchSearchResults操作创建者导入到我需要的任何Redux连接的组件中,并确保它已执行。例如:
export default ({ handleCheckboxChange, articleType, fetchSearchResults }) => (
<div style={{ marginTop: '15px', marginBottom: '20px' }}>
<span className="search-title">Type: </span>
{articleTypes.map(type => (
<Checkbox
key={type}
type={type}
handleCheckboxChange={() => {
handleCheckboxChange('articleType', { type });
fetchSearchResults();
}}
isChecked={type === articleType}
/>
))}
</div>
);
这是动作创建者现在的样子...
export const fetchSearchResults = () => ({ type: FETCH_SEARCH_RESULTS });
这使您的动作创作者可以纯粹而又超级简单地进行推理。每当在我的UI的任何部分中分派此操作时,我的传奇都会选择此操作,并使用商店的最新版本进行搜索。
这是使它起作用的传奇:
import { select, takeLatest, call, put } from 'redux-saga/effects';
import { getSearchCriteria } from '../selectors';
import { Api, craftSearchQueryUrl } from '../utils';
import {
FETCH_SEARCH_RESULTS,
FETCH_SEARCH_RESULTS_SUCCESS,
FETCH_SEARCH_RESULTS_FAILED
} from '../actions';
function* fetchSearchResults() {
const criteria = yield select(getSearchCriteria);
try {
const { data } = yield call(Api.get, craftSearchQueryUrl(criteria));
yield put({ type: FETCH_SEARCH_RESULTS_SUCCESS, searchResults: data });
} catch (err) {
yield put({ type: FETCH_SEARCH_RESULTS_FAILED, error: err });
}
}
export default function* searchResults() {
yield takeLatest(FETCH_SEARCH_RESULTS, fetchSearchResults);
}