2 ms·
Edit: I read this back to myself and realized it may come off as harsh or mean. I don’t intend it as such, and I apologize if it’s received that way. I think be
by eyelidlessness 5y ago
Edit: I read this back to myself and realized it may come off as harsh or mean. I don’t intend it as such, and I apologize if it’s received that way. I think being precise about this is worth pursuing, for the sake of both developers thinking about it and users experiencing what we build.
- - -
Of course it matters.
“Well behaved react code” isn’t side-effect free, as much as it tries hard to look almost like it is. You can write perfectly idiomatic components which follow all best practices, and evaluating a component when you don’t intend to use it will still:
1. Execute the code (duh), which at minimum uses CPU resources
2. Perform relevant hooks and call lifecycle methods, including those which have side effects (hint: “effect” is in the hook’s name, for this reason): starting timers, loading resources, calling non-React APIs, pretty much anything goes by design, even if you follow all the rules and other best practices.
3. Prepare for a new, potentially concurrent, render. This may speculatively interrupt/pause a render in progress which would otherwise complete without interruption. Which may, in turn, cause seemingly unrelated components to be called again, cascading this entire set of potential side effects to them, and so on.
4. Call any components in that cascade whether they’re well behaved in your control, or epic shit show dependencies.
Being in an expression position does not mean it’s side effect free. If you don’t believe me, run this code:
const isPure = (value) => (
value = 'NOPE',
console.log('There is no code block in sight, this function has zero statements. Is it pure?', value),
value
);
isPure('yeah?');
And sure, React and most JSX implementations are designed to make evaluating JSX side effects local as much as possible. But that’s limited because it’s exposed to APIs not under its control by design, first of all. And more importantly, I would hope that, on HN of all places, a knowable answer to a factual question “is code executed?” is not treated as subjective. This is the exact same question as “is it control flow?”
- deleted 5y ago[deleted]
- rimunroe 5y ago> 1. Execute the code (duh), which at minimum uses CPU resources In this case it really is quite a minimum use of CPU, as React's createElement function doesn't do a whole lot. As far as I know, in a production build it's really just going to do a teeny amount of work to create an object (which looks like {type: Login, props: {}, /* ...some other properties */}). "Evaluating a component" isn't something the user ever does directly. The React runtime is the only thing which ever invokes a component's render method. > 2. Perform relevant hooks and call lifecycle methods, including those which have side effects (hint: “effect” is in the hook’s name, for this reason): starting timers, loading resources, calling non-React APIs, pretty much anything goes by design, even if you follow all the rules and other best practices. > 3. Prepare for a new, potentially concurrent, render. This may speculatively interrupt/pause a render in progress which would otherwise complete without interruption. Which may, in turn, cause seemingly unrelated components to be called again, cascading this entire set of potential side effects to them, and so on. > 4. Call any components in that cascade whether they’re well behaved in your control, or epic shit show dependencies. It won't do any of these things. React won't try to actually render a component (call its render function) unless an element referring to it ends up being returned by the render function of another component which gets rendered.
- jameshart 5y agoI'm sorry, but you're just mistaken about how JSX works. <Foo /> does not translate into a call to Foo(). It translates into React.createElement(Foo). And Foo will only be called if the element returned from that call winds up being examined by some renderer like ReactDOM, mounted as a component, and actually rendered. Here: function UnrenderedComponent() { console.log("UnrenderedComponent does not get executed"); React.useEffect(() => { console.log("this effect never runs") }); return "This won't get rendered" } function RenderedComponent() { console.log("Rendered Component running") React.useEffect(() => { console.log("this effect runs") }); const x = <UnrenderedComponent/>; return "This will get rendered"; } ReactDOM.render( <RenderedComponent />, document.getElementById('container') ); I'll save you copy/pasting - here's a jsFiddle: https://jsfiddle.net/gr4kxq81/ https://jsfiddle.net/gr4kxq81/ That code unconditionally includes the evaluation of <UnrenderedComponent/> in the context of a render method. But the UnrenderedComponent() function does not get called, its useEffect call never happens, its effect code never gets executed, because the resulting element doesn't make its way back to ReactDOM to be mounted. someBoolean && <MyComponent/> is not control flow, it's a simple expression that conditionally evaluates to an instruction to either not render anything, or to mount and render a MyComponent. It's cheap and side effect free. My comment about 'assuming well behaved React code' was more directed at the fact that technically you can plug in a custom JSX pragma so you might not just be handing it to React.createElement, in which case all bets are off for what happens. But React doesn't call render methods for components unless they're mounted.
- eyelidlessness 5y ago> I'm sorry, but you're just mistaken about how JSX works. No, I’m not. JSX doesn’t do anything. But it’s an expression and evaluated according to the rules of JS expressions. You might expect React or whatever to evaluate it one way today, and it can be evaluated another way tomorrow. React.createElement isn’t the API you’re using, neither is react/jsx-runtime. You’re using an expression with some special angle brackets and curly braces. You have no idea if your component is being called, but semantically you have every reason to believe it is and will be. Bool && otherExpression is control flow because it’s specifically defined as an expression in the host language. Anything else is assuming compiler magic you were never promised.