我有以下代码:
import React, { useState, useEffect } from "react"
function callSearchApi(userName: string, searchOptions: SearchOptions, searchQuery: string): Promise<SearchResult>{
return new Promise((resolve, reject) => {
const searchResult =
searchOptions.fooOption
? ["Foo 1", "Foo 2", "Foo 3"]
: ["Bar 1", "Bar 2"]
setTimeout(()=>resolve(searchResult), 3000)
})
}
type SearchOptions = {
fooOption: boolean
}
type SearchResult = string[]
export type SearchPageProps = {
userName: string
}
export function SearchPage(props: SearchPageProps) {
const [isSearching, setIsSearching] = useState<boolean>(false)
const [searchResult, setSearchResult] = useState<SearchResult>([])
const [searchOptions, setSearchOptions] = useState<SearchOptions>({fooOption: false})
const [searchQuery, setSearchQuery] = useState<string>("")
const [lastSearchButtonClickTimestamp, setLastSearchButtonClickTimestamp] = useState<number>(Date.now())
// ####################
useEffect(() => {
setIsSearching(true)
setSearchResult([])
const doSearch = () => callSearchApi(props.userName, searchOptions, searchQuery)
doSearch().then(newSearchResult => {
setSearchResult(newSearchResult)
setIsSearching(false)
}).catch(error => {
console.log(error)
setIsSearching(false)
})
}, [lastSearchButtonClickTimestamp])
// ####################
const handleSearchButtonClick = () => {
setLastSearchButtonClickTimestamp(Date.now())
}
return (
<div>
<div>
<label>
<input
type="checkbox"
checked={searchOptions.fooOption}
onChange={ev => setSearchOptions({fooOption: ev.target.checked})}
/>
Foo Option
</label>
</div>
<div>
<input
type="text"
value={searchQuery}
placeholder="Search Query"
onChange={ev => setSearchQuery(ev.target.value)}
/>
</div>
<div>
<button onClick={handleSearchButtonClick} disabled={isSearching}>
{isSearching ? "searching..." : "Search"}
</button>
</div>
<hr/>
<div>
<label>Search Result: </label>
<input
type="text"
readOnly={true}
value={searchResult}
/>
</div>
</div>
)
}
export default SearchPage
另请参阅this Codesandbox。
代码工作正常。我可以在文本字段中更改搜索查询,然后单击选项复选框。准备好之后,我可以单击“搜索”按钮,然后才通过获取数据产生副作用。
现在,问题在于编译器抱怨:
React Hook useEffect缺少依赖项:“ props.user.loginName”,“ searchFilter”和“ searchQuery”。包括它们或删除依赖项数组。 [反应钩/穷尽滴水]
但是,如果我在依赖项列表中添加props.user.loginName
,searchFilter
和searchQuery
,则每当我单击复选框或在文本字段中键入单个字符时,就会触发副作用
我确实了解了钩子依赖项的概念,但是我不知道如何首先输入一些数据,仅通过单击按钮即可触发副作用。
最佳做法是什么?我已经读过https://reactjs.org/docs/hooks-effect.html和https://www.robinwieruch.de/react-hooks-fetch-data,但找不到任何有关我的问题的例子。
更新1:
我还提出了this solution,它看起来像:
type DoSearch = {
call: ()=>Promise<SearchResult>
}
export function SearchPage(props: SearchPageProps) {
const [isSearching, setIsSearching] = useState<boolean>(false)
const [searchResult, setSearchResult] = useState<SearchResult>([])
const [searchOptions, setSearchOptions] = useState<SearchOptions>({fooOption: false})
const [searchQuery, setSearchQuery] = useState<string>("")
const [doSearch, setDoSearch] = useState<DoSearch>()
// ####################
useEffect(() => {
if(doSearch !==undefined){
setIsSearching(true)
setSearchResult([])
doSearch.call().then(newSearchResult => {
setSearchResult(newSearchResult)
setIsSearching(false)
}).catch(error => {
console.log(error)
setIsSearching(false)
})
}
}, [doSearch])
// ####################
const handleSearchButtonClick = () => {
setDoSearch({call: () => callSearchApi(props.userName, searchOptions, searchQuery)})
}
return (<div>...</div>)
}
现在,实际函数是唯一可以正常运行的依赖项,并且编译器也很满意。
但是,我不喜欢的事实是,我需要具有call
属性的包装对象。
如果我想将箭头函数直接传递给状态,则无法按预期方式运行,例如:
const [doSearch, setDoSearch] = useState<()=>Promise<SearchResult>>()
...
setDoSearch(() => callSearchApi(props.userName, searchOptions, searchQuery))
doSearch
未设置为箭头功能,但立即执行了callSearchApi
。有人知道为什么吗?
答案 0 :(得分:1)
您可以从效果中删除setIsSearching(true)
,并在单击按钮时将其分开。
const handleSearchButtonClick = () => {
setLastSearchButtonClickTimestamp(Date.now())
setIsSearching(true);
}
然后,您可以像这样修改useEffect
语句:
useEffect(() => {
if(!isSearching) {
return false;
}
setSearchResult([])
const doSearch = () => callSearchApi(props.userName, searchOptions, searchQuery)
doSearch().then(newSearchResult => {
setSearchResult(newSearchResult)
setIsSearching(false)
}).catch(error => {
console.log(error)
setIsSearching(false)
})
}, [allYourSuggestedDependencies]) // add all the suggested dependencies
这将完成您想要的。另一种方法是禁用react-hooks/exhaustive-deps
规则。
如果只需要在单击按钮时才触发获取,那么我将使用一个函数。
useEffect
很有用,例如,当您有一个过滤器列表(切换),并且每次切换一个过滤器(想像一个电子商务)时都希望进行提取。这是一个幼稚的例子,但它指出了重点:
useEffect(() => {
fetchProducts(filters);
}, [filters])
答案 1 :(得分:1)
那应该是useEffect。
props.userName
在依赖列表中很有意义,因为我们肯定要在更改userName时获取新数据。searchOptions
和searchQuery
遇到这种情况时,最好使用reducer,因此只需要分派action ==> searchOptions
和searchQuery
就不会了在userEffect
内部。 This article from Dan Abramov 提供了深入的解释,并且提供了实现它的简单示例我将使用useReducer
快速转换您的示例,请看看
import React, { useState, useEffect, useReducer } from "react";
function callSearchApi(
userName: string,
searchOptions: SearchOptions,
searchQuery: string
): Promise<SearchResult> {
return new Promise((resolve, reject) => {
const searchResult = searchOptions.fooOption
? ["Foo 1", "Foo 2", "Foo 3"]
: ["Bar 1", "Bar 2"];
setTimeout(() => resolve(searchResult), 3000);
});
}
const initialState = {
searchOptions: { fooOption: false },
searchQuery: "",
startSearch: false, // can replace searching
searchResult: []
};
const reducer = (state: any, action: any) => {
switch (action.type) {
case "SEARCH_START":
return { ...state, startSearch: true, setSearchResult: [] }; //setSearchResult: [] base on your example
case "SEARCH_SUCCESS":
return { ...state, setSearchResult: action.data, startSearch: false };
case "SEARCH_FAIL":
return { ...state, startSearch: false };
case "UPDATE_SEARCH_OPTION":
return { ...state, searchOptions: { fooOption: action.data } };
case "UPDATE_SEARCH_QUERY":
return { ...state, searchQuery: action.data };
default:
return state;
}
};
function SearchPage(props: SearchPageProps) {
const [
lastSearchButtonClickTimestamp,
setLastSearchButtonClickTimestamp
] = useState<number>(Date.now());
const [state, dispatch] = useReducer(reducer, initialState);
const { searchOptions, startSearch, searchQuery, searchResult } = state;
// ####################
useEffect(() => {
dispatch({ type: "SEARCH_START" });
}, [lastSearchButtonClickTimestamp]);
// ####################
const handleSearchButtonClick = () => {
setLastSearchButtonClickTimestamp(Date.now());
};
if (startSearch) {
callSearchApi(props.userName, searchOptions, searchQuery)
.then(newSearchResult => {
dispatch({ type: "SEARCH_SUCCESS", data: newSearchResult });
})
.catch(error => {
console.log(error);
dispatch({ type: "SEARCH_FAIL" });
});
}
return (
<div>
<div>
<label>
<input
type="checkbox"
checked={searchOptions.fooOption}
onChange={ev =>
dispatch({
type: "UPDATE_SEARCH_OPTION",
data: ev.target.checked
})
}
/>
Foo Option
</label>
</div>
<div>
<input
type="text"
value={searchQuery}
placeholder="Search Query"
onChange={ev =>
dispatch({ type: "UPDATE_SEARCH_QUERY", data: ev.target.value })
}
/>
</div>
<div>
<button onClick={handleSearchButtonClick} disabled={startSearch}>
{startSearch ? "searching..." : "Search"}
</button>
</div>
<hr />
<div>
<label>Search Result: </label>
<input type="text" readOnly={true} value={searchResult} />
</div>
</div>
);
}
export default SearchPage;
答案 2 :(得分:-1)
副作用并不意味着那样。但是,如果要在变量发生更改时执行useEffect,可以将其放在依赖项数组中。
示例
function effect() {
let [n, setN] = useState('')
useEffect(() => {
//some api need to call when 'n' value updated everytime.
}, [n])
//update the N variable with ur requirement
const updateN = (val) => {
setN(val)
}
}
希望这会有所帮助