React Hooks - How do I implement shouldComponentUp

2020-08-09 05:09发布

问题:

I know you can tell React to skip an effect by passing an array as an optional second argument.

For example:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

But what if i want to control over the comparison? to add my own comparison logic.

I would expect something like React.memo that you can pass a function as a second argument.

回答1:

In a comment above Gabriele Petrioli links to the React.memo documentation that explains how to implement shouldComponentUpdate. I was googling combinations of shouldComponentUpdate + useEffect + "react hooks", and this came up as the result. So after solving my problem with the linked documentation I thought I would bring the information here as well.

This is the old way of implementing shouldComponentUpdate:

class MyComponent extends React.Component{
  shouldComponentUpdate(nextProps){
    return nextProps.value !== this.props.value;
  }
  render(){
    return (
     <div>{"My Component " + this.props.value}</div>
    );  
 }
}

The New React Hooks way:

React.memo(function MyComponent (props) {

  return <div>{ "My Component " + props.value }</div>;

}) 

I know you were probably asking for more in your question, but for anyone coming from Google looking for how to implement shouldComponentUpdate using React Hooks, there you go.

The documentation is here: how-do-i-implement-shouldcomponentupdate



回答2:

Here is a custom hook which takes an updater function, and returns a value that changes only when the updater function returns true, which can be passed in the second argument to useEffect or useCallback or useMemo to force a re-render:

function useShouldRecalculate(shouldRecalculateFn) {
  const prevValueRef = useRef(0);
  if(shouldRecalculateFn()) {
    // If we need to recalculate, change the value we return
    prevValueRef.current += 1;
  } // else we return the same value as the previous render, which won't trigger a recalculation
  return prevValueRef.current;
}

For example, this will update the document title only when count is even:

const shouldUpdateTitle = useShouldRecalculate(
  () => count % 2 === 0
);

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [shouldUpdateTitle]); // eslint-disable-line react-hooks/exhaustive-deps

Why you probably shouldn't do this

In most cases, I wouldn't recommend doing this.

Generally, there's going to be cleaner ways to accomplish the same task, using idiomatic hooks API. (The above example could have just put an if block around the line that updates the document's title.)

Perhaps more importantly, the deps argument isn't just about optimization, but about keeping closure values up-to-date, and avoiding bugs like:

const [count, setCount] = useState(0)
const increment = useCallback(() => {
    // Bug: `count` is always 0 here, due to incorrect use of the `deps` argument
    setCount(count + 1)
}, [])

This bug will be caught by the react-hooks/exhaustive-deps linter rule, but you'll have to disable that rule anywhere where you use custom logic to control execution.

Use custom memoization logic is likely to make your components more bug-prone and harder to reason about in general. So I'd consider this useShouldRecalculate hook something of a last resort.



回答3:

An alternative might be to use useRef to hold your data, and use useState ONLY to store the data you want to display. Sometimes this works better than the memo approach: I've had a case recently where React was still rendering needlessly when using React.memo, and it was messing up some PIXI display. The approach below fixed it for me... hopefully I did not do an anti-pattern :-)

const countRef = useRef(0);
const [countDisplay, setCountDisplay] = useState(0);

yourUpdateFunction = () => {
  // This is where count gets updated
  countRef.current = countRef.current + 1;
  if ((countRef.current % 2) === 0) setCountDisplay(countRef.current);
}

return (<p>countDisplay</p>);


回答4:

Adding to PAT-O-MATION's answer,
React.memo also accepts a second parameter, which is function which can be used to determine if a component should render or not if the function returns true then component won't re-render on change of that prop, conversely it updates when the return value is false

function SomeComp({prop1, prop2) {
    return(
        ..
    )

}
React.memo(SomeComp, (props, nextProps)=> {
    if(props.prop1 === nextProps.prop1) {
        // don't re-render/update
        return true
    }
})

Note: Component would only re-render when the callback function returns false, so in the above case even if the prop2 value changes it won't re-render



回答5:

Passing the optional second array argument kicks in the effect function if the property passed in the array changes - just like shouldComponentUpdate method in a class component would run when props passed to the component change. You can decide in the effect function based upon the value of the parameter - if you want to the effect to apply or not.