Open Aprillion opened 4 years ago
Hmm good point. I think the intended part that was implied is that the computation is cached. I don't think "functional" describes that but you're right this is not exactly "lazy" either.
We're gonna rewrite those parts anyway (#3308) so we'll do that change there.
Brainstorming time if you haven't decided on the final version yet:
I don't think "functional" describes that but you're right this is not exactly "lazy" either.
The "lazy ref init" pattern uses the same terminology so if we decide to use another term we should look at every usage of "lazy" in the docs.
I always understood lazy as in "called when actually needed". For useState that means it won't be called on updates because we definitely are going to throw away the result. As opposed to useState(() => new Connection())
where we'll only throw it away if the component doesn't get mounted.
So for me I do consider the state lazy. That lazy init just happens to be needed sync on mount. On updates React is actually lazy and just ignores it.
If you look at "lazy initialization" on google:
Lazy initialization of an object means that its creation is deferred until it is first used.
In computer programming, lazy initialization is the tactic of delaying the creation of an object, the calculation of a value, or some other expensive process until the first time it is needed.
-- https://en.wikipedia.org/wiki/Lazy_initialization
As I interpret the React docs the term is fitting. Though we should always make an effort to explain terms.
The "lazy ref init" pattern uses the same terminology so if we decide to use another term we should look at every usage of "lazy" in the docs.
@eps1lon hm, good point, but I don't think this feature was intended for actual lazy data structures in the sense you quote:
const [value] = useState(function*() { let n=0; while(true) yield n++ })
console.log(value.next())
I would probably define the generator outside of React, initialize the iterator in useEffect(..., [])
and store the iterator instance in a useRef()
...
The documentation that you point to supports my point (if I may interpret it charitably):
useRef does not accept a special function overload like useState. Instead, you can write your own function that creates and sets it lazily:
So the useState
function argument is NOT intended for lazy initialization in the quoted meaning of "lazy"!! And the docs might need more extensive review of the usage of "lazy"...
FTR: Following is already "lazy" in this sense because a Component is a function that is not executed immediately, but only when actually rendered (and we don't call them Lazy Initialized Components):
function Component() {
const connection = getConnection() // initialized on first render and cached later, nothing to do with useState
const [value, setValue] = useState(connetion)
}
to clarify: useState(getConnetion)
is a better pattern than my last example, but that fact has nothing to do with "lazy" vs "greedy" evaluation and everything to do with optimization to prevent calculating expensive values that would be ignored anyway
And to be crystal clear whether we are on the same page in understanding of "lazy" from the quoted C++ article:
// Initialize by using default Lazy<T> constructor. The
// Orders array itself is not created yet.
Lazy<Orders> _orders = new Lazy<Orders>();
// We need to create the array only if displayOrders is true
if (displayOrders == true)
{
DisplayOrders(_orders.Value.OrderData);
}
That is not how useState function argument works:
const [value, setValue] = useState(() => ({orderData: 'data'))
// value.orderData already exists on the above line, React does NOT defer evaluation until the line below
if (displayOrders === true) {
console.log(value.orderData)
}
// value.orderData already exists on the above line, React does NOT defer evaluation until the line below
It couldn't know this without an actual compiler. As soon as the state is declared it is considered "used". But I understand how this is an overly technical explanation.
I would still caution against changing the term because it might make perfect sense right now to some people (though I am biased here because it makes sense to me). I'd rather explain the term in the context of React. The docs already use a bunch of terms that aren't technically correct in every instance (pure, side-effect free, memoization).
I'm with @eps1lon. Specifically:
So for me I do consider the state lazy. That lazy init just happens to be needed sync on mount. On updates React is actually lazy and just ignores it.
But I also understand the problem that @Aprillion mentions. Maybe we can call it something like function argument
or initializer
, just to stop any confusion?
Is there any good reason to call the
useState(() => ...)
feature "Lazy initial state"? The actual implementation is synchronous ("greedy") as far as I can tell, so I would propose to rename it to "Functional initial state" (for consistency with "Functional updates").Related "Lazy initialization" in useReducer section => I would propose "Initialization function"
I can make a PR unless someone points out some aspect of the naming that I missed.