9 ms·
Hooks let you colocate logic that, in class components, would need to be split across multiple lifecycle methods. In practice you need to remove the event liste
by idreyn 4y ago
Hooks let you colocate logic that, in class components, would need to be split across multiple lifecycle methods. In practice you need to remove the event listener when the component unmounts. You can get reasonably close to a hooksy API for doing this here:
class MyComponent {
componentWillMount() {
this.stopWatchingViewport = watchViewPort((x, y) => {
this.setState({ something: x + y });
});
}
componentWillUnmount() {
this.stopWatchingViewport();
}
}
watchViewPort(callback) {
const onResize = (event) => {
// get x and y
callback(x, y);
};
addEventListener("resize", onResize);
return () => removeEventListener("resize", onResize);
}
But being able to slice up logic by functionality, rather than by lifecycle event, gets gradually nicer as you have more of it.
- theteapot 4y ago> Hooks let you colocate logic that, in class components, would need to be split across multiple lifecycle methods. I think the obvious OO alternative to hooks would have been this: class MyComponent { constructor() { this.attachBehaviour(new MyBehaviour(this)); this.attachBehaviour(new MyOtherBehaviour(this)); } render() { /* ... */ } } class MyBehaviour implements ComponentLifeCycleHooks { componentDidMount() { /* ... */ } componentWillUnmount() { /* ... */ } componentDidUpdate() { /* ... */ } } Weird React didn't even seem to consider when they went to hooks. Would be possible to implement yourself though. I'm not saying this is better than hooks / "composable" / "functional" API (I quite like Vue's composable API) but it's less of a departure from class based components.
- mattgperry 4y agoInteresting you assume they didn't consider this. They probably did. How do this two behaviours compose together/interact with each other?
- theteapot 4y agoI made no such assumption. I've just never seen it discussed, where as, for example, I've seen "mixins" discussed and dismissed (justifiably) as an alt. > How do this two behaviours compose together/interact with each other? What?
- codeptualize 4y agoDeparture from class based components was one of the motivations behind hooks, those are: 1. It’s hard to reuse stateful logic between components 2. Complex components become hard to understand 3. Classes confuse both people and machines See https://legacy.reactjs.org/docs/hooks-intro.html#motivation https://legacy.reactjs.org/docs/hooks-intro.html#motivation for details. Agree or not, but it is a very intentional and well motivated direction.
- theteapot 4y agoAbove was OO an based solution to #1, and arguably #2. Many would argue hooks did nothing to solve #2 and may have made things worse. > Agree or not, but it is a very intentional and well motivated direction. Agree with problem exists (roughly), disagree on solution.
- spion 4y agoThats purely a React API limitation. The hook API could be class based: class ViewportHook { // API on use constructor(component) { this.viewportState = component.addState(this, initialValue) const unsubscribe = watchViewport((x, y) => this.viewportState.set({something: x + y})) component.addOnUnmount(unsubscribe); // you can also use another hook - hook composition works this.otherHook = component.use(SubHook); // use the other hook's api } // API to expose (in render) value() { return this.viewportState.get() // use this.otherHook too if you like } } You would be able to use it in a component like this class MyComponent constructor() { this.viewport = this.use(ViewportHook); } render() { const viewportSize = this.viewport.value(); // use in render } } Boring, and a bit less weird.
- orangepanda 4y agoHow would passing a value from one hook to another look like, for example, Subhook requiring viewport value to set up some subscription?
- spion 4y agoGood question. component.use(SubHook, param) could be used, that would pass the param as a second argument to the constructor. The main reason why this isn't the case, I think, is concurrent mode. Hooks force certain values to be retreived and stay stable during render (i.e. you can only get a component state value during render function) and this is important if there are multiple setup and teardowns going on. (Concurrent mode is IMO a bit of unfortunate React complexity that a lot of users of React don't really need, and many others can avoid)
- rimunroe 4y agoThis is a hook version of the same code as near as I could guess it. function useViewport(initialValue) { const [state, setState] = useState(initialValue); useEffect(() => { return watchViewport((x, y) => setState(x + y)) }, []) return state.get(); } // usage function MyComponent() { const viewportSize = useViewport() // use in render } I made a couple of assumptions here. From usage, I assume watchViewport is supposed to both subscribe and return a teardown function. I also assume that the viewportState.set/get are functions for getting and setting the tracked value in the component's state. In my opinion, there are many advantages to the hook version and several major disadvantages to the class version: 1. There's no need for hooks to have a value method or React.Components to grow the use or addState methods because the hook is just a function call which returns a value. How it produces that value is up to the code inside the function--which does call out to React--but the fact the function will always be called when MyComponent is called (absent something throwing earlier in the function body of course). The value you see being returned by the hook will always match the value you get when you call useViewport() in your component. These two things are guaranteed to have the exact same behavior as any other JavaScript function call and thus you can reason about the "registration" and value passing without needing to learn any framework-specific APIs. 2. There's no need for an addOnUnmount method on the component object passed to the ViewportHook constructor, because the hook function can use the function component's equivalent to componentWillUnmount (the return value of a useEffect) in the exact same way as a function component can, but without need for external registration. 3. In order to implement the class API, you'd need to either (A) pass the component's actual instance to the hook, or (B) create a new type of value to represent the instance of the component the hook is registering against. Option (B) is yet another API to learn, as you have to learn a new type of object to deal with in a React application. Option (A) would mean figuring out a way to prevent people from calling those methods after construction, OR introducing the possibility of registering a new slice of state partway through. The latter might be possible, and maybe that's even what you intend, but I'd want to know what the expected impact on methods like shouldComponentUpdate or getDerivedStateFromProps would be. Speaking of those... 4. I can't think of any obvious way you could pass previous versions of hook-related instance properties to lifecycle methods in the same way that you can pass prevProps and prevState. 5. Concision: the hook version has a dramatic reduction in the amount of code you have to read 6. Bundle size: because the hook version relies on functions rather than class properties, it can be minified trivially and thus reduce bundle size even more than the obvious reduction in character count would imply