First of all, useMemo is normally used to memoize values, not functions. The function passed to useMemo is nullary and is supposed to simply produce values. For memoizing actual functions (event handlers and whatnot), there’s useCallback. But I think this is beside the point.
The differences to normal memoizing functions are:
- Free variables
- Where the cache resides
- The stability of the returned function
1. Free and Bound Variables
Normally, the memoization helpers only care about bound variables. The arguments passed to the memoized version of the function are used as cache keys, and the original function is only called if there’s a cache miss. If the value in the cache was calculated using some free variables, and values of those has changed, the value won’t be re-evaluated.
So it’s a good practice to only memoize functions that don’t use any free values (or at least use some that don’t change during the app lifetime). But that advice is not very useful for functions created inside React components, because those functions often use values from the component function’s scope. And with that usage pattern, it’s easy to get stale values, so you have to take care of the free variables, and so (the React solution) you have to list them as explicit dependencies.
I think this is the difference most relevant to exhaustive dependencies, but let me go on anyway, in case you could benefit from more context 
2. The Cache
With traditional memoization, to my knowledge, the cache is created either
- inside the scope created by the
memoize call,
- inside the ES module from which the
memoize function is imported, or
- the cache instance is provided by the consumer.
Variant 1 could be OK with class components: you could memoize in the constructor once and be done with it. But with function components, where the functions are re-run repeatedly, that would just create a new empty cache every time, and there’d be no memoization, effectively. Well, there would if you’d use your value way more often that the component re-renders (i.e., the component function re-runs), but that’s not an awfully frequent case.
Variant 2 would mean all the instances of all the component share their cache, and variant 3 means some manual labour, and you’d have to do some non-trivial work to separate cache for instances of the same component.
So the React solution is that useMemo is stateful, in the same way as useState/useReducer: it tracks some internal state for every hook usage in every component instance. So there’s a separate cache for every (first) call of a useMemo hook inside every createElement call, and the consumers don’t have to do anything extra, other than abide the rules of hooks.
3. Stability
One concern that traditional memoization, to my knowledge, doesn’t have at all, but that is very important for modern React, is the stability of the references. And since it’s directly related to the hooks’ dependencies, I think it’s worth going into some detail.
Why stability matters
First of all, in React a stable value is a value that doesn’t change on re-renders. And when it comes to non-scalar values, it means that the reference doesn’t change. So, for clarity, creating a new value of [a, b] on every render is still unstable, even if both a and b haven’t changed.
Why does it matter? React uses diffing all the time, for the hooks’ dependencies and for the components’ props. Diffing is done shallowly, i.e., every property of a props object and every element of a dependency array is compared using Object.is. So if the value is an object (arrays and functions are objects in JS too), it’s compared by reference.
Comparison by reference blends very well with immutable patterns. There, new value === new reference. So React can compare values cheaply and then bail out of re-rendering (if it’s props) or out of running computations/effects (if it’s hook dependencies).
Also, React actually stabilises functions returned by useState or useReducer, so you can pass them as props or use them inside effects, and you never have to worry about handling the changes of their values, because those functions literally never change. And that’s why the eslint plugin ignores those functions: it knows you don’t have to list them as dependencies.
Where it breaks
The problems begins when you create values on the fly. Every function you create inside the component function, every array or object where you combine some values, and for that matter, every value created using a variant constructor with payload means a different value on every re-render.
Sure enough, if the value changes over time, you do want the reference to change. But only when the value actually changes. IOW, you want to enforce the new value === new reference invariant. Otherwise, if you get new refs without new values, you create false positives when diffing and your components can go into infinite loops. It happens all the time, and I don’t know any middle or senior React dev who doesn’t occasionally get bitten by that.
So this is something React docs tell about useCallback but don’t tell about useMemo: as often as not, it’s used not for “expensive computations”, but simply to stabilise the references between the actual changes in the values.
But, using useMemo, it’s easy to break the invariant the other way: if you forget to list a dependency, the hook fails to produce a new reference pointing to a new value. Now your reference is too stable.
And this is what the exhaustive deps part of the plugin tries to solve: it prevents you from overstabilising your values (or skipping your effects when you shouldn’t).