当我发现我的输入在每次按键时都失去了焦点时,我正在构建一个很好的旧的读取-获取-建议查找栏。
我了解到,因为我的输入组件是在包含它的头组件内部定义的,所以对状态变量的更改会触发父组件的重新呈现,而父组件又会重新定义输入组件,从而导致这种行为。使用useCallback可以避免这种情况。
现在,状态值仍然是一个空字符串,即使调用了它的回调(正如我看到的console.log的击键)
通过将state和state setter作为props传递给input组件可以修复这个问题。**但是我不太明白为什么。**我猜测包含在useCallback中的state和setter与后续调用产生的状态和setter "断开"。
我很高兴能读到一个解释来澄清这一点。为什么它以一种方式工作而不是另一种方式?当使用usecallback时,封闭作用域是如何测量的?
这是密码。
export const Header = () => {
const [theQuery, setTheQuery] = useState("");
const [results, setResults] = useState<ResultType>();
// Query
const runQuery = async () => {
const r = await fetch(`/something`);
if (r.ok) {
setResults(await r.json());
}
}
const debouncedQuery = debounce(runQuery, 500);
useEffect(() => {
if (theQuery.length > 3) {
debouncedQuery()
}
}, [theQuery]);
const SearchResults = ({ results }: { results: ResultType }) => (
<div id="results">{results.map(r => (
<>
<h4><a href={`/linkto/${r.id}`}>{r.title}</a></h4>
{r.matches.map(text => (
<p>{text}</p>
))}
</>
))}</div>
)
// HERE
// Why does this work when state and setter go as
// props (commented out) but not when they're in scope?
const Lookup = useCallback((/* {theQuery, setTheQuery} : any */) => {
return (
<div id='lookup_area'>
<input id="theQuery" value={theQuery}
placeholder={'Search...'}
onChange={(e) => {
setTheQuery(e.target.value);
console.log(theQuery);
}}
type="text" />
</div>
)
}, [])
return (
<>
<header className={`${results ? 'has_results' : ''}`}>
<Lookup /* theQuery={theQuery} setTheQuery={setTheQuery} */ />
</header>
{results && <SearchResults results={results} />}
</>
)
}
1条答案
按热度按时间7z5jn7bk1#
在另一个组件的render函数中包含本质上是组件的定义通常不是一个好主意,因为您已经遇到了所有的挑战,但是我会回到这个问题并回答您最初的问题。
执行以下操作时:
基本上就是说,当
Header
组件挂载时,在Lookup
中存储一个返回JSX的函数,并在组件 * mount * 时将其放入缓存中,在后续呈现时从不刷新。react将从初始呈现中提取定义,而不是重新定义函数--这称为记忆。这意味着Lookup
在所有渲染之间将是参考稳定的。deps数组
[]
定义了它只在挂载上,deps数组是一个列表,当在渲染之间更改时,将触发回调重新定义,同时将从当前呈现中拉入其所包含的组件的新范围。由于您没有列出deps数组内父作用域的回调函数内使用的所有内容,你实际上是在解决一个bug,如果你使用正确的提升规则,这个bug会被标记出来,这是因为如果内部使用的东西,比如theQuery
和setTheQuery
发生了变化,那么Lookup
回调将不会刷新/重新定义--它将使用挂载时存储在本地缓存中的值,而本地缓存又会引用这些值的旧副本。也就是说,既然您这样做了,
Lookup
就保持稳定,不会被刷新。正如您所说的,如果它刷新了,您将看到它被重新挂载,并看到它的隐式DOM状态在react中,组件定义需要是引用稳定的。您的useCallback
基本上是组件定义,但在另一个组件呈现中,因此,它留给你来处理棘手的记忆业务,以"防止"它被重新定义的每一个渲染。我会回来,你如何正确地解决这个问题很快。当添加
theQuery={theQuery} setTheQuery={setTheQuery}
时,您通过将此数据从父作用域传递到回调函数来解决实际的根本问题,这样它就不需要使用从初始呈现时就已存在的陈旧数据。但是您所做的实际上是在另一个组件中编写一个组件,这使得封装更少,并产生了您所看到的问题。您只需要将
Lookup
定义为它自己的组件。还有SearchResults
。由于组件是在render之外定义的,因此它们只定义了一次,如果不通过props传递给
Header
组件,它们就不能直接访问Header
组件的作用域。这是一个正确封装和参数化的组件,消除了陷入作用域和记忆混乱的机会。将组件封装在组件中只是为了访问作用域是不可取的。这是错误的思维方式。您应该尽可能合理地分解组件。您总是可以在一个文件中包含多个组件。在React中,组件不仅是重用的单元,而且是封装逻辑的主要方式。它应该在这种情况下使用。