Notes on Flow 0.101
The upgrade to Flow v0.101 was surprisingly quiet in our code base. But the release notes contain some interesting reads which I'll summarize as: a looming default on inexact object, retapping on React.memo
and React.lazy
, and LSP and performance improvements.
Here are some notes I jotted down as I learn about the changes. I've started building this Flow Notes repo too.
Note This is not a complete list of the changes. There are many more commits and especially around around LSP and performance optimizations that I have not written about.
We released a new implicit-inexact-object lint to detect when an inexact object is used without explicitly adding
...
to the end of the props list.
This looks like a scary future causal of unhappiness because after then objects will be exact by default. And reading On the Roadmap: Exact Objects by Default, it seems that this has been plotted for a while and may land anytime. Although, as you may read in the related commit, this linting rule is still an opt-in. Winter hasn't come yet.
React.memo
andReact.lazy
now both allow you to specify an instance type viaReact.AbstractComponent
.
Previously React.memo
and React.lazy
used React.ComponentType
which became stricter after v0.100. This recent change that landed on v0.101 changed React.memo
and React.lazy
to use React.AbstractComponent
internally. They now both take an extra type parameter for the instance. It also means that we can now seamlessly pass the returns of React.forwardRef
to React.memo
and React.lazy
. Check out the following test files for demos:
React.memo
React.memo
, with memo
for memoization, is used on functional components that are supposed to render the same result given the same props.
The signature of React.memo
is as follows:
declare export function memo<Config, Instance = mixed>(
component: React$AbstractComponent<Config, Instance>,
equal?: (Config, Config) => boolean
): React$AbstractComponent<Config, Instance>
And we can use it this way:
// @flow
const React = require('react')
type Props = {| cornStyle: 'spiraling' | 'colorful' |}
function Unicorn(x: Props) {
return null
}
const MemoUnicorn = React.memo<Props>(Unicorn)
const memo_unicorn_ok = () => <MemoUnicorn cornStyle="spiraling" /> // ok
const memo_unicorn_error = () => <MemoUnicorn corn="spiraling" /> // error
We may optionally provide an equal
method as a performance optimization. React.memo
provides the type information for the method with the same props type we supplied, and we don't need any extra type annotations for it. And since we did provide a Props
type parameter to React.memo
, Flow is able to catch errors on the equal
method:
const memo_unicorn_with_equal_ok =
React.memo<Props>(Unicorn, (props1, props2) => props1.cornStyle === props2.cornStyle); // ok
const memo_unicorn_with_equal_error =
React.memo<Props>(Unicorn, (props1, props2) => props1.corn === props2.corn); // error
And we can pass the returns of React.forwardRef
to React.memo
:
const { useImperativeHandle } = React
function Demo(props, ref) {
useImperativeHandle(ref, () => ({
moo(x: string) {},
}))
return null
}
const Memo = React.memo(React.forwardRef(Demo))
function App() {
// Error below: moo expects a string, given a number
return <Memo ref={ref => ref && ref.moo(0)} />
}
React.lazy
React.lazy
currently lets us render a dynamic import as a regular component. The signature of React.lazy
tells us that it takes an async function which resolves to a module export, which in turn contains a default
field:
declare export function lazy<P>(
component: () => Promise<{ default: React$ComponentType<P>, ... }>
): React$ComponentType<P>
Here's how we may annotate a React.lazy
loaded funcitonal component:
//@flow
const React = require('react')
type Props = {| cornStyle: 'spiraling' | 'colorful' |}
function Unicorn(x: Props) {
return null
}
const LazyFunctionComponent = React.lazy(() =>
Promise.resolve({ default: Unicorn })
)
const lazy_unicorn_ok = () => <LazyUnicorn cornStyle="spiraling" /> // ok
const lazy_unicorn_error = () => <LazyUnicorn corn="spiraling" /> // error
Likewise, we can pass the returns of React.forwardRef
to React.lazy
:
const { useImperativeHandle } = React
function Demo(props, ref) {
useImperativeHandle(ref, () => ({
moo(x: string) {},
}))
return null
}
const Lazy = React.lazy(async () => ({
default: React.forwardRef(Demo),
}))
function App() {
// Error below: moo expects a string, given a number
return (
<React.Suspense fallback="Loading...">
<Lazy ref={ref => ref && ref.moo(0)} />;
</React.Suspense>
)
}
✌️ Till next time
Recently I've started organizing my notes to a new GitHub repo: Flow Notes. Mainly because:
- These notes work better when organized by topics instead of as stream of versioned updates
- The repo can be a home base where people can check back for Flow usages and questions
- It's easier to collaborate
This is also inspired by React TypeScript Cheatsheet maintained by @swyx and a few others. Our brother faction TypeScript has much better community support than Flow. They have books, a number of cheatsheets and guides, active channels such as r/typescript, etc. Whereas none of Flow's communities are effectively active, this includes r/flowtype, stack overflow, Reactiflux, searching flowtype under r/react, etc.
As I learn more about Flow, I realize that much of the high quality documentation is in Flow's and Flow Typed's code base. Even the Flow team constantly encourages people to read the type definitions from the source code directly, and references people to those places. While it is very true and the source code is my major source of understanding, checking the source code for docs is probably not the default behavior by normal users. People get frustrated when their main goal is to implement their features and not to become expert in Flow. Furthermore, while the code is indeed very self-documented, the code base contains much more information than what is needed by normal users. Somehow that reminds me of legal documents ¯\_(ツ)_/¯
That said, I believe there is more work that needs done in its documentation. And I'd like to try if herding my notes in GitHub is an effective idea. So do check it out if it sounds like something that may be helpful, let me know what is needed and in what format, and it'd be even greater if you'd like to join this party of putting up organized notes for Flow.
References
Resources
- v0.101 release notes
- On the Roadmap: Exact Objects by Default
React.memo
on Flow NotesReact.lazy
on Flow Notes
Commits
- Strictify
React$ComponentType
andReact$ElementType
- Update React.memo and React.lazy to account for refs
$Keys
should produce a union ofSingletonStrTs
instead ofStrTs
I have not yet written about this, but it is rather interesting- Do not include implicit inexact object lint in all
Examples
- Demo lazy component with forward ref
- Demo memo component with forward ref
- Flow Try:
React.memo
- Flow Try:
React.lazy
React