简体   繁体   English

使用 React.memo 和 React.useCallback 防止重新渲染

[英]Prevent re-render using React.memo and React.useCallback

For learning purpose,出于学习目的,

I am trying prevent re-render on <InputWithLable /> component whenever i Dismiss a search result (see deploy in Full code )每当我关闭搜索结果时,我都会尝试防止在<InputWithLable />组件上重新渲染(请参阅完整代码中的部署)

I have use React.memo but it still re-render.我已经使用React.memo但它仍然重新渲染。 So I think maybe its props is the culprit.所以我想也许它的props是罪魁祸首。 I use React.useCallback to handleSearch prop, but it doesn't work.我用React.useCallbackhandleSearch道具,但它不工作。

Full code完整代码

 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> import React from 'react'; const API_ENDPOINT = 'https://hn.algolia.com/api/v1/search?query='; const useSemiPersistentState = (key, initialState) => { const [value, setValue] = React.useState( localStorage.getItem(key) || initialState ); React.useEffect(() => { localStorage.setItem(key, value); }, [value, key]); return [value, setValue]; }; function storiesReducer(prevState, action) { switch (action.type) { case "SET": return { ...prevState, data: action.data, isLoading: false, isError: false }; case "REMOVE": return { ...prevState, data: prevState.data.filter( story => action.data.objectID !== story.objectID ) } case "ERROR": return { ...prevState, isLoading: false, isError: true }; default: throw new Error(); } } const App = () => { const [searchTerm, setSearchTerm] = useSemiPersistentState( 'search', 'Google' ); const [stories, dispatchStories] = React.useReducer(storiesReducer, { data: [], isLoading: true, isError: false }); const [url, setUrl] = React.useState(""); const handleFetchStories = React.useCallback(() => { fetch(url) .then((response) => response.json()) .then((result) => { console.log(result); dispatchStories({ type: "SET", data: result.hits }) }) .catch(err => dispatchStories({ type: "ERROR", data: err })) }, [url]) React.useEffect(() => { handleFetchStories(); }, [handleFetchStories]) const handleRemoveStory = React.useCallback( (item) => { dispatchStories({ type: "REMOVE", data: item }); }, [], // chi render 1 lan vi props khong thay doi ) const handleSearch = React.useCallback( (e) => { setSearchTerm(e.target.value); }, [], ) // Chuc nang filter la cua server (vd: database) // const searchedStories = stories.data ? stories.data.filter(story => // story.title.toLowerCase().includes(searchTerm.toLowerCase()) // ) : null; // nghich cai nay! console.log('App render'); return ( <div> <h1>My Hacker Stories</h1> <InputWithLabel id="search" value={searchTerm} isFocused onInputChange={handleSearch} > <strong>Search:</strong> </InputWithLabel> <button onClick={() => setUrl(API_ENDPOINT + searchTerm)}>Search!</button> <hr /> {stories.isError && <h4>ERROR!</h4>} {stories.isLoading ? <i>Loading...</i> : <List list={stories.data} onRemoveItem={handleRemoveStory} />} </div> ); }; const InputWithLabel = React.memo( ({ id, value, type = 'text', onInputChange, isFocused, children, }) => { const inputRef = React.useRef(); React.useEffect(() => { if (isFocused) { inputRef.current.focus(); } }, [isFocused]); console.log('Search render') return ( <> <label htmlFor={id}>{children}</label> &nbsp; <input ref={inputRef} id={id} type={type} value={value} onChange={onInputChange} /> </> ); } ); // Prevent default React render mechanism: Parent rerender -> Child rerender const List = React.memo( ({ list, onRemoveItem }) => console.log('List render') || list.map(item => ( <Item key={item.objectID} item={item} onRemoveItem={onRemoveItem} /> )) ); const Item = ({ item, onRemoveItem }) => ( <div> <span> <a href={item.url}>{item.title}</a> </span> <span>{item.author}</span> <span>{item.num_comments}</span> <span>{item.points}</span> <span> <button type="button" onClick={() => onRemoveItem(item)}> Dismiss </button> </span> </div> ); export default App;

You should not be looking at how many times a component's render function gets called;您不应该查看组件的渲染函数被调用的次数; React is free to call it as many times as it likes (and indeed, in strict mode, it calls them twice to help you not make mistakes). React 可以随心所欲地多次调用它(实际上,在严格模式下,它会调用它们两次以帮助您避免出错)。

But to answer your question (with the actual code that uses children ):但是要回答您的问题(使用使用children的实际代码):

<InputWithLabel>
   <strong>Search:</strong>
</InputWithLabel>

compiles down to编译为

React.createElement(InputWithLabel, null,
    React.createElement("strong", null, "Search:"))

the identity of the children prop (the <strong /> element) changes for each render of the parent component since React.createElement() returns new objects for each invocation. children prop( <strong />元素)的标识随着父组件的每次渲染而改变,因为React.createElement()为每次调用返回新对象。 Since that identity changes, React.memo does nothing.由于身份发生了变化, React.memo什么也不做。

If you wanted to (but please don't ), you could do如果你想(但请不要),你可以做

const child = React.useMemo(() => <strong>Search:</strong>);
// ...
<InputWithLabel>{child}</InputWithLabel>

but doing that for all of your markup leads to nigh-unreadable code.但是对所有标记执行此操作会导致代码几乎不可读。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM