Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
120 views
in Technique[技术] by (71.8m points)

javascript - Change detection in React

I'd like to perform a side-effect when some data changes, e.g.

const useSearchResults = () => {
  const location = useLocation();
  const [data, setData] = useState();
  useEffect(() => {
    Api.search(location.query.q).then(data => setData(data));
  }, [location.query.q]);

  // Compute some derived data from `data`.
  const searchResults =
    data &&
    data.map(item => ({
      id: item.id,
      name: `${item.firstName} ${item.lastName}`
    }));

  return searchResults;
};

const Component = () => {
  const searchResults = useSearchResults();
  useEffect(() => {
    alert('Search results have changed'); // Side-effect
  }, [searchResults]);
  return <pre>{JSON.stringify(searchResults)}</pre>;
};

If something causes Component to re-render, the alert will fire even if the searchResults haven't changed because we map over the underlying stable data in useSearchResults creating a new instance on every render.

My initial approach would be to use useMemo:

  // Stabilise `searchResults`'s identity with useMemo.
  const searchResults = useMemo(
    () =>
      data &&
      data.map(item => ({
        id: item.id,
        name: `${item.firstName} ${item.lastName}`
      })),
    [data]
  );

However useMemo has no semantic guarantee so it's (theoretically) only good for performance optimisations.

Does React offer a straight forward solution to this (common?) problem?

question from:https://stackoverflow.com/questions/66064561/change-detection-in-react

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

If it's very important that the side effect doesn't re-run, you can base your comparison on a deep equality check. We want to see that the search results are actually different results and not just a new array with the same results.

There are various implementations of a usePrevious hook floating around that you can use, but basically it's just a ref. Save the previous version of the search results to a ref. When your effect runs, see if the current results are different than the previous results. If they are, do your side effect and update the previous to the current.

const Component = () => {
  const searchResults = useSearchResults();

  // create ref for previous
  const comparisonRef = useRef('');

  useEffect(() => {
    // stringify to compare
    if ( JSON.stringify(searchResults) !== JSON.stringify(comparisonRef.current) ) {
       // run side effect only if true
       alert('Search results have changed');
       // update the ref for the next check
       comparisonRef.current = searchResults;
    }
  }, [searchResults, comparisonRef]);

  return <pre>{JSON.stringify(searchResults)}</pre>;
};

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...