2 ms·
What you're claiming here does not match the evidence from your links. For example, in your second link, one of the functions expects a 'complex' instance Disp
by cryptica 6y ago
What you're claiming here does not match the evidence from your links.
For example, in your second link, one of the functions expects a 'complex' instance DispatchProps which has a method toggleOn... This abstraction doesn't make sense conceptually. What is a DispatchProps? It doesn't adhere to Alan Kay's notion of a 'message', it's clearly a structure (it's a complex one because it exposes a method). Components should communicate to each other via messages, not structures.
Also, the method builder.addMatcher(...) accepts a function as an argument - It doesn't seem like an ideal abstraction either. Functions are not messages.
Also, the thunkSendMessage function signature is very complex; the return type is highly convoluted. That's definitely not a message.
Overall, I see a lot more of these complex instances being passed around in these examples than used to be the case with JS when mostly just raw objects were being passed as arguments and returned.
You can already see the complexity seeping into the interfaces. Just a few years of TypeScript is distorting the original philosophy. I remember Dan Abramov was very careful about what to pass into functions and what to return from them and made it a point to encourage cloning objects using the ... spread operator.
That philosophy appears to have been forgotten.
- kilburn 6y ago> For example, in your second link, one of the functions expects a 'complex' instance DispatchProps which has a method toggleOn... This abstraction doesn't make sense conceptually. What is a DispatchProps? It doesn't adhere to Alan Kay's notion of a 'message', it's clearly a structure (it's a complex one because it exposes a method). Components should communicate to each other via messages, not structures. the `mapDispatchToProps` idea has been in `react-redux` since the very beginning. I don't understand what are you complaining about here. If you prefer, you can ignore this practice and pass the whole dispatch function to the component, and then call `dispatch(actionCreator(params))` directly. Typescript won't get in the way of you doing that. > Also, the method builder.addMatcher(...) accepts a function as an argument - It doesn't seem like an ideal abstraction either. Functions are not messages. The addMatcher API is not concerned with sending messages, it is concerned with receiving them. You are specifying what should happen when a certain message type arrives. Hence it seems normal to me to specify that as a function that given the message does some stuff. Since these are reducers, the "does some stuff" part is: given a previous state and an action of type X, compute and return the new state Y. > Also, the thunkSendMessage function signature is very complex; the return type is highly convoluted. That's definitely not a message. Of course not. It is a function that sends some kind of message after asynchronously calling some API and getting its response, or another kind of message if the API call failed. That's what the complex type says. The usefulness of thunks is that you do have this notion of "ongoing asynchronous process that may succeed or fail" explicitly represented (so you can for instance cancel it), which you'll have a much harder time if you want to represent by using just actions and reducers. > You can already see the complexity seeping into the interfaces. Just a few years of TypeScript is distorting the original philosophy. I remember Dan Abramov was very careful about what to pass into functions and what to return from them and made it a point to encourage cloning objects using the ... spread operator. I think you are misremembering some things here. Dan Abramov used to advocate for a strict separation between presentational and container components [1], including the use of `connect` in exactly the same fashion you are criticizing in your first point. Likewise, cloning objects using the spread operator was never a goal in and of itself. Dan advocated for it as an easier way to avoid mutating the state. Not mutating the state was the point here. Redux-toolkit accomplishes this point by using immer, a library that conceptually gives you a copy of the state so you can just mutate and return that instead (with the advantage that non-modified parts are not cloned). Also, you can just not use that and return spread-operator-cloned objects from your reducer functions instead. Everything will keep working normally (and passing the type checks) in that case. [1] https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0 https://medium.com/@dan_abramov/smart-and-dumb-components-7c... PS: Thanks for your work Mark!
- acemarke 6y agoWow. I'm sorry, but you've completely misinterpreted both what I was trying to say, and what those Redux-related APIs do. A lot of people seem to assume that "using TS" means "must use the `class` keyword and deep inheritance chains", ala Java and C#. Redux, on the other hand, is FP-inspired. Nothing about the Redux core involves classes in any way - everything is just functions, including the middleware API, with an emphasis on immutability. You brought up Redux's use of immutable state updates, but then said "that doesn't translate well to TS". I was attempting to show that it _does_ translate just fine to TS, because TS lets you write functions and make immutable updates. `return {...state, field: value}` works as fine in TS as it does in JS. I wasn't trying to touch anything about "what a message is". Having said that, the rest of your observations about the Redux core and React-Redux APIs in this comment show a general misunderstanding of what Redux is and how it gets used. After all, Dan and Andrew came up with the actual React-Redux API, the concept of `mapDispatch` for passing action creators as props to React components, and the thunk middleware. The Redux Toolkit `builder.addMatcher` API is a recent addition, but all it is is syntax sugar for "if the dispatched action is any one of these types, we want to update to it", same as if I wrote a multi-condition `if` statement by hand. None those have anything to do with TS, and they did not become more complex because we're now using TypeScript. In fact, it's the other way around - the complexity of the dynamic JavaScript behavior actually requires us to write much more complex TS types to capture how the code actually works. (There's a good reason why the React-Redux TS types are insanely complex, and I'm so glad they're maintained in DefinitelyTyped instead of by us! `connect` has so many overloads and different options that affect downstream props values, it's almost impossible to capture that with static types.)
- cryptica 6y agoMaybe you also misunderstood my initial comment. I can say for sure that of all the programming trends, there has been almost no discussion around interface complexity. There has been plenty of discussions around FP, dependency injection, type safety and a host of other trendy programming topics but I've never heard anyone point out interface complexity as a problem. Complex interfaces lead to 'tight coupling'; developers have known for decades that this is bad but we don't seem to be discussing it much anymore. The reason why JSON-based REST APIs became so successful is because it greatly reduced interface complexity (compared to XML-based SOAP) and in doing so, it loosened the coupling between different services. Until we start discussing interface complexity, nobody will fully realize what the drawbacks of TypeScript are. My main problem with TypeScript is that it is most useful when code quality is low (high interface complexity; tight coupling). That's what I mean when I say that it encourages bad programming practices; the people who find TypeScript most useful are those who tend to produce the worst code in terms of interface complexity.