reduxjs / react-redux

Official React bindings for Redux
https://react-redux.js.org
MIT License
23.37k stars 3.37k forks source link

Use Proxy-based selectors #1653

Closed theKashey closed 2 years ago

theKashey commented 3 years ago

Based on:


Proxy based solutions were floating around for a while, and actually actively used inside(MobX) and outside of React ecosystem(especially in Vue and Immer). I am opening this discussion to resolve status quo and add some momentum to Redux + Proxies investigations.

Overall proxies are capable to solve 3 objective:

// reading only .a and .b const fn = memoize(x => ({ sum: x.a + x.b, diff: x.a - x.b }));

fn({ a: 2, b: 1, c: 1 }); // ---> { sum: 3, diff: 1 } fn({ a: 3, b: 1, c: 1 }); // ---> { sum: 4, diff: 2 } fn({ a: 3, b: 1, c: 9 }); // ---> { sum: 4, diff: 2 } (returning old value) // ^ "c" does not matter



- __testing__. While the use case above is a little synthetic and such memoization is not really needed unless a developer willing not to use any memoization at all, it can be used to check _selector quality_, and report all selectors which react to the state change while they should not
> technically running selector twice in dev mode might solve majority of the problems, like returning new array/object every time, proxy based memoization is capable to detect more complicated situations and provide some guidance around the problem. As seen in `why-did-you-update`
![image](https://user-images.githubusercontent.com/582410/97410043-a9b29d80-1952-11eb-96f9-d37fdf5346ca.png)

- __slice isolation__. One named issue of redux is it's "global" store, and the fact that all listeners would be notified on state update. Knowing which slices were updated, and which selectors are listening for them, might provide an optimisation for selectors. This is actually a very old idea - https://github.com/reduxjs/react-redux/pull/1021 - but still valuable.

---

What proxies can solve:
- speed. Proxy based memoization works "more often" and might result a better experience
- less dev pain. Proxies "just works". At least, in the test mode, they can point on the problem instantly, not asking to invest some hours in debugging.
- TBD

---

What proxies cannot solve:
- per-component memoization. It's just absolutely orthogonal problem.
- TBD

--- 

What proxies can break:
- As long as one has to wrap `state` with a Proxy - one will use `Proxy` in all "frontend" code. In the same time at reducers/middleware side it will be just `state`. (Only once) I had a problem with "equal reference" memoization working differently in `useSelector` and `saga`. It's very edge-case-y, but having state sometimes wrapped, and sometimes not __can__ break stuff.

---

### Actionable items:
- [ ] decide about proxy-memoized selector
- [ ] decide about proxy-based selector pureness / quality checking
- [ ] decide about slice isolation or/and proxy-based tracking/DevTools integration. It's really possible to know which components reading which slice or/and react to a some piece of information and this knowledge can transform DevTools.

cc @markerikson , @dai-shi 
theKashey commented 2 years ago

I reckon sooner or later proxies will make their way to the "read" part, as immer made it's way to the "write" part. But it does not to be today. Let's wait for a solution we all can recommend without a second thought.